Merge pull request #1201 from trheyi/main

Refactor user configuration to unify login and registration handling
This commit is contained in:
Max 2025-10-15 09:59:09 +08:00 committed by GitHub
commit e690ac95ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 192 additions and 150 deletions

View file

@ -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
}

62
openapi/user/entry.go Normal file
View file

@ -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
}

View file

@ -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",
}

View file

@ -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
}

View file

@ -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

View file

@ -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