Merge pull request #1197 from trheyi/main
Add Yao user ID support in OIDC and token generation
This commit is contained in:
commit
f86b72108f
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{
|
teams, err := u.GetTeams(ctx, model.QueryParam{
|
||||||
Select: u.teamFields,
|
Select: u.teamFields,
|
||||||
Wheres: []model.QueryWhere{
|
Wheres: []model.QueryWhere{
|
||||||
{Column: "team_id", Value: teamIDs, Method: "in"},
|
{Column: "team_id", Value: teamIDs, OP: "in"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -567,6 +567,9 @@ func (s *Service) SignIDToken(clientID, scope string, expiresIn int, userdata *t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Yao custom fields with namespace
|
// Add Yao custom fields with namespace
|
||||||
|
if userdata.YaoUserID != "" {
|
||||||
|
claims["yao:user_id"] = userdata.YaoUserID
|
||||||
|
}
|
||||||
if userdata.YaoTenantID != "" {
|
if userdata.YaoTenantID != "" {
|
||||||
claims["yao:tenant_id"] = userdata.YaoTenantID
|
claims["yao:tenant_id"] = userdata.YaoTenantID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,9 @@ func MakeOIDCUserInfo(user map[string]interface{}) *OIDCUserInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yao custom fields with namespace
|
// 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 {
|
if tenantID, ok := user["yao:tenant_id"].(string); ok {
|
||||||
userInfo.YaoTenantID = tenantID
|
userInfo.YaoTenantID = tenantID
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -690,6 +690,7 @@ type OIDCUserInfo struct {
|
||||||
Address *OIDCAddress `json:"address,omitempty"` // Physical mailing address
|
Address *OIDCAddress `json:"address,omitempty"` // Physical mailing address
|
||||||
|
|
||||||
// Additional custom claims with namespace
|
// 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
|
YaoTenantID string `json:"yao:tenant_id,omitempty"` // Yao tenant ID
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ func issueTokens(ctx context.Context, userid string, teamID string, team map[str
|
||||||
// Prepare OIDC user info
|
// Prepare OIDC user info
|
||||||
oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user)
|
oidcUserInfo := oauthtypes.MakeOIDCUserInfo(user)
|
||||||
oidcUserInfo.Sub = subject
|
oidcUserInfo.Sub = subject
|
||||||
|
oidcUserInfo.YaoUserID = userid // Add original user ID
|
||||||
|
|
||||||
// Prepare extra claims for access token
|
// Prepare extra claims for access token
|
||||||
extraClaims := make(map[string]interface{})
|
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)
|
inviter, err := provider.GetUser(ctx, inviterID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
inviterInfo = &InviterInfo{
|
inviterInfo = &InviterInfo{
|
||||||
|
UserID: inviterID,
|
||||||
Name: toString(inviter["name"]),
|
Name: toString(inviter["name"]),
|
||||||
Picture: toString(inviter["picture"]),
|
Picture: toString(inviter["picture"]),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -392,6 +392,7 @@ type PublicInvitationResponse struct {
|
||||||
|
|
||||||
// InviterInfo represents public information about the person who sent the invitation
|
// InviterInfo represents public information about the person who sent the invitation
|
||||||
type InviterInfo struct {
|
type InviterInfo struct {
|
||||||
|
UserID string `json:"user_id"` // Inviter's user ID
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Picture string `json:"picture"` // Always return, empty string if not set
|
Picture string `json:"picture"` // Always return, empty string if not set
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ func attachProfile(group *gin.RouterGroup, oauth types.OAuth) {
|
||||||
profile := group.Group("/profile")
|
profile := group.Group("/profile")
|
||||||
profile.Use(oauth.Guard)
|
profile.Use(oauth.Guard)
|
||||||
|
|
||||||
profile.GET("/", placeholder) // Get user profile
|
profile.GET("/", GinProfileGet) // Get user profile
|
||||||
profile.PUT("/", placeholder) // Update user profile
|
profile.PUT("/", placeholder) // Update user profile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue