Refactor token handling to support optional extra claims in access token generation
- Updated storeAccessToken and related methods to accept an optional extraClaims parameter for additional flexibility. - Modified MakeAccessToken and generateAccessTokenWithScope functions to incorporate extra claims, enhancing token customization. - Adjusted tests to utilize the new extraClaims parameter, ensuring comprehensive coverage of the updated functionality.
This commit is contained in:
parent
60c830099e
commit
07cc11b363
7 changed files with 169 additions and 70 deletions
|
|
@ -240,7 +240,7 @@ func (s *Service) RefreshToken(ctx context.Context, refreshToken string, scope .
|
|||
|
||||
// Generate new access token with final scope
|
||||
expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds())
|
||||
newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn)
|
||||
newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn, nil)
|
||||
if err != nil {
|
||||
return nil, &types.ErrorResponse{
|
||||
Code: types.ErrorServerError,
|
||||
|
|
@ -339,7 +339,7 @@ func (s *Service) RotateRefreshToken(ctx context.Context, oldToken string, reque
|
|||
|
||||
// Generate new tokens with final scope and original subject
|
||||
expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds())
|
||||
newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn)
|
||||
newAccessToken, err := s.generateAccessTokenWithScope(clientID, finalScope, originalSubject, expiresIn, nil)
|
||||
if err != nil {
|
||||
return nil, &types.ErrorResponse{
|
||||
Code: types.ErrorServerError,
|
||||
|
|
@ -428,7 +428,7 @@ func (s *Service) handleAuthorizationCodeGrant(ctx context.Context, client *type
|
|||
|
||||
// Generate and store access token with proper scope and subject
|
||||
expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds())
|
||||
accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn)
|
||||
accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn, nil)
|
||||
if err != nil {
|
||||
return nil, &types.ErrorResponse{
|
||||
Code: types.ErrorServerError,
|
||||
|
|
@ -464,7 +464,7 @@ func (s *Service) handleClientCredentialsGrant(ctx context.Context, client *type
|
|||
|
||||
// Generate and store access token with client's scope (no user subject for client credentials)
|
||||
expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds())
|
||||
accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, "", expiresIn)
|
||||
accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, "", expiresIn, nil)
|
||||
if err != nil {
|
||||
return nil, &types.ErrorResponse{
|
||||
Code: types.ErrorServerError,
|
||||
|
|
@ -507,7 +507,7 @@ func (s *Service) handleRefreshTokenGrant(ctx context.Context, client *types.Cli
|
|||
|
||||
// Generate and store new access token with proper scope and subject
|
||||
expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds())
|
||||
accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn)
|
||||
accessToken, err := s.generateAccessTokenWithScope(client.ClientID, scope, subject, expiresIn, nil)
|
||||
if err != nil {
|
||||
return nil, &types.ErrorResponse{
|
||||
Code: types.ErrorServerError,
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ func TestRevoke(t *testing.T) {
|
|||
clientID := GetActualClientID(testClients[0].ClientID)
|
||||
|
||||
// Store token using the new method
|
||||
err := service.storeAccessToken(token, clientID, "", "", 3600)
|
||||
err := service.storeAccessToken(token, clientID, "", "", 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = service.Revoke(ctx, token, "access_token")
|
||||
|
|
|
|||
|
|
@ -128,14 +128,14 @@ var (
|
|||
|
||||
// DefaultTeamFields contains basic team fields
|
||||
DefaultTeamFields = []interface{}{
|
||||
"id", "team_id", "name", "display_name", "description", "website", "logo",
|
||||
"team_id", "name", "display_name", "description", "website", "logo",
|
||||
"owner_id", "status", "type_id", "type", "is_verified", "verified_at",
|
||||
"created_at", "updated_at",
|
||||
}
|
||||
|
||||
// DefaultTeamDetailFields contains all team fields including contact info and metadata
|
||||
DefaultTeamDetailFields = []interface{}{
|
||||
"id", "team_id", "name", "display_name", "description", "website", "logo",
|
||||
"team_id", "name", "display_name", "description", "website", "logo",
|
||||
"owner_id", "contact_email", "contact_phone", "is_verified", "verified_at", "verified_by",
|
||||
"team_code", "team_code_type", "status", "type_id", "type", "address", "street_address",
|
||||
"city", "state_province", "postal_code", "country", "country_name", "region", "zoneinfo",
|
||||
|
|
@ -144,14 +144,14 @@ var (
|
|||
|
||||
// DefaultMemberFields contains basic member fields
|
||||
DefaultMemberFields = []interface{}{
|
||||
"id", "team_id", "user_id", "member_type", "role_id", "status",
|
||||
"team_id", "user_id", "member_type", "role_id", "status",
|
||||
"invitation_id", "invited_by", "invited_at", "joined_at", "invitation_token", "invitation_expires_at",
|
||||
"last_active_at", "login_count", "message", "created_at", "updated_at",
|
||||
}
|
||||
|
||||
// DefaultMemberDetailFields contains all member fields including robot config and permissions
|
||||
DefaultMemberDetailFields = []interface{}{
|
||||
"id", "team_id", "user_id", "member_type", "role_id", "status",
|
||||
"team_id", "user_id", "member_type", "role_id", "status",
|
||||
"robot_name", "robot_description", "robot_avatar", "robot_config", "agents", "tools",
|
||||
"mcp_servers", "data_access_permissions", "system_prompt", "is_active_robot",
|
||||
"schedule_config", "random_activity", "activity_frequency", "last_robot_activity",
|
||||
|
|
|
|||
|
|
@ -434,15 +434,21 @@ func (s *Service) GetKeyID() string {
|
|||
}
|
||||
|
||||
// SignToken signs a token based on the configured format (jwt or opaque)
|
||||
func (s *Service) SignToken(tokenType, clientID, scope, subject string, expiresIn int) (string, error) {
|
||||
// extraClaims: optional extra claims to add to the token (e.g., team_id, tenant_id)
|
||||
func (s *Service) SignToken(tokenType, clientID, scope, subject string, expiresIn int, extraClaims ...map[string]interface{}) (string, error) {
|
||||
var claims map[string]interface{}
|
||||
if len(extraClaims) > 0 {
|
||||
claims = extraClaims[0]
|
||||
}
|
||||
|
||||
switch s.config.Token.AccessTokenFormat {
|
||||
case "jwt":
|
||||
return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn)
|
||||
return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn, claims)
|
||||
case "opaque":
|
||||
return s.signOpaqueToken(tokenType, clientID, scope, subject)
|
||||
default:
|
||||
// Default to JWT if format is not specified or unknown
|
||||
return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn)
|
||||
return s.signJWTToken(tokenType, clientID, scope, subject, expiresIn, claims)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -577,25 +583,32 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t
|
|||
}
|
||||
|
||||
// signJWTToken signs a JWT token using the configured signing algorithm
|
||||
func (s *Service) signJWTToken(tokenType, clientID, scope, subject string, expiresIn int) (string, error) {
|
||||
func (s *Service) signJWTToken(tokenType, clientID, scope, subject string, expiresIn int, extraClaims map[string]interface{}) (string, error) {
|
||||
if s.signingCerts == nil || s.signingCerts.SigningKey == nil {
|
||||
return "", fmt.Errorf("signing certificates not initialized")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
claims := &types.JWTClaims{
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Issuer: s.config.IssuerURL,
|
||||
Subject: subject,
|
||||
Audience: clientID,
|
||||
ExpiresAt: now.Add(time.Duration(expiresIn) * time.Second).Unix(),
|
||||
NotBefore: now.Unix(),
|
||||
IssuedAt: now.Unix(),
|
||||
Id: generateJTI(),
|
||||
},
|
||||
ClientID: clientID,
|
||||
Scope: scope,
|
||||
TokenType: tokenType,
|
||||
|
||||
// Use MapClaims to support extra claims
|
||||
claims := jwt.MapClaims{
|
||||
// Standard JWT claims
|
||||
"iss": s.config.IssuerURL,
|
||||
"sub": subject,
|
||||
"aud": clientID,
|
||||
"exp": now.Add(time.Duration(expiresIn) * time.Second).Unix(),
|
||||
"nbf": now.Unix(),
|
||||
"iat": now.Unix(),
|
||||
"jti": generateJTI(),
|
||||
// Custom OAuth claims
|
||||
"client_id": clientID,
|
||||
"scope": scope,
|
||||
"token_type": tokenType,
|
||||
}
|
||||
|
||||
// Add extra claims if provided (e.g., team_id, tenant_id)
|
||||
for key, value := range extraClaims {
|
||||
claims[key] = value
|
||||
}
|
||||
|
||||
// Create token with claims
|
||||
|
|
@ -614,8 +627,8 @@ func (s *Service) verifyJWTToken(tokenString string) (*types.TokenClaims, error)
|
|||
return nil, fmt.Errorf("signing certificates not initialized")
|
||||
}
|
||||
|
||||
// Parse token with claims
|
||||
token, err := jwt.ParseWithClaims(tokenString, &types.JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// Parse token with MapClaims to support extra claims
|
||||
token, err := jwt.ParseWithClaims(tokenString, jwt.MapClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// Validate signing method
|
||||
expectedMethod := getSigningMethod(s.config.Token.AccessTokenSigningAlg)
|
||||
if token.Method != expectedMethod {
|
||||
|
|
@ -635,22 +648,75 @@ func (s *Service) verifyJWTToken(tokenString string) (*types.TokenClaims, error)
|
|||
}
|
||||
|
||||
// Extract claims
|
||||
jwtClaims, ok := token.Claims.(*types.JWTClaims)
|
||||
mapClaims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid JWT claims type")
|
||||
}
|
||||
|
||||
// Convert to TokenClaims
|
||||
tokenClaims := &types.TokenClaims{
|
||||
Subject: jwtClaims.Subject,
|
||||
ClientID: jwtClaims.ClientID,
|
||||
Scope: jwtClaims.Scope,
|
||||
TokenType: jwtClaims.TokenType,
|
||||
ExpiresAt: time.Unix(jwtClaims.ExpiresAt, 0),
|
||||
IssuedAt: time.Unix(jwtClaims.IssuedAt, 0),
|
||||
Issuer: jwtClaims.Issuer,
|
||||
Audience: []string{jwtClaims.Audience},
|
||||
JTI: jwtClaims.Id,
|
||||
Extra: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
// Extract standard claims
|
||||
if sub, ok := mapClaims["sub"].(string); ok {
|
||||
tokenClaims.Subject = sub
|
||||
}
|
||||
if clientID, ok := mapClaims["client_id"].(string); ok {
|
||||
tokenClaims.ClientID = clientID
|
||||
}
|
||||
if scope, ok := mapClaims["scope"].(string); ok {
|
||||
tokenClaims.Scope = scope
|
||||
}
|
||||
if tokenType, ok := mapClaims["token_type"].(string); ok {
|
||||
tokenClaims.TokenType = tokenType
|
||||
}
|
||||
if iss, ok := mapClaims["iss"].(string); ok {
|
||||
tokenClaims.Issuer = iss
|
||||
}
|
||||
if jti, ok := mapClaims["jti"].(string); ok {
|
||||
tokenClaims.JTI = jti
|
||||
}
|
||||
|
||||
// Extract time claims
|
||||
if exp, ok := mapClaims["exp"].(float64); ok {
|
||||
tokenClaims.ExpiresAt = time.Unix(int64(exp), 0)
|
||||
}
|
||||
if iat, ok := mapClaims["iat"].(float64); ok {
|
||||
tokenClaims.IssuedAt = time.Unix(int64(iat), 0)
|
||||
}
|
||||
|
||||
// Extract audience
|
||||
if aud, ok := mapClaims["aud"].(string); ok {
|
||||
tokenClaims.Audience = []string{aud}
|
||||
} else if audArray, ok := mapClaims["aud"].([]interface{}); ok {
|
||||
audience := make([]string, 0, len(audArray))
|
||||
for _, a := range audArray {
|
||||
if audStr, ok := a.(string); ok {
|
||||
audience = append(audience, audStr)
|
||||
}
|
||||
}
|
||||
tokenClaims.Audience = audience
|
||||
}
|
||||
|
||||
// Extract extended claims for multi-tenancy and team support
|
||||
if teamID, ok := mapClaims["team_id"].(string); ok {
|
||||
tokenClaims.TeamID = teamID
|
||||
}
|
||||
if tenantID, ok := mapClaims["tenant_id"].(string); ok {
|
||||
tokenClaims.TenantID = tenantID
|
||||
}
|
||||
|
||||
// Store all extra claims for flexibility
|
||||
standardClaims := map[string]bool{
|
||||
"sub": true, "client_id": true, "scope": true, "token_type": true,
|
||||
"exp": true, "iat": true, "nbf": true, "iss": true, "aud": true, "jti": true,
|
||||
"team_id": true, "tenant_id": true,
|
||||
}
|
||||
for key, value := range mapClaims {
|
||||
if !standardClaims[key] {
|
||||
tokenClaims.Extra[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return tokenClaims, nil
|
||||
|
|
|
|||
|
|
@ -248,8 +248,13 @@ func (s *Service) ValidateTokenBinding(ctx context.Context, token string, bindin
|
|||
// ============================================================================
|
||||
|
||||
// MakeAccessToken generates a new access token with specific parameters and stores it
|
||||
func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int) (string, error) {
|
||||
return s.generateAccessTokenWithScope(clientID, scope, subject, expiresIn)
|
||||
// extraClaims: optional extra claims to add to the token (e.g., team_id, tenant_id)
|
||||
func (s *Service) MakeAccessToken(clientID, scope, subject string, expiresIn int, extraClaims ...map[string]interface{}) (string, error) {
|
||||
var claims map[string]interface{}
|
||||
if len(extraClaims) > 0 {
|
||||
claims = extraClaims[0]
|
||||
}
|
||||
return s.generateAccessTokenWithScope(clientID, scope, subject, expiresIn, claims)
|
||||
}
|
||||
|
||||
// MakeRefreshToken generates a new refresh token with specific parameters and stores it
|
||||
|
|
@ -338,19 +343,27 @@ func (s *Service) validateAudience(audience string) error {
|
|||
// generateAccessToken generates a new access token
|
||||
func (s *Service) generateAccessToken(clientID string) (string, error) {
|
||||
expiresIn := int(s.config.Token.AccessTokenLifetime.Seconds())
|
||||
return s.generateAccessTokenWithScope(clientID, "", "", expiresIn)
|
||||
return s.generateAccessTokenWithScope(clientID, "", "", expiresIn, nil)
|
||||
}
|
||||
|
||||
// generateAccessTokenWithScope generates a new access token with specific parameters and stores it
|
||||
func (s *Service) generateAccessTokenWithScope(clientID, scope, subject string, expiresIn int) (string, error) {
|
||||
func (s *Service) generateAccessTokenWithScope(clientID, scope, subject string, expiresIn int, extraClaims map[string]interface{}) (string, error) {
|
||||
// Use the new signing mechanism based on configuration
|
||||
accessToken, err := s.SignToken("access_token", clientID, scope, subject, expiresIn)
|
||||
var accessToken string
|
||||
var err error
|
||||
|
||||
if extraClaims != nil {
|
||||
accessToken, err = s.SignToken("access_token", clientID, scope, subject, expiresIn, extraClaims)
|
||||
} else {
|
||||
accessToken, err = s.SignToken("access_token", clientID, scope, subject, expiresIn)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Store access token with metadata
|
||||
err = s.storeAccessToken(accessToken, clientID, scope, subject, expiresIn)
|
||||
// Store access token with metadata (including extra claims)
|
||||
err = s.storeAccessToken(accessToken, clientID, scope, subject, expiresIn, extraClaims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -359,7 +372,7 @@ func (s *Service) generateAccessTokenWithScope(clientID, scope, subject string,
|
|||
}
|
||||
|
||||
// storeAccessToken stores access token with metadata and specified expiration
|
||||
func (s *Service) storeAccessToken(accessToken, clientID string, scope string, subject string, expiresIn int) error {
|
||||
func (s *Service) storeAccessToken(accessToken, clientID string, scope string, subject string, expiresIn int, extraClaims map[string]interface{}) error {
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(time.Duration(expiresIn) * time.Second).Unix()
|
||||
|
||||
|
|
@ -373,6 +386,11 @@ func (s *Service) storeAccessToken(accessToken, clientID string, scope string, s
|
|||
"expires_at": expiresAt,
|
||||
}
|
||||
|
||||
// Add extra claims if provided (e.g., team_id, tenant_id)
|
||||
for key, value := range extraClaims {
|
||||
tokenData[key] = value
|
||||
}
|
||||
|
||||
ttl := time.Duration(expiresIn) * time.Second
|
||||
return s.store.Set(s.accessTokenKey(accessToken), tokenData, ttl)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func TestIntrospect(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token using the updated method with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
response, err := service.Introspect(ctx, token)
|
||||
|
|
@ -49,7 +49,7 @@ func TestIntrospect(t *testing.T) {
|
|||
|
||||
// Store expired token with negative expiresIn (already expired)
|
||||
expiresIn := -3600 // Expired 1 hour ago
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, expiresIn)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, expiresIn, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
response, err := service.Introspect(ctx, token)
|
||||
|
|
@ -72,7 +72,7 @@ func TestIntrospect(t *testing.T) {
|
|||
clientID := GetActualClientID(testClients[0].ClientID)
|
||||
|
||||
// Store minimal token data with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, "", "", 3600)
|
||||
err := service.storeAccessToken(token, clientID, "", "", 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
response, err := service.Introspect(ctx, token)
|
||||
|
|
@ -91,7 +91,7 @@ func TestIntrospect(t *testing.T) {
|
|||
scope := "openid profile"
|
||||
|
||||
// Store token with expiration based on config
|
||||
err := service.storeAccessToken(token, clientID, scope, "", 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, "", 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
response, err := service.Introspect(ctx, token)
|
||||
|
|
@ -119,7 +119,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store subject token with expiresIn parameter
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test token exchange
|
||||
|
|
@ -174,7 +174,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
|
||||
// Store expired token with negative expiresIn
|
||||
expiresIn := -3600 // Expired 1 hour ago
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
response, err := service.TokenExchange(ctx, subjectToken, "urn:ietf:params:oauth:token-type:access_token", "https://api.example.com", "openid profile")
|
||||
|
|
@ -194,7 +194,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store subject token with expiresIn parameter
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test with valid audience (should succeed since audience validation is not enforced)
|
||||
|
|
@ -212,7 +212,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store subject token with expiresIn parameter
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test with empty audience
|
||||
|
|
@ -230,7 +230,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store subject token with expiresIn parameter
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test with invalid scope (should succeed since scope validation is basic)
|
||||
|
|
@ -247,7 +247,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
|
||||
// Store expired subject token with negative expiresIn
|
||||
expiresIn := -3600 // Expired 1 hour ago
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, expiresIn, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
response, err := service.TokenExchange(ctx, subjectToken, "urn:ietf:params:oauth:token-type:access_token", "https://api.example.com", "openid profile")
|
||||
|
|
@ -266,7 +266,7 @@ func TestTokenExchange(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store subject token with expiresIn parameter
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test without audience and scope
|
||||
|
|
@ -299,7 +299,7 @@ func TestValidateTokenAudience(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := service.ValidateTokenAudience(ctx, token, expectedAudience)
|
||||
|
|
@ -317,7 +317,7 @@ func TestValidateTokenAudience(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := service.ValidateTokenAudience(ctx, token, expectedAudience)
|
||||
|
|
@ -335,7 +335,7 @@ func TestValidateTokenAudience(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := service.ValidateTokenAudience(ctx, token, expectedAudience)
|
||||
|
|
@ -354,7 +354,7 @@ func TestValidateTokenAudience(t *testing.T) {
|
|||
|
||||
// Store expired token with negative expiresIn
|
||||
expiresIn := -3600 // Expired 1 hour ago
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, expiresIn)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, expiresIn, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err := service.ValidateTokenAudience(ctx, token, expectedAudience)
|
||||
|
|
@ -413,7 +413,7 @@ func TestValidateTokenBinding(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
binding := &types.TokenBinding{
|
||||
|
|
@ -434,7 +434,7 @@ func TestValidateTokenBinding(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
binding := &types.TokenBinding{
|
||||
|
|
@ -455,7 +455,7 @@ func TestValidateTokenBinding(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
binding := &types.TokenBinding{
|
||||
|
|
@ -476,7 +476,7 @@ func TestValidateTokenBinding(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
binding := &types.TokenBinding{
|
||||
|
|
@ -498,7 +498,7 @@ func TestValidateTokenBinding(t *testing.T) {
|
|||
|
||||
// Store expired token with negative expiresIn
|
||||
expiresIn := -3600 // Expired 1 hour ago
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, expiresIn)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, expiresIn, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
binding := &types.TokenBinding{
|
||||
|
|
@ -670,7 +670,7 @@ func TestTokenIntegration(t *testing.T) {
|
|||
// Step 2: Store token data with expiresIn parameter
|
||||
scope := "openid profile email"
|
||||
subject := testUsers[0].UserID
|
||||
err = service.storeAccessToken(accessToken, clientID, scope, subject, 3600)
|
||||
err = service.storeAccessToken(accessToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Step 3: Introspect token
|
||||
|
|
@ -756,7 +756,7 @@ func TestTokenEdgeCases(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store token with expiresIn parameter (it will handle data types correctly)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(token, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Should handle gracefully
|
||||
|
|
@ -774,7 +774,7 @@ func TestTokenEdgeCases(t *testing.T) {
|
|||
subject := testUsers[0].UserID
|
||||
|
||||
// Store subject token with expiresIn parameter
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600)
|
||||
err := service.storeAccessToken(subjectToken, clientID, scope, subject, 3600, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Very long audience
|
||||
|
|
|
|||
|
|
@ -572,6 +572,13 @@ type TokenClaims struct {
|
|||
Issuer string `json:"iss,omitempty"` // Token issuer
|
||||
Audience []string `json:"aud,omitempty"` // Token audience
|
||||
JTI string `json:"jti,omitempty"` // JWT ID (for JWT tokens)
|
||||
|
||||
// Extended claims for multi-tenancy and team support
|
||||
TeamID string `json:"team_id,omitempty"` // Team identifier
|
||||
TenantID string `json:"tenant_id,omitempty"` // Tenant identifier
|
||||
|
||||
// Extra claims for flexibility
|
||||
Extra map[string]interface{} `json:"-"` // Additional custom claims (not serialized directly)
|
||||
}
|
||||
|
||||
// AuthorizedInfo represents authorized information
|
||||
|
|
@ -581,6 +588,10 @@ type AuthorizedInfo struct {
|
|||
Scope string `json:"scope,omitempty"` // Access scope
|
||||
SessionID string `json:"session_id,omitempty"` // Session ID
|
||||
UserID string `json:"user_id,omitempty"` // User ID
|
||||
|
||||
// Extended fields for multi-tenancy and team support
|
||||
TeamID string `json:"team_id,omitempty"` // Team identifier
|
||||
TenantID string `json:"tenant_id,omitempty"` // Tenant identifier
|
||||
}
|
||||
|
||||
// JWTClaims represents JWT-specific claims structure
|
||||
|
|
@ -589,6 +600,10 @@ type JWTClaims struct {
|
|||
ClientID string `json:"client_id"` // OAuth client ID
|
||||
Scope string `json:"scope,omitempty"` // Access scope
|
||||
TokenType string `json:"token_type"` // Token type
|
||||
|
||||
// Extended claims for multi-tenancy and team support
|
||||
TeamID string `json:"team_id,omitempty"` // Team identifier
|
||||
TenantID string `json:"tenant_id,omitempty"` // Tenant identifier
|
||||
}
|
||||
|
||||
// ClientConfig represents default client configuration
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue