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
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 { func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
if currentPassword == "" || newPassword == "" || confirmNewPassword == "" { 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 { 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 { 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) claims, err := getTokenClaims(r)
if err != nil { if err != nil {
@@ -191,7 +191,7 @@ func doChangeAdminPassword(r *http.Request, currentPassword, newPassword, confir
} }
match, err := admin.CheckPassword(currentPassword) match, err := admin.CheckPassword(currentPassword)
if !match || err != nil { if !match || err != nil {
return dataprovider.NewValidationError("Current password does not match") return dataprovider.NewValidationError("current password does not match")
} }
admin.Password = newPassword admin.Password = newPassword

View File

@@ -12,6 +12,13 @@ import (
"github.com/drakkan/sftpgo/utils" "github.com/drakkan/sftpgo/utils"
) )
type tokenAudience = string
const (
tokenAudienceWeb tokenAudience = "Web"
tokenAudienceAPI tokenAudience = "API"
)
const ( const (
claimUsernameKey = "username" claimUsernameKey = "username"
claimPermissionsKey = "permissions" claimPermissionsKey = "permissions"
@@ -87,13 +94,14 @@ func (c *jwtTokenClaims) hasPerm(perm string) bool {
return utils.IsStringInSlice(perm, c.Permissions) 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() claims := c.asMap()
now := time.Now().UTC() now := time.Now().UTC()
claims[jwt.JwtIDKey] = xid.New().String() claims[jwt.JwtIDKey] = xid.New().String()
claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second) claims[jwt.NotBeforeKey] = now.Add(-30 * time.Second)
claims[jwt.ExpirationKey] = now.Add(tokenDuration) claims[jwt.ExpirationKey] = now.Add(tokenDuration)
claims[jwt.AudienceKey] = audience
token, tokenString, err := tokenAuth.Encode(claims) token, tokenString, err := tokenAuth.Encode(claims)
if err != nil { 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 { 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 { if err != nil {
return err 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, Permissions: admin.Permissions,
Signature: admin.GetSignature(), Signature: admin.GetSignature(),
} }
token, err := c.createTokenResponse(server.tokenAuth) token, err := c.createTokenResponse(server.tokenAuth, tokenAudienceWeb)
assert.NoError(t, err) assert.NoError(t, err)
form := make(url.Values) form := make(url.Values)

View File

@@ -8,6 +8,7 @@ import (
"github.com/lestrrat-go/jwx/jwt" "github.com/lestrrat-go/jwx/jwt"
"github.com/drakkan/sftpgo/logger" "github.com/drakkan/sftpgo/logger"
"github.com/drakkan/sftpgo/utils"
) )
type ctxKeyConnAddr int type ctxKeyConnAddr int
@@ -37,6 +38,11 @@ func jwtAuthenticator(next http.Handler) http.Handler {
sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) sendAPIResponse(w, r, err, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return 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) { if isTokenInvalidated(r) {
logger.Debug(logSender, "", "the token has been invalidated") logger.Debug(logSender, "", "the token has been invalidated")
sendAPIResponse(w, r, nil, "Your token is no longer valid", http.StatusUnauthorized) 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) http.Redirect(w, r, webLoginPath, http.StatusFound)
return 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) { if isTokenInvalidated(r) {
logger.Debug(logSender, "", "the token has been invalidated") logger.Debug(logSender, "", "the token has been invalidated")
http.Redirect(w, r, webLoginPath, http.StatusFound) 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(), Signature: admin.GetSignature(),
} }
resp, err := c.createTokenResponse(s.tokenAuth) resp, err := c.createTokenResponse(s.tokenAuth, tokenAudienceAPI)
if err != nil { if err != nil {
sendAPIResponse(w, r, err, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) sendAPIResponse(w, r, err, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)