diff --git a/api/jwt/jwt.go b/api/jwt/jwt.go index 5fb031cfd..f0455c5b6 100644 --- a/api/jwt/jwt.go +++ b/api/jwt/jwt.go @@ -13,6 +13,8 @@ import ( "github.com/rs/zerolog/log" ) +const year = time.Hour * 24 * 365 + // scope represents JWT scopes that are supported in JWT claims. type scope string @@ -29,7 +31,7 @@ type claims struct { Role int `json:"role"` Scope scope `json:"scope"` ForceChangePassword bool `json:"forceChangePassword"` - jwt.StandardClaims + jwt.RegisteredClaims } var ( @@ -98,7 +100,7 @@ func (service *Service) defaultExpireAt() time.Time { // GenerateToken generates a new JWT token. func (service *Service) GenerateToken(data *portainer.TokenData) (string, time.Time, error) { expiryTime := service.defaultExpireAt() - token, err := service.generateSignedToken(data, expiryTime.Unix(), defaultScope) + token, err := service.generateSignedToken(data, expiryTime, defaultScope) return token, expiryTime, err } @@ -121,7 +123,7 @@ func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData, if err != nil { return nil, errInvalidJWTToken } - if user.TokenIssueAt > cl.StandardClaims.IssuedAt { + if user.TokenIssueAt > cl.RegisteredClaims.ExpiresAt.Unix() { return nil, errInvalidJWTToken } @@ -156,7 +158,7 @@ func (service *Service) SetUserSessionDuration(userSessionDuration time.Duration service.userSessionTimeout = userSessionDuration } -func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt int64, scope scope) (string, error) { +func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt time.Time, scope scope) (string, error) { secret, found := service.secrets[scope] if !found { return "", fmt.Errorf("invalid scope: %v", scope) @@ -170,7 +172,7 @@ func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt if settings.IsDockerDesktopExtension { // Set expiration to 99 years for docker desktop extension. log.Info().Msg("detected docker desktop extension mode") - expiresAt = time.Now().Add(time.Hour * 8760 * 99).Unix() + expiresAt = time.Now().Add(year * 99) } cl := claims{ @@ -179,10 +181,12 @@ func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt Role: int(data.Role), Scope: scope, ForceChangePassword: data.ForceChangePassword, - StandardClaims: jwt.StandardClaims{ - ExpiresAt: expiresAt, - IssuedAt: time.Now().Unix(), - }, + } + + if !expiresAt.IsZero() { + cl.RegisteredClaims = jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expiresAt), + } } token := jwt.NewWithClaims(jwt.SigningMethodHS256, cl) diff --git a/api/jwt/jwt_kubeconfig.go b/api/jwt/jwt_kubeconfig.go index 7cdd1d6b9..3f7cf7b8a 100644 --- a/api/jwt/jwt_kubeconfig.go +++ b/api/jwt/jwt_kubeconfig.go @@ -18,9 +18,9 @@ func (service *Service) GenerateTokenForKubeconfig(data *portainer.TokenData) (s return "", err } - expiryAt := time.Now().Add(expiryDuration).Unix() + expiryAt := time.Now().Add(expiryDuration) if expiryDuration == time.Duration(0) { - expiryAt = 0 + expiryAt = time.Time{} } return service.generateSignedToken(data, expiryAt, kubeConfigScope) diff --git a/api/jwt/jwt_kubeconfig_test.go b/api/jwt/jwt_kubeconfig_test.go index b3796b129..2b5e43ab3 100644 --- a/api/jwt/jwt_kubeconfig_test.go +++ b/api/jwt/jwt_kubeconfig_test.go @@ -43,14 +43,14 @@ func TestService_GenerateTokenForKubeconfig(t *testing.T) { name string fields fields args args - wantExpiresAt int64 + wantExpiresAt *jwt.NumericDate wantErr bool }{ { name: "kubeconfig no expiry", fields: myFields, args: myArgs, - wantExpiresAt: 0, + wantExpiresAt: nil, wantErr: false, }, } diff --git a/api/jwt/jwt_test.go b/api/jwt/jwt_test.go index e98731eb2..f83b6c520 100644 --- a/api/jwt/jwt_test.go +++ b/api/jwt/jwt_test.go @@ -20,7 +20,7 @@ func TestGenerateSignedToken(t *testing.T) { ID: 1, Role: 1, } - expiresAt := time.Now().Add(1 * time.Hour).Unix() + expiresAt := time.Now().Add(1 * time.Hour) generatedToken, err := svc.generateSignedToken(token, expiresAt, defaultScope) assert.NoError(t, err, "failed to generate a signed token") @@ -36,7 +36,7 @@ func TestGenerateSignedToken(t *testing.T) { assert.Equal(t, token.Username, tokenClaims.Username) assert.Equal(t, int(token.ID), tokenClaims.UserID) assert.Equal(t, int(token.Role), tokenClaims.Role) - assert.Equal(t, expiresAt, tokenClaims.ExpiresAt) + assert.Equal(t, jwt.NewNumericDate(expiresAt), tokenClaims.ExpiresAt) } func TestGenerateSignedToken_InvalidScope(t *testing.T) { @@ -49,7 +49,7 @@ func TestGenerateSignedToken_InvalidScope(t *testing.T) { ID: 1, Role: 1, } - expiresAt := time.Now().Add(1 * time.Hour).Unix() + expiresAt := time.Now().Add(1 * time.Hour) _, err = svc.generateSignedToken(token, expiresAt, "testing") assert.Error(t, err)