Add Yao type information to user and token claims

- Enhanced the OIDCUserInfo structure to include YaoTypeID and YaoType fields for better user type management.
- Updated SignIDToken method to incorporate YaoTypeID and YaoType claims, ensuring comprehensive user type information in ID tokens.
- Modified team creation logic to set default type_id based on team configuration, improving team management capabilities.
- Refactored token generation methods to support additional type information in access and refresh tokens, enhancing overall token customization.
This commit is contained in:
Max 2025-10-13 18:26:52 +08:00
parent a4c628f3c2
commit 5e67a9e5c0
6 changed files with 142 additions and 8 deletions

View file

@ -576,6 +576,9 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t
if userdata.YaoIsOwner != nil { if userdata.YaoIsOwner != nil {
claims["yao:is_owner"] = *userdata.YaoIsOwner claims["yao:is_owner"] = *userdata.YaoIsOwner
} }
if userdata.YaoTypeID != "" {
claims["yao:type_id"] = userdata.YaoTypeID
}
// Add Yao team info if present // Add Yao team info if present
if userdata.YaoTeam != nil { if userdata.YaoTeam != nil {
teamMap := make(map[string]interface{}) teamMap := make(map[string]interface{})
@ -601,6 +604,22 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t
claims["yao:team"] = teamMap claims["yao:team"] = teamMap
} }
} }
// Add Yao type info if present
if userdata.YaoType != nil {
typeMap := make(map[string]interface{})
if userdata.YaoType.TypeID != "" {
typeMap["type_id"] = userdata.YaoType.TypeID
}
if userdata.YaoType.Name != "" {
typeMap["name"] = userdata.YaoType.Name
}
if userdata.YaoType.Locale != "" {
typeMap["locale"] = userdata.YaoType.Locale
}
if len(typeMap) > 0 {
claims["yao:type"] = typeMap
}
}
// Add scope if provided (useful for determining which claims to include) // Add scope if provided (useful for determining which claims to include)
if scope != "" { if scope != "" {

View file

@ -107,6 +107,9 @@ func (user OIDCUserInfo) Map() map[string]interface{} {
if user.YaoIsOwner != nil { if user.YaoIsOwner != nil {
result["yao:is_owner"] = user.YaoIsOwner result["yao:is_owner"] = user.YaoIsOwner
} }
if user.YaoTypeID != "" {
result["yao:type_id"] = user.YaoTypeID
}
// Add Yao team info if present and has content // Add Yao team info if present and has content
if user.YaoTeam != nil { if user.YaoTeam != nil {
@ -134,6 +137,23 @@ func (user OIDCUserInfo) Map() map[string]interface{} {
} }
} }
// Add Yao type info if present and has content
if user.YaoType != nil {
typeMap := make(map[string]interface{})
if user.YaoType.TypeID != "" {
typeMap["type_id"] = user.YaoType.TypeID
}
if user.YaoType.Name != "" {
typeMap["name"] = user.YaoType.Name
}
if user.YaoType.Locale != "" {
typeMap["locale"] = user.YaoType.Locale
}
if len(typeMap) > 0 {
result["yao:type"] = typeMap
}
}
// Include raw data if available // Include raw data if available
// if user.Raw != nil { // if user.Raw != nil {
// // Merge raw data, but let structured fields take precedence // // Merge raw data, but let structured fields take precedence
@ -254,6 +274,9 @@ func MakeOIDCUserInfo(user map[string]interface{}) *OIDCUserInfo {
if isOwner, ok := user["yao:is_owner"].(bool); ok { if isOwner, ok := user["yao:is_owner"].(bool); ok {
userInfo.YaoIsOwner = &isOwner userInfo.YaoIsOwner = &isOwner
} }
if typeID, ok := user["yao:type_id"].(string); ok {
userInfo.YaoTypeID = typeID
}
// Yao team info (nested object) // Yao team info (nested object)
if teamData, ok := user["yao:team"].(map[string]interface{}); ok { if teamData, ok := user["yao:team"].(map[string]interface{}); ok {
@ -283,6 +306,21 @@ func MakeOIDCUserInfo(user map[string]interface{}) *OIDCUserInfo {
userInfo.YaoTeam = team userInfo.YaoTeam = team
} }
// Yao type info (nested object)
if typeData, ok := user["yao:type"].(map[string]interface{}); ok {
typeInfo := &OIDCTypeInfo{}
if typeID, ok := typeData["type_id"].(string); ok {
typeInfo.TypeID = typeID
}
if name, ok := typeData["name"].(string); ok {
typeInfo.Name = name
}
if locale, ok := typeData["locale"].(string); ok {
typeInfo.Locale = locale
}
userInfo.YaoType = typeInfo
}
return userInfo return userInfo
} }

View file

@ -694,6 +694,8 @@ type OIDCUserInfo struct {
YaoTeamID string `json:"yao:team_id,omitempty"` // Yao team ID YaoTeamID string `json:"yao:team_id,omitempty"` // Yao team ID
YaoTeam *OIDCTeamInfo `json:"yao:team,omitempty"` // Yao team info YaoTeam *OIDCTeamInfo `json:"yao:team,omitempty"` // Yao team info
YaoIsOwner *bool `json:"yao:is_owner,omitempty"` // Yao is owner YaoIsOwner *bool `json:"yao:is_owner,omitempty"` // Yao is owner
YaoTypeID string `json:"yao:type_id,omitempty"` // Yao user type ID
YaoType *OIDCTypeInfo `json:"yao:type,omitempty"` // Yao user type info
// Raw response for debugging and custom processing // Raw response for debugging and custom processing
Raw map[string]interface{} `json:"raw,omitempty"` // Original provider response Raw map[string]interface{} `json:"raw,omitempty"` // Original provider response
@ -709,6 +711,13 @@ type OIDCTeamInfo struct {
UpdatedAt *int64 `json:"updated_at,omitempty"` // Team updated at (seconds since epoch) UpdatedAt *int64 `json:"updated_at,omitempty"` // Team updated at (seconds since epoch)
} }
// OIDCTypeInfo represents user type information based on OIDC standard
type OIDCTypeInfo struct {
TypeID string `json:"type_id,omitempty"` // User type identifier
Name string `json:"name,omitempty"` // User type name
Locale string `json:"locale,omitempty"` // User type locale
}
// OIDCAddress represents the OIDC address claim structure // OIDCAddress represents the OIDC address claim structure
type OIDCAddress struct { type OIDCAddress struct {
Formatted string `json:"formatted,omitempty"` // Full mailing address Formatted string `json:"formatted,omitempty"` // Full mailing address

View file

@ -300,12 +300,12 @@ func issueTokens(ctx context.Context, userid string, teamID string, team map[str
oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user) oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user)
oidcUserInfo.Sub = subject oidcUserInfo.Sub = subject
// Prepare extra claims for team context // Prepare extra claims for access token
var extraClaims map[string]interface{} extraClaims := make(map[string]interface{})
// Add team context if available
if teamID != "" && team != nil { if teamID != "" && team != nil {
extraClaims = map[string]interface{}{ extraClaims["team_id"] = teamID
"team_id": teamID,
}
// Add tenant_id if available from the team // Add tenant_id if available from the team
if tenantID := toString(team["tenant_id"]); tenantID != "" { if tenantID := toString(team["tenant_id"]); tenantID != "" {
@ -344,10 +344,46 @@ func issueTokens(ctx context.Context, userid string, teamID string, team map[str
oidcUserInfo.YaoTeam = teamInfo oidcUserInfo.YaoTeam = teamInfo
} }
// Add type information (use team type if in team context, otherwise use user type)
var typeID string
if teamID != "" && team != nil {
// Team context - use team's type
typeID = toString(team["type_id"])
} else {
// Personal context - use user's type
typeID = toString(user["type_id"])
}
if typeID != "" {
// Add type_id to extra claims for access token
extraClaims["type_id"] = typeID
oidcUserInfo.YaoTypeID = typeID
// Get type details
userProvider, err := oauth.OAuth.GetUserProvider()
if err == nil {
typeInfo, err := userProvider.GetType(ctx, typeID)
if err == nil && typeInfo != nil {
// Add type info to OIDC user info
typeDetails := &oauthtypes.OIDCTypeInfo{}
if typeIDVal := toString(typeInfo["type_id"]); typeIDVal != "" {
typeDetails.TypeID = typeIDVal
}
if name := toString(typeInfo["name"]); name != "" {
typeDetails.Name = name
}
if locale := toString(typeInfo["locale"]); locale != "" {
typeDetails.Locale = locale
}
oidcUserInfo.YaoType = typeDetails
}
}
}
// Sign OIDC Token // Sign OIDC Token
var oidcToken string var oidcToken string
var err error var err error
if extraClaims != nil { if len(extraClaims) > 0 {
oidcToken, err = oauth.OAuth.SignIDToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), yaoClientConfig.ExpiresIn, oidcUserInfo, extraClaims) oidcToken, err = oauth.OAuth.SignIDToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), yaoClientConfig.ExpiresIn, oidcUserInfo, extraClaims)
} else { } else {
oidcToken, err = oauth.OAuth.SignIDToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), yaoClientConfig.ExpiresIn, oidcUserInfo) oidcToken, err = oauth.OAuth.SignIDToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), yaoClientConfig.ExpiresIn, oidcUserInfo)
@ -358,7 +394,7 @@ func issueTokens(ctx context.Context, userid string, teamID string, team map[str
// Sign Access Token // Sign Access Token
var accessToken string var accessToken string
if extraClaims != nil { if len(extraClaims) > 0 {
accessToken, err = oauth.OAuth.MakeAccessToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.ExpiresIn, extraClaims) accessToken, err = oauth.OAuth.MakeAccessToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.ExpiresIn, extraClaims)
} else { } else {
accessToken, err = oauth.OAuth.MakeAccessToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.ExpiresIn) accessToken, err = oauth.OAuth.MakeAccessToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.ExpiresIn)
@ -369,7 +405,7 @@ func issueTokens(ctx context.Context, userid string, teamID string, team map[str
// Sign Refresh Token // Sign Refresh Token
var refreshToken string var refreshToken string
if extraClaims != nil { if len(extraClaims) > 0 {
refreshToken, err = oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn, extraClaims) refreshToken, err = oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn, extraClaims)
} else { } else {
refreshToken, err = oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn) refreshToken, err = oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn)

View file

@ -689,6 +689,37 @@ func teamCreate(ctx context.Context, userID string, teamData maps.MapStrAny) (st
teamData["created_at"] = time.Now() teamData["created_at"] = time.Now()
teamData["updated_at"] = time.Now() teamData["updated_at"] = time.Now()
// Set default type_id from team config if not provided
if _, hasType := teamData["type_id"]; !hasType {
// Try to get locale from team data
locale := ""
if localeVal, ok := teamData["locale"].(string); ok && localeVal != "" {
locale = strings.TrimSpace(strings.ToLower(localeVal))
}
// Fallback: try common locale variations or use "en" as final fallback
// This ensures we always get a valid config even if locale is invalid
teamConfig := GetTeamConfig(locale)
if teamConfig == nil {
// Try fallback locales in order
fallbackLocales := []string{"en", "zh-cn"}
for _, fallback := range fallbackLocales {
teamConfig = GetTeamConfig(fallback)
if teamConfig != nil {
break
}
}
}
// Apply default type from config if available
if teamConfig != nil && teamConfig.Type != "" {
teamData["type_id"] = teamConfig.Type
}
}
// Clean up: remove locale from team data as it's not stored in database
delete(teamData, "locale")
// Create team // Create team
teamID, err := provider.CreateTeam(ctx, teamData) teamID, err := provider.CreateTeam(ctx, teamData)
if err != nil { if err != nil {

View file

@ -415,6 +415,7 @@ type CreateInvitationRequest struct {
type TeamConfig struct { type TeamConfig struct {
Roles []*TeamRole `json:"roles,omitempty"` Roles []*TeamRole `json:"roles,omitempty"`
Invite *InviteConfig `json:"invite,omitempty"` Invite *InviteConfig `json:"invite,omitempty"`
Type string `json:"type,omitempty"` // Default type for new teams
} }
// TeamRole represents a team role configuration // TeamRole represents a team role configuration