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.
This commit is contained in:
parent
484ed8d899
commit
b43a38b387
6 changed files with 192 additions and 150 deletions
|
|
@ -33,8 +33,8 @@ var (
|
||||||
defaultConfig *Config
|
defaultConfig *Config
|
||||||
// Team configurations by locale
|
// Team configurations by locale
|
||||||
teamConfigs = make(map[string]*TeamConfig)
|
teamConfigs = make(map[string]*TeamConfig)
|
||||||
// Register configurations by locale
|
// Entry configurations by locale (unified login + register)
|
||||||
registerConfigs = make(map[string]*RegisterConfig)
|
entryConfigs = make(map[string]*EntryConfig)
|
||||||
// Mutex for thread safety
|
// Mutex for thread safety
|
||||||
configMutex sync.RWMutex
|
configMutex sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
@ -50,7 +50,7 @@ func Load(appConfig config.Config) error {
|
||||||
providers = make(map[string]*Provider)
|
providers = make(map[string]*Provider)
|
||||||
defaultConfig = nil
|
defaultConfig = nil
|
||||||
teamConfigs = make(map[string]*TeamConfig)
|
teamConfigs = make(map[string]*TeamConfig)
|
||||||
registerConfigs = make(map[string]*RegisterConfig)
|
entryConfigs = make(map[string]*EntryConfig)
|
||||||
|
|
||||||
// Load signin configurations from openapi/user/signin directory
|
// Load signin configurations from openapi/user/signin directory
|
||||||
err := loadSigninConfigs(appConfig.Root)
|
err := loadSigninConfigs(appConfig.Root)
|
||||||
|
|
@ -58,10 +58,10 @@ func Load(appConfig config.Config) error {
|
||||||
return fmt.Errorf("failed to load signin configs: %v", err)
|
return fmt.Errorf("failed to load signin configs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load register configurations from openapi/user/register directory
|
// Load entry configurations from openapi/user/entry directory
|
||||||
err = loadRegisterConfigs(appConfig.Root)
|
err = loadEntryConfigs(appConfig.Root)
|
||||||
if err != nil {
|
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
|
// Load team configurations from openapi/user/team directory
|
||||||
|
|
@ -366,57 +366,6 @@ func loadTeamConfigs(_ string) error {
|
||||||
return nil
|
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
|
// GetPublicConfig returns the public configuration for a given locale
|
||||||
func GetPublicConfig(locale string) *Config {
|
func GetPublicConfig(locale string) *Config {
|
||||||
configMutex.RLock()
|
configMutex.RLock()
|
||||||
|
|
@ -500,34 +449,6 @@ func GetTeamConfig(locale string) *TeamConfig {
|
||||||
return nil
|
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"
|
// extractEnvVarName extracts the environment variable name from a string like "$ENV.VAR_NAME"
|
||||||
func extractEnvVarName(value string) string {
|
func extractEnvVarName(value string) string {
|
||||||
if value == "" {
|
if value == "" {
|
||||||
|
|
@ -680,13 +601,108 @@ func processConfigENVVariables(config *Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processRegisterConfigENVVariables processes environment variables in the register configuration
|
// loadEntryConfigs loads all entry configurations from the openapi/user/entry directory
|
||||||
func processRegisterConfigENVVariables(config *RegisterConfig) {
|
// 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
|
// Process form configuration
|
||||||
missingEnvVars := processFormConfigENVVariables(config.Form)
|
formMissingVars := processFormConfigENVVariables(config.Form)
|
||||||
|
missingEnvVars = append(missingEnvVars, formMissingVars...)
|
||||||
|
|
||||||
// Log warning for missing environment variables
|
// Log warning for missing environment variables
|
||||||
if len(missingEnvVars) > 0 {
|
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
62
openapi/user/entry.go
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -87,14 +87,14 @@ func LoginThirdParty(providerID string, userinfo *oauthtypes.OIDCUserInfo, login
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get register configuration for role and type
|
// Get entry configuration for role and type
|
||||||
registerConfig := GetRegisterConfig(locale)
|
entryConfig := GetEntryConfig(locale)
|
||||||
if registerConfig == nil {
|
if entryConfig == nil {
|
||||||
// If no register config found, try to get default register config
|
// If no entry config found, try to get default entry config
|
||||||
log.Warn("Register configuration not found for locale '%s', trying default locale 'en'", locale)
|
log.Warn("Entry configuration not found for locale '%s', trying default locale 'en'", locale)
|
||||||
registerConfig = GetRegisterConfig("en")
|
entryConfig = GetEntryConfig("en")
|
||||||
if registerConfig == nil {
|
if entryConfig == nil {
|
||||||
return nil, fmt.Errorf("register configuration not found. Please create register config files in openapi/user/register/")
|
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,
|
"given_name": userinfo.GivenName,
|
||||||
"family_name": userinfo.FamilyName,
|
"family_name": userinfo.FamilyName,
|
||||||
"picture": userinfo.Picture,
|
"picture": userinfo.Picture,
|
||||||
"role_id": registerConfig.Role,
|
"role_id": entryConfig.Role,
|
||||||
"type_id": registerConfig.Type,
|
"type_id": entryConfig.Type,
|
||||||
"status": "active",
|
"status": "active",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -84,20 +84,25 @@ type ProviderRegisterConfig struct {
|
||||||
Auto bool `json:"auto,omitempty"`
|
Auto bool `json:"auto,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterConfig represents the register configuration
|
// EntryConfig represents the unified auth entry configuration (login + register)
|
||||||
type RegisterConfig struct {
|
// This merges signin and register configurations into a single entry point
|
||||||
|
type EntryConfig struct {
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Default bool `json:"default,omitempty"`
|
Default bool `json:"default,omitempty"`
|
||||||
SuccessURL string `json:"success_url,omitempty"`
|
SuccessURL string `json:"success_url,omitempty"`
|
||||||
FailureURL string `json:"failure_url,omitempty"`
|
FailureURL string `json:"failure_url,omitempty"`
|
||||||
AutoLogin bool `json:"auto_login,omitempty"`
|
LogoutRedirect string `json:"logout_redirect,omitempty"` // From signin config
|
||||||
Role string `json:"role,omitempty"`
|
ClientID string `json:"client_id,omitempty"` // From signin config
|
||||||
Type string `json:"type,omitempty"` // User type id
|
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"`
|
Form *FormConfig `json:"form,omitempty"`
|
||||||
Messenger *MessengerConfig `json:"messenger,omitempty"`
|
Token *TokenConfig `json:"token,omitempty"` // From signin config
|
||||||
InviteRequired bool `json:"invite_required,omitempty"`
|
Messenger *MessengerConfig `json:"messenger,omitempty"` // From register config
|
||||||
ThirdParty *ThirdParty `json:"third_party,omitempty"` // Third party login configuration (copied from signin config)
|
InviteRequired bool `json:"invite_required,omitempty"` // From register config
|
||||||
|
ThirdParty *ThirdParty `json:"third_party,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MessengerConfig represents the messenger configuration for user registration
|
// MessengerConfig represents the messenger configuration for user registration
|
||||||
|
|
|
||||||
|
|
@ -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.GET("/login", getLoginConfig) // Get login page config (public) - migrated from /signin
|
||||||
group.POST("/login", login) // User login (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("/login/captcha", getCaptcha) // Get captcha for login (public)
|
||||||
group.GET("/register", getRegisterConfig) // Get register page config (public)
|
group.GET("/entry", getEntryConfig) // Get unified auth entry config (public)
|
||||||
group.POST("/register", register) // User register (public)
|
group.POST("/entry", entry) // Unified auth entry (login/register) (public)
|
||||||
group.POST("/logout", oauth.Guard, placeholder) // User logout
|
group.POST("/logout", oauth.Guard, placeholder) // User logout
|
||||||
|
|
||||||
// Logined User Settings
|
// Logined User Settings
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue