Add Yao user ID support in OIDC and token generation
- Enhanced the SignIDToken method to include the original Yao user ID in the token claims, improving user identification. - Updated the MakeOIDCUserInfo function to extract and set the Yao user ID from the user map, ensuring consistency in user information. - Modified the OIDCUserInfo structure to include a field for Yao user ID, facilitating better integration with Yao-specific features. - Adjusted the team invitation response to include the inviter's user ID, enhancing the invitation context.
This commit is contained in:
parent
5e67a9e5c0
commit
0d83faeeca
9 changed files with 165 additions and 3 deletions
|
|
@ -291,7 +291,7 @@ func (u *DefaultUser) GetTeamsByMember(ctx context.Context, memberID string) ([]
|
|||
teams, err := u.GetTeams(ctx, model.QueryParam{
|
||||
Select: u.teamFields,
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "team_id", Value: teamIDs, Method: "in"},
|
||||
{Column: "team_id", Value: teamIDs, OP: "in"},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -567,6 +567,9 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t
|
|||
}
|
||||
|
||||
// Add Yao custom fields with namespace
|
||||
if userdata.YaoUserID != "" {
|
||||
claims["yao:user_id"] = userdata.YaoUserID
|
||||
}
|
||||
if userdata.YaoTenantID != "" {
|
||||
claims["yao:tenant_id"] = userdata.YaoTenantID
|
||||
}
|
||||
|
|
|
|||
|
|
@ -265,6 +265,9 @@ func MakeOIDCUserInfo(user map[string]interface{}) *OIDCUserInfo {
|
|||
}
|
||||
|
||||
// Yao custom fields with namespace
|
||||
if userID, ok := user["yao:user_id"].(string); ok {
|
||||
userInfo.YaoUserID = userID
|
||||
}
|
||||
if tenantID, ok := user["yao:tenant_id"].(string); ok {
|
||||
userInfo.YaoTenantID = tenantID
|
||||
}
|
||||
|
|
|
|||
|
|
@ -690,6 +690,7 @@ type OIDCUserInfo struct {
|
|||
Address *OIDCAddress `json:"address,omitempty"` // Physical mailing address
|
||||
|
||||
// Additional custom claims with namespace
|
||||
YaoUserID string `json:"yao:user_id,omitempty"` // Yao user ID (original user ID)
|
||||
YaoTenantID string `json:"yao:tenant_id,omitempty"` // Yao tenant ID
|
||||
YaoTeamID string `json:"yao:team_id,omitempty"` // Yao team ID
|
||||
YaoTeam *OIDCTeamInfo `json:"yao:team,omitempty"` // Yao team info
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ func issueTokens(ctx context.Context, userid string, teamID string, team map[str
|
|||
// Prepare OIDC user info
|
||||
oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user)
|
||||
oidcUserInfo.Sub = subject
|
||||
oidcUserInfo.YaoUserID = userid // Add original user ID
|
||||
|
||||
// Prepare extra claims for access token
|
||||
extraClaims := make(map[string]interface{})
|
||||
|
|
|
|||
152
openapi/user/profile.go
Normal file
152
openapi/user/profile.go
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/openapi/oauth"
|
||||
oauthtypes "github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/openapi/response"
|
||||
)
|
||||
|
||||
// User Profile Management Handlers
|
||||
|
||||
// GinProfileGet handles GET /profile - Get current user profile
|
||||
func GinProfileGet(c *gin.Context) {
|
||||
// Get authorized user info
|
||||
authInfo := oauth.GetAuthorizedInfo(c)
|
||||
if authInfo == nil || authInfo.UserID == "" {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrInvalidClient.Code,
|
||||
ErrorDescription: "User not authenticated",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusUnauthorized, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get user provider
|
||||
userProvider, err := oauth.OAuth.GetUserProvider()
|
||||
if err != nil {
|
||||
log.Error("Failed to get user provider: %v", err)
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrServerError.Code,
|
||||
ErrorDescription: "Failed to get user provider",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get user data with scopes
|
||||
ctx := c.Request.Context()
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
user, err := userProvider.GetUserWithScopes(ctx, authInfo.UserID)
|
||||
if err != nil {
|
||||
log.Error("Failed to get user profile: %v", err)
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrServerError.Code,
|
||||
ErrorDescription: "Failed to retrieve user profile",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get Yao client config
|
||||
yaoClientConfig := GetYaoClientConfig()
|
||||
|
||||
// Get or create subject
|
||||
subject, err := oauth.OAuth.Subject(yaoClientConfig.ClientID, authInfo.UserID)
|
||||
if err != nil {
|
||||
log.Warn("Failed to get user subject: %s", err.Error())
|
||||
subject = authInfo.UserID // Fallback to user ID
|
||||
}
|
||||
|
||||
// Prepare OIDC user info (same format as login response)
|
||||
oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user)
|
||||
oidcUserInfo.Sub = subject
|
||||
oidcUserInfo.YaoUserID = authInfo.UserID
|
||||
|
||||
// Add team context if available from token
|
||||
if authInfo.TeamID != "" {
|
||||
// Get team details
|
||||
team, err := userProvider.GetTeamByMember(ctx, authInfo.TeamID, authInfo.UserID)
|
||||
if err == nil && team != nil {
|
||||
// Add team info to OIDC user info
|
||||
oidcUserInfo.YaoTeamID = authInfo.TeamID
|
||||
|
||||
teamInfo := &oauthtypes.OIDCTeamInfo{}
|
||||
if teamIDVal := toString(team["team_id"]); teamIDVal != "" {
|
||||
teamInfo.TeamID = teamIDVal
|
||||
}
|
||||
if logo := toString(team["logo"]); logo != "" {
|
||||
teamInfo.Logo = logo
|
||||
}
|
||||
if name := toString(team["name"]); name != "" {
|
||||
teamInfo.Name = name
|
||||
}
|
||||
if description := toString(team["description"]); description != "" {
|
||||
teamInfo.Description = description
|
||||
}
|
||||
if ownerID := toString(team["owner_id"]); ownerID != "" {
|
||||
teamInfo.OwnerID = ownerID
|
||||
|
||||
// Check if user is owner
|
||||
if ownerID == authInfo.UserID {
|
||||
isOwner := true
|
||||
oidcUserInfo.YaoIsOwner = &isOwner
|
||||
}
|
||||
}
|
||||
oidcUserInfo.YaoTeam = teamInfo
|
||||
|
||||
// Add tenant_id if available from the team
|
||||
if tenantID := toString(team["tenant_id"]); tenantID != "" {
|
||||
oidcUserInfo.YaoTenantID = tenantID
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add type information
|
||||
var typeID string
|
||||
if authInfo.TeamID != "" {
|
||||
// Team context - try to get team's type first
|
||||
team, err := userProvider.GetTeamByMember(ctx, authInfo.TeamID, authInfo.UserID)
|
||||
if err == nil && team != nil {
|
||||
typeID = toString(team["type_id"])
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to user's type if no team type
|
||||
if typeID == "" {
|
||||
typeID = toString(user["type_id"])
|
||||
}
|
||||
|
||||
if typeID != "" {
|
||||
oidcUserInfo.YaoTypeID = typeID
|
||||
|
||||
// Get type details
|
||||
typeInfo, err := userProvider.GetType(ctx, typeID)
|
||||
if err == nil && typeInfo != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to map for response
|
||||
profileData := oidcUserInfo.Map()
|
||||
|
||||
// Return user profile
|
||||
response.RespondWithSuccess(c, http.StatusOK, profileData)
|
||||
}
|
||||
|
|
@ -817,6 +817,7 @@ func teamInvitationGetPublic(ctx context.Context, invitationID, locale string) (
|
|||
inviter, err := provider.GetUser(ctx, inviterID)
|
||||
if err == nil {
|
||||
inviterInfo = &InviterInfo{
|
||||
UserID: inviterID,
|
||||
Name: toString(inviter["name"]),
|
||||
Picture: toString(inviter["picture"]),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -392,6 +392,7 @@ type PublicInvitationResponse struct {
|
|||
|
||||
// InviterInfo represents public information about the person who sent the invitation
|
||||
type InviterInfo struct {
|
||||
UserID string `json:"user_id"` // Inviter's user ID
|
||||
Name string `json:"name,omitempty"`
|
||||
Picture string `json:"picture"` // Always return, empty string if not set
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,8 +189,8 @@ func attachProfile(group *gin.RouterGroup, oauth types.OAuth) {
|
|||
profile := group.Group("/profile")
|
||||
profile.Use(oauth.Guard)
|
||||
|
||||
profile.GET("/", placeholder) // Get user profile
|
||||
profile.PUT("/", placeholder) // Update user profile
|
||||
profile.GET("/", GinProfileGet) // Get user profile
|
||||
profile.PUT("/", placeholder) // Update user profile
|
||||
}
|
||||
|
||||
// User management (CRUD)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue