Merge pull request #1196 from trheyi/main

Add Yao type information to user and token claims
This commit is contained in:
Max 2025-10-13 18:27:46 +08:00 committed by GitHub
commit 51f3ed70e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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 {
claims["yao:is_owner"] = *userdata.YaoIsOwner
}
if userdata.YaoTypeID != "" {
claims["yao:type_id"] = userdata.YaoTypeID
}
// Add Yao team info if present
if userdata.YaoTeam != nil {
teamMap := make(map[string]interface{})
@ -601,6 +604,22 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t
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)
if scope != "" {

View file

@ -107,6 +107,9 @@ func (user OIDCUserInfo) Map() map[string]interface{} {
if user.YaoIsOwner != nil {
result["yao:is_owner"] = user.YaoIsOwner
}
if user.YaoTypeID != "" {
result["yao:type_id"] = user.YaoTypeID
}
// Add Yao team info if present and has content
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
// if user.Raw != nil {
// // 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 {
userInfo.YaoIsOwner = &isOwner
}
if typeID, ok := user["yao:type_id"].(string); ok {
userInfo.YaoTypeID = typeID
}
// Yao team info (nested object)
if teamData, ok := user["yao:team"].(map[string]interface{}); ok {
@ -283,6 +306,21 @@ func MakeOIDCUserInfo(user map[string]interface{}) *OIDCUserInfo {
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
}

View file

@ -694,6 +694,8 @@ type OIDCUserInfo struct {
YaoTeamID string `json:"yao:team_id,omitempty"` // Yao team ID
YaoTeam *OIDCTeamInfo `json:"yao:team,omitempty"` // Yao team info
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 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)
}
// 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
type OIDCAddress struct {
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.Sub = subject
// Prepare extra claims for team context
var extraClaims map[string]interface{}
// Prepare extra claims for access token
extraClaims := make(map[string]interface{})
// Add team context if available
if teamID != "" && team != nil {
extraClaims = map[string]interface{}{
"team_id": teamID,
}
extraClaims["team_id"] = teamID
// Add tenant_id if available from the team
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
}
// 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
var oidcToken string
var err error
if extraClaims != nil {
if len(extraClaims) > 0 {
oidcToken, err = oauth.OAuth.SignIDToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), yaoClientConfig.ExpiresIn, oidcUserInfo, extraClaims)
} else {
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
var accessToken string
if extraClaims != nil {
if len(extraClaims) > 0 {
accessToken, err = oauth.OAuth.MakeAccessToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.ExpiresIn, extraClaims)
} else {
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
var refreshToken string
if extraClaims != nil {
if len(extraClaims) > 0 {
refreshToken, err = oauth.OAuth.MakeRefreshToken(yaoClientConfig.ClientID, strings.Join(scopes, " "), subject, yaoClientConfig.RefreshTokenExpiresIn, extraClaims)
} else {
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["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
teamID, err := provider.CreateTeam(ctx, teamData)
if err != nil {

View file

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