mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-08 07:10:56 +03:00
JWT: add token audience
a token released for API audience cannot be used for web pages and vice-versa
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user