소스 검색

JWT: add token audience

a token released for API audience cannot be used for web pages and
vice-versa
Nicola Murino 4 년 전
부모
커밋
4f609cfa30
6개의 변경된 파일194개의 추가작업 그리고 133개의 파일을 삭제
  1. 4 4
      httpd/api_admin.go
  2. 10 2
      httpd/auth_utils.go
  3. 167 125
      httpd/httpd_test.go
  4. 1 1
      httpd/internal_test.go
  5. 11 0
      httpd/middleware.go
  6. 1 1
      httpd/server.go

+ 4 - 4
httpd/api_admin.go

@@ -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

+ 10 - 2
httpd/auth_utils.go

@@ -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
 	}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 167 - 125
httpd/httpd_test.go


+ 1 - 1
httpd/internal_test.go

@@ -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)

+ 11 - 0
httpd/middleware.go

@@ -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)

+ 1 - 1
httpd/server.go

@@ -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)

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.