From b43a38b38772203a8ad2558372fe9f4448a0116f Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 15 Oct 2025 09:58:37 +0800 Subject: [PATCH] Refactor user configuration to unify login and registration handling - Replaced separate register configurations with a unified entry configuration that combines login and registration settings. - Updated related functions and structures to support the new entry configuration, enhancing the user authentication process. - Removed deprecated register configuration handling and endpoints, streamlining the codebase. - Improved error handling for missing entry configurations, ensuring better user experience during authentication. --- openapi/user/config.go | 194 +++++++++++++++++++++------------------ openapi/user/entry.go | 62 +++++++++++++ openapi/user/login.go | 20 ++-- openapi/user/register.go | 41 --------- openapi/user/types.go | 21 +++-- openapi/user/user.go | 4 +- 6 files changed, 192 insertions(+), 150 deletions(-) create mode 100644 openapi/user/entry.go delete mode 100644 openapi/user/register.go diff --git a/openapi/user/config.go b/openapi/user/config.go index 00a5e72b..61082624 100644 --- a/openapi/user/config.go +++ b/openapi/user/config.go @@ -33,8 +33,8 @@ var ( defaultConfig *Config // Team configurations by locale teamConfigs = make(map[string]*TeamConfig) - // Register configurations by locale - registerConfigs = make(map[string]*RegisterConfig) + // Entry configurations by locale (unified login + register) + entryConfigs = make(map[string]*EntryConfig) // Mutex for thread safety configMutex sync.RWMutex ) @@ -50,7 +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) + entryConfigs = make(map[string]*EntryConfig) // Load signin configurations from openapi/user/signin directory err := loadSigninConfigs(appConfig.Root) @@ -58,10 +58,10 @@ 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) + // Load entry configurations from openapi/user/entry directory + err = loadEntryConfigs(appConfig.Root) if err != nil { - return fmt.Errorf("failed to load register configs: %v", err) + return fmt.Errorf("failed to load entry configs: %v", err) } // Load team configurations from openapi/user/team directory @@ -366,57 +366,6 @@ 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) - } - - // Process ENV variables in the configuration - processRegisterConfigENVVariables(&config) - - // Copy third_party from signin config if available - if signinConfig, exists := fullConfigs[locale]; exists && signinConfig.ThirdParty != nil { - config.ThirdParty = signinConfig.ThirdParty - } - - // 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() @@ -500,34 +449,6 @@ 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 == "" { @@ -680,13 +601,108 @@ func processConfigENVVariables(config *Config) { } } -// processRegisterConfigENVVariables processes environment variables in the register configuration -func processRegisterConfigENVVariables(config *RegisterConfig) { +// loadEntryConfigs loads all entry configurations from the openapi/user/entry directory +// Entry config merges signin and register configurations +func loadEntryConfigs(_ string) error { + // Use Walk to find all configuration files in the entry directory + err := application.App.Walk("openapi/user/entry", 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 entry config %s: %v", filename, err) + } + + // Parse the configuration + var config EntryConfig + err = application.Parse(filename, configRaw, &config) + if err != nil { + return fmt.Errorf("failed to parse entry config %s: %v", filename, err) + } + + // Process ENV variables in the configuration + processEntryConfigENVVariables(&config) + + // Store entry configuration + entryConfigs[locale] = &config + + return nil + }) + + if err != nil { + return fmt.Errorf("failed to walk entry directory: %v", err) + } + + return nil +} + +// processEntryConfigENVVariables processes environment variables in the entry configuration +func processEntryConfigENVVariables(config *EntryConfig) { + var missingEnvVars []string + + // Process client_id and client_secret (from signin config) + if strings.HasPrefix(config.ClientID, "$ENV.") { + envVar := strings.TrimPrefix(config.ClientID, "$ENV.") + if _, exists := os.LookupEnv(envVar); !exists { + missingEnvVars = append(missingEnvVars, envVar) + } + } + config.ClientID = replaceENVVar(config.ClientID) + + if strings.HasPrefix(config.ClientSecret, "$ENV.") { + envVar := strings.TrimPrefix(config.ClientSecret, "$ENV.") + if _, exists := os.LookupEnv(envVar); !exists { + missingEnvVars = append(missingEnvVars, envVar) + } + } + config.ClientSecret = replaceENVVar(config.ClientSecret) + // Process form configuration - missingEnvVars := processFormConfigENVVariables(config.Form) + formMissingVars := processFormConfigENVVariables(config.Form) + missingEnvVars = append(missingEnvVars, formMissingVars...) // Log warning for missing environment variables if len(missingEnvVars) > 0 { - fmt.Printf("Warning: The following environment variables are not set in register configuration: %v\n", missingEnvVars) + fmt.Printf("Warning: The following environment variables are not set in entry configuration: %v\n", missingEnvVars) } } + +// GetEntryConfig returns the entry configuration for a given locale +func GetEntryConfig(locale string) *EntryConfig { + 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 := entryConfigs[locale]; exists { + return config + } + + // If no specific locale, try to get "en" as default + if config, exists := entryConfigs["en"]; exists { + return config + } + + // If "en" is not available, try to get any available configuration + for _, config := range entryConfigs { + return config + } + + return nil +} diff --git a/openapi/user/entry.go b/openapi/user/entry.go new file mode 100644 index 00000000..ea8ab02c --- /dev/null +++ b/openapi/user/entry.go @@ -0,0 +1,62 @@ +package user + +import ( + "github.com/gin-gonic/gin" + "github.com/yaoapp/yao/openapi/response" + "github.com/yaoapp/yao/openapi/utils" +) + +// getEntryConfig is the handler for get unified auth entry configuration +func getEntryConfig(c *gin.Context) { + // Get locale from query parameter (optional) + locale := c.Query("locale") + + // Get entry configuration for the specified locale + config := GetEntryConfig(locale) + + // Set session id if not exists + sid := utils.GetSessionID(c) + if sid == "" { + sid = generateSessionID() + response.SendSessionCookie(c, sid) + } + + // If no configuration found, return error + if config == nil { + errorResp := &response.ErrorResponse{ + Code: response.ErrInvalidRequest.Code, + ErrorDescription: "No entry configuration found for the requested locale", + } + response.RespondWithError(c, response.StatusNotFound, errorResp) + return + } + + // Create public config without sensitive data + publicConfig := *config + publicConfig.ClientSecret = "" // Remove sensitive data + + // Remove captcha secret from public config + if publicConfig.Form != nil && publicConfig.Form.Captcha != nil && publicConfig.Form.Captcha.Options != nil { + // Create a copy of captcha options without the secret + captchaOptions := make(map[string]interface{}) + for k, v := range publicConfig.Form.Captcha.Options { + if k != "secret" { + captchaOptions[k] = v + } + } + publicConfig.Form.Captcha.Options = captchaOptions + } + + // Return the entry configuration + response.RespondWithSuccess(c, response.StatusOK, publicConfig) +} + +// entry is the handler for unified auth entry (login/register) +// The backend determines whether this is a login or registration based on email existence +func entry(c *gin.Context) { + // This is a placeholder - you may need to implement the actual login/register logic here + // The logic should: + // 1. Check if the email exists in the database + // 2. If exists: proceed with login flow + // 3. If not exists: proceed with registration flow +} diff --git a/openapi/user/login.go b/openapi/user/login.go index 6339151d..05f8d4e2 100644 --- a/openapi/user/login.go +++ b/openapi/user/login.go @@ -87,14 +87,14 @@ 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/") + // Get entry configuration for role and type + entryConfig := GetEntryConfig(locale) + if entryConfig == nil { + // If no entry config found, try to get default entry config + log.Warn("Entry configuration not found for locale '%s', trying default locale 'en'", locale) + entryConfig = GetEntryConfig("en") + if entryConfig == nil { + return nil, fmt.Errorf("entry configuration not found. Please create entry config files in openapi/user/entry/") } } @@ -120,8 +120,8 @@ func LoginThirdParty(providerID string, userinfo *oauthtypes.OIDCUserInfo, login "given_name": userinfo.GivenName, "family_name": userinfo.FamilyName, "picture": userinfo.Picture, - "role_id": registerConfig.Role, - "type_id": registerConfig.Type, + "role_id": entryConfig.Role, + "type_id": entryConfig.Type, "status": "active", } diff --git a/openapi/user/register.go b/openapi/user/register.go deleted file mode 100644 index 285aab91..00000000 --- a/openapi/user/register.go +++ /dev/null @@ -1,41 +0,0 @@ -package user - -import ( - "github.com/gin-gonic/gin" - "github.com/yaoapp/yao/openapi/response" - "github.com/yaoapp/yao/openapi/utils" -) - -// getRegisterConfig is the handler for get register configuration -func getRegisterConfig(c *gin.Context) { - // Get locale from query parameter (optional) - locale := c.Query("locale") - - // Get register configuration for the specified locale (already includes third_party from signin config) - config := GetRegisterConfig(locale) - - // Set session id if not exists - sid := utils.GetSessionID(c) - if sid == "" { - sid = generateSessionID() - response.SendSessionCookie(c, sid) - } - - // If no configuration found, return error - if config == nil { - errorResp := &response.ErrorResponse{ - Code: response.ErrInvalidRequest.Code, - ErrorDescription: "No register configuration found for the requested locale", - } - response.RespondWithError(c, response.StatusNotFound, errorResp) - return - } - - // Return the register configuration - response.RespondWithSuccess(c, response.StatusOK, config) -} - -// register is the handler for user registration -func register(c *gin.Context) { - // This is a placeholder - you may need to implement the actual registration logic here -} diff --git a/openapi/user/types.go b/openapi/user/types.go index ac17784f..dd679abb 100644 --- a/openapi/user/types.go +++ b/openapi/user/types.go @@ -84,20 +84,25 @@ type ProviderRegisterConfig struct { Auto bool `json:"auto,omitempty"` } -// RegisterConfig represents the register configuration -type RegisterConfig struct { +// EntryConfig represents the unified auth entry configuration (login + register) +// This merges signin and register configurations into a single entry point +type EntryConfig struct { 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 + LogoutRedirect string `json:"logout_redirect,omitempty"` // From signin config + ClientID string `json:"client_id,omitempty"` // From signin config + ClientSecret string `json:"client_secret,omitempty"` // From signin config (not exposed to frontend) + AutoLogin bool `json:"auto_login,omitempty"` // From register config + Role string `json:"role,omitempty"` // From register config + Type string `json:"type,omitempty"` // From register config - User type id Form *FormConfig `json:"form,omitempty"` - Messenger *MessengerConfig `json:"messenger,omitempty"` - InviteRequired bool `json:"invite_required,omitempty"` - ThirdParty *ThirdParty `json:"third_party,omitempty"` // Third party login configuration (copied from signin config) + Token *TokenConfig `json:"token,omitempty"` // From signin config + Messenger *MessengerConfig `json:"messenger,omitempty"` // From register config + InviteRequired bool `json:"invite_required,omitempty"` // From register config + ThirdParty *ThirdParty `json:"third_party,omitempty"` } // MessengerConfig represents the messenger configuration for user registration diff --git a/openapi/user/user.go b/openapi/user/user.go index 914bd9e9..093d1bc6 100644 --- a/openapi/user/user.go +++ b/openapi/user/user.go @@ -31,8 +31,8 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) { group.GET("/login", getLoginConfig) // Get login page config (public) - migrated from /signin group.POST("/login", login) // User login (public) - migrated from /signin group.GET("/login/captcha", getCaptcha) // Get captcha for login (public) - group.GET("/register", getRegisterConfig) // Get register page config (public) - group.POST("/register", register) // User register (public) + group.GET("/entry", getEntryConfig) // Get unified auth entry config (public) + group.POST("/entry", entry) // Unified auth entry (login/register) (public) group.POST("/logout", oauth.Guard, placeholder) // User logout // Logined User Settings