JWT: add token audience

a token released for API audience cannot be used for web pages and
vice-versa
This commit is contained in:
Nicola Murino 2021-02-02 09:14:10 +01:00
parent 78bf808322
commit 4f609cfa30
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
6 changed files with 299 additions and 184 deletions

View file

@ -173,13 +173,13 @@ func changeAdminPassword(w http.ResponseWriter, r *http.Request) {
func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
if currentPassword == "" || newPassword == "" || confirmNewPassword == "" {
return dataprovider.NewValidationError("Please provide the current password and the new one two times")
return dataprovider.NewValidationError("please provide the current password and the new one two times")
}
if newPassword != confirmNewPassword {
return dataprovider.NewValidationError("The two password fields do not match")
return dataprovider.NewValidationError("the two password fields do not match")
}
if currentPassword == newPassword {
return dataprovider.NewValidationError("The new password must be different from the current one")
return dataprovider.NewValidationError("the new password must be different from the current one")
}
claims, err := getTokenClaims(r)
if err != nil {
@ -191,7 +191,7 @@ func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confir
}
match, err := admin.CheckPassword(currentPassword)
if !match || err != nil {
return dataprovider.NewValidationError("Current password does not match")
return dataprovider.NewValidationError("current password does not match")
}
admin.Password = newPassword

View file

@ -12,6 +12,13 @@ import (
"github.com/drakkan/sftpgo/utils"
)
type tokenAudience = string
const (
tokenAudienceWeb tokenAudience = "Web"
tokenAudienceAPI tokenAudience = "API"
)
const (
claimUsernameKey = "username"
claimPermissionsKey = "permissions"
@ -87,13 +94,14 @@ func (c *jwtTokenClaims) hasPerm(perm string) bool {
return utils.IsStringInSlice(perm, c.Permissions)
}
func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth) (map[string]interface{}, error) {
func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth, audience tokenAudience) (map[string]interface{}, error) {
claims := c.asMap()
now := time.Now().UTC()
claims[jwt.JwtIDKey] = xid.New().String()
claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second)
claims[jwt.ExpirationKey] = now.Add(tokenDuration)
claims[jwt.AudienceKey] = audience
token, tokenString, err := tokenAuth.Encode(claims)
if err != nil {
@ -108,7 +116,7 @@ func (c *jwtTokenClaims) createTokenResponse(tokenAuth *jwtauth.JWTAuth) (map[st
}
func (c *jwtTokenClaims) createAndSetCookie(w http.ResponseWriter, r *http.Request, tokenAuth *jwtauth.JWTAuth) error {
resp, err := c.createTokenResponse(tokenAuth)
resp, err := c.createTokenResponse(tokenAuth, tokenAudienceWeb)
if err != nil {
return err
}

File diff suppressed because it is too large Load diff

View file

@ -352,7 +352,7 @@ func TestUpdateWebAdminInvalidClaims(t *testing.T) {
Permissions: admin.Permissions,
Signature: admin.GetSignature(),
}
token, err := c.createTokenResponse(server.tokenAuth)
token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWeb)
assert.NoError(t, err)
form := make(url.Values)

View file

@ -8,6 +8,7 @@ import (
"github.com/lestrrat-go/jwx/jwt"
"github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils"
)
type ctxKeyConnAddr int
@ -37,6 +38,11 @@ func jwtAuthenticator(next http.Handler) http.Handler {
sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return
}
if !utils.IsStringInSlice(tokenAudienceAPI, token.Audience()) {
logger.Debug(logSender, "", "the token audience is not valid")
sendAPIResponse(w, r, nil, "Your token audience is not valid", http.StatusUnauthorized)
return
}
if isTokenInvalidated(r) {
logger.Debug(logSender, "", "the token has been invalidated")
sendAPIResponse(w, r, nil, "Your token is no longer valid", http.StatusUnauthorized)
@ -64,6 +70,11 @@ func jwtAuthenticatorWeb(next http.Handler) http.Handler {
http.Redirect(w, r, webLoginPath, http.StatusFound)
return
}
if !utils.IsStringInSlice(tokenAudienceWeb, token.Audience()) {
logger.Debug(logSender, "", "the token audience is not valid")
http.Redirect(w, r, webLoginPath, http.StatusFound)
return
}
if isTokenInvalidated(r) {
logger.Debug(logSender, "", "the token has been invalidated")
http.Redirect(w, r, webLoginPath, http.StatusFound)

View file

@ -175,7 +175,7 @@ func (s *httpdServer) checkAddrAndSendToken(w http.ResponseWriter, r *http.Reque
Signature: admin.GetSignature(),
}
resp, err := c.createTokenResponse(s.tokenAuth)
resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI)
if err != nil {
sendAPIResponse(w, r, err, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)