diff --git a/openapi/user/config.go b/openapi/user/config.go index d0bf1dfb..05c502d3 100644 --- a/openapi/user/config.go +++ b/openapi/user/config.go @@ -33,6 +33,8 @@ var ( defaultConfig *Config // Team configurations by locale teamConfigs = make(map[string]*TeamConfig) + // Register configurations by locale + registerConfigs = make(map[string]*RegisterConfig) // Mutex for thread safety configMutex sync.RWMutex ) @@ -48,6 +50,7 @@ func Load(appConfig config.Config) error { providers = make(map[string]*Provider) defaultConfig = nil teamConfigs = make(map[string]*TeamConfig) + registerConfigs = make(map[string]*RegisterConfig) // Load signin configurations from openapi/user/signin directory err := loadSigninConfigs(appConfig.Root) @@ -55,6 +58,12 @@ func Load(appConfig config.Config) error { return fmt.Errorf("failed to load signin configs: %v", err) } + // Load register configurations from openapi/user/register directory + err = loadRegisterConfigs(appConfig.Root) + if err != nil { + return fmt.Errorf("failed to load register configs: %v", err) + } + // Load team configurations from openapi/user/team directory err = loadTeamConfigs(appConfig.Root) if err != nil { @@ -357,6 +366,49 @@ func loadTeamConfigs(_ string) error { return nil } +// loadRegisterConfigs loads all register configurations from the openapi/user/register directory +func loadRegisterConfigs(_ string) error { + // Use Walk to find all configuration files in the register directory + err := application.App.Walk("openapi/user/register", func(root, filename string, isdir bool) error { + if isdir { + return nil + } + + // Only process .yao files + if !strings.HasSuffix(filename, ".yao") { + return nil + } + + // Extract locale from filename (basename without extension) + baseName := filepath.Base(filename) + locale := strings.ToLower(strings.TrimSuffix(baseName, ".yao")) + + // Read configuration + configRaw, err := application.App.Read(filename) + if err != nil { + return fmt.Errorf("failed to read register config %s: %v", filename, err) + } + + // Parse the configuration + var config RegisterConfig + err = application.Parse(filename, configRaw, &config) + if err != nil { + return fmt.Errorf("failed to parse register config %s: %v", filename, err) + } + + // Store register configuration + registerConfigs[locale] = &config + + return nil + }) + + if err != nil { + return fmt.Errorf("failed to walk register directory: %v", err) + } + + return nil +} + // GetPublicConfig returns the public configuration for a given locale func GetPublicConfig(locale string) *Config { configMutex.RLock() @@ -440,6 +492,34 @@ func GetTeamConfig(locale string) *TeamConfig { return nil } +// GetRegisterConfig returns the register configuration for a given locale +func GetRegisterConfig(locale string) *RegisterConfig { + configMutex.RLock() + defer configMutex.RUnlock() + + // Normalize language code to lowercase + if locale != "" { + locale = strings.TrimSpace(strings.ToLower(locale)) + } + + // Try to get the specific locale configuration + if config, exists := registerConfigs[locale]; exists { + return config + } + + // If no specific locale, try to get "en" as default + if config, exists := registerConfigs["en"]; exists { + return config + } + + // If "en" is not available, try to get any available configuration + for _, config := range registerConfigs { + return config + } + + return nil +} + // extractEnvVarName extracts the environment variable name from a string like "$ENV.VAR_NAME" func extractEnvVarName(value string) string { if value == "" { diff --git a/openapi/user/login.go b/openapi/user/login.go index 3eee2623..39a250ca 100644 --- a/openapi/user/login.go +++ b/openapi/user/login.go @@ -79,7 +79,7 @@ func getCaptcha(c *gin.Context) { } // LoginThirdParty is the handler for third party login -func LoginThirdParty(providerID string, userinfo *oauthtypes.OIDCUserInfo, loginCtx *LoginContext) (*LoginResponse, error) { +func LoginThirdParty(providerID string, userinfo *oauthtypes.OIDCUserInfo, loginCtx *LoginContext, locale string) (*LoginResponse, error) { // Get provider provider, err := GetProvider(providerID) @@ -87,6 +87,17 @@ func LoginThirdParty(providerID string, userinfo *oauthtypes.OIDCUserInfo, login return nil, err } + // Get register configuration for role and type + registerConfig := GetRegisterConfig(locale) + if registerConfig == nil { + // If no register config found, try to get default register config + log.Warn("Register configuration not found for locale '%s', trying default locale 'en'", locale) + registerConfig = GetRegisterConfig("en") + if registerConfig == nil { + return nil, fmt.Errorf("register configuration not found. Please create register config files in openapi/user/register/") + } + } + // Check if user exists userProvider, err := oauth.OAuth.GetUserProvider() if err != nil { @@ -109,8 +120,8 @@ func LoginThirdParty(providerID string, userinfo *oauthtypes.OIDCUserInfo, login "given_name": userinfo.GivenName, "family_name": userinfo.FamilyName, "picture": userinfo.Picture, - "role_id": provider.Register.Role, - "type_id": provider.Register.Type, + "role_id": registerConfig.Role, + "type_id": registerConfig.Type, "status": "active", } diff --git a/openapi/user/oauth.go b/openapi/user/oauth.go index a67dafc2..2f37df43 100644 --- a/openapi/user/oauth.go +++ b/openapi/user/oauth.go @@ -174,7 +174,14 @@ func authback(c *gin.Context) { // LoginThirdParty(providerID, userInfo) loginCtx := makeLoginContext(c) - loginResponse, err := LoginThirdParty(providerID, userInfo, loginCtx) + + // Use locale from params, fallback to "en" if not provided + locale := params.Locale + if locale == "" { + locale = "en" + } + + loginResponse, err := LoginThirdParty(providerID, userInfo, loginCtx, locale) if err != nil { errorResp := &response.ErrorResponse{ Code: response.ErrInvalidRequest.Code, diff --git a/openapi/user/types.go b/openapi/user/types.go index b4a3ad03..4f41fd8a 100644 --- a/openapi/user/types.go +++ b/openapi/user/types.go @@ -77,11 +77,23 @@ type ThirdParty struct { Providers []*Provider `json:"providers,omitempty"` } -// RegisterConfig represents the auto register configuration +// ProviderRegisterConfig represents the auto register configuration in provider +type ProviderRegisterConfig struct { + Auto bool `json:"auto,omitempty"` +} + +// RegisterConfig represents the register configuration type RegisterConfig struct { - Auto bool `json:"auto,omitempty"` - Role string `json:"role,omitempty"` - Type string `json:"type,omitempty"` // User type id + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Default bool `json:"default,omitempty"` + SuccessURL string `json:"success_url,omitempty"` + FailureURL string `json:"failure_url,omitempty"` + AutoLogin bool `json:"auto_login,omitempty"` + Role string `json:"role,omitempty"` + Type string `json:"type,omitempty"` // User type id + Form *FormConfig `json:"form,omitempty"` + ConfirmPassword *PasswordConfig `json:"confirm_password,omitempty"` } // YaoClientConfig represents the Yao OpenAPI Client config @@ -95,21 +107,21 @@ type YaoClientConfig struct { // Provider represents a third party login provider type Provider struct { - ID string `json:"id,omitempty"` - Label string `json:"label,omitempty"` - Title string `json:"title,omitempty"` - Logo string `json:"logo,omitempty"` - Color string `json:"color,omitempty"` - TextColor string `json:"text_color,omitempty"` - ClientID string `json:"client_id,omitempty"` - ClientSecret string `json:"client_secret,omitempty"` - ClientSecretGenerator *SecretGenerator `json:"client_secret_generator,omitempty"` - Scopes []string `json:"scopes,omitempty"` - ResponseMode string `json:"response_mode,omitempty"` - UserInfoSource string `json:"user_info_source,omitempty"` // "endpoint" (default) | "id_token" | "access_token" - Endpoints *Endpoints `json:"endpoints,omitempty"` - Mapping interface{} `json:"mapping,omitempty"` // string (preset) | map[string]string (custom) | nil (generic) - Register *RegisterConfig `json:"register,omitempty"` + ID string `json:"id,omitempty"` + Label string `json:"label,omitempty"` + Title string `json:"title,omitempty"` + Logo string `json:"logo,omitempty"` + Color string `json:"color,omitempty"` + TextColor string `json:"text_color,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + ClientSecretGenerator *SecretGenerator `json:"client_secret_generator,omitempty"` + Scopes []string `json:"scopes,omitempty"` + ResponseMode string `json:"response_mode,omitempty"` + UserInfoSource string `json:"user_info_source,omitempty"` // "endpoint" (default) | "id_token" | "access_token" + Endpoints *Endpoints `json:"endpoints,omitempty"` + Mapping interface{} `json:"mapping,omitempty"` // string (preset) | map[string]string (custom) | nil (generic) + Register *ProviderRegisterConfig `json:"register,omitempty"` } // SecretGenerator represents the client secret generator configuration