Browse Source

fix: improve login error handling to distinguish database errors from auth failures

ValidateAndFill now checks the DB query result and returns sentinel errors
(ErrDatabase, ErrInvalidCredentials, ErrUserEmptyCredentials) instead of
hardcoded Chinese strings. The controller maps each sentinel to the
appropriate i18n message, so users see "please contact admin" on DB errors
instead of a misleading "wrong password" message. Non-DB errors still
return a unified vague response to avoid leaking user existence.
CaIon 3 weeks ago
parent
commit
2819e3a1d1
5 changed files with 28 additions and 11 deletions
  1. 9 4
      controller/user.go
  2. 1 1
      i18n/locales/en.yaml
  3. 1 1
      i18n/locales/zh-CN.yaml
  4. 1 1
      i18n/locales/zh-TW.yaml
  5. 16 4
      model/user.go

+ 9 - 4
controller/user.go

@@ -52,10 +52,15 @@ func Login(c *gin.Context) {
 	}
 	}
 	err = user.ValidateAndFill()
 	err = user.ValidateAndFill()
 	if err != nil {
 	if err != nil {
-		c.JSON(http.StatusOK, gin.H{
-			"message": err.Error(),
-			"success": false,
-		})
+		switch {
+		case errors.Is(err, model.ErrDatabase):
+			common.SysLog(fmt.Sprintf("Login database error for user %s: %v", username, err))
+			common.ApiErrorI18n(c, i18n.MsgDatabaseError)
+		case errors.Is(err, model.ErrUserEmptyCredentials):
+			common.ApiErrorI18n(c, i18n.MsgInvalidParams)
+		default:
+			common.ApiErrorI18n(c, i18n.MsgUserUsernameOrPasswordError)
+		}
 		return
 		return
 	}
 	}
 
 

+ 1 - 1
i18n/locales/en.yaml

@@ -2,7 +2,7 @@
 
 
 # Common messages
 # Common messages
 common.invalid_params: "Invalid parameters"
 common.invalid_params: "Invalid parameters"
-common.database_error: "Database error, please try again later"
+common.database_error: "Database error, please contact the administrator"
 common.retry_later: "Please try again later"
 common.retry_later: "Please try again later"
 common.generate_failed: "Generation failed"
 common.generate_failed: "Generation failed"
 common.not_found: "Not found"
 common.not_found: "Not found"

+ 1 - 1
i18n/locales/zh-CN.yaml

@@ -3,7 +3,7 @@
 
 
 # Common messages
 # Common messages
 common.invalid_params: "无效的参数"
 common.invalid_params: "无效的参数"
-common.database_error: "数据库错误,请稍后重试"
+common.database_error: "数据库出错,请联系管理员"
 common.retry_later: "请稍后重试"
 common.retry_later: "请稍后重试"
 common.generate_failed: "生成失败"
 common.generate_failed: "生成失败"
 common.not_found: "未找到"
 common.not_found: "未找到"

+ 1 - 1
i18n/locales/zh-TW.yaml

@@ -3,7 +3,7 @@
 
 
 # Common messages
 # Common messages
 common.invalid_params: "無效的參數"
 common.invalid_params: "無效的參數"
-common.database_error: "資料庫錯誤,請稍後重試"
+common.database_error: "資料庫出錯,請聯繫管理員"
 common.retry_later: "請稍後重試"
 common.retry_later: "請稍後重試"
 common.generate_failed: "生成失敗"
 common.generate_failed: "生成失敗"
 common.not_found: "未找到"
 common.not_found: "未找到"

+ 16 - 4
model/user.go

@@ -18,6 +18,12 @@ import (
 
 
 const UserNameMaxLength = 20
 const UserNameMaxLength = 20
 
 
+var (
+	ErrDatabase              = errors.New("database error")
+	ErrInvalidCredentials    = errors.New("invalid credentials")
+	ErrUserEmptyCredentials  = errors.New("empty credentials")
+)
+
 // User if you add sensitive fields, don't forget to clean them in setupLogin function.
 // User if you add sensitive fields, don't forget to clean them in setupLogin function.
 // Otherwise, the sensitive information will be saved on local storage in plain text!
 // Otherwise, the sensitive information will be saved on local storage in plain text!
 type User struct {
 type User struct {
@@ -597,13 +603,19 @@ func (user *User) ValidateAndFill() (err error) {
 	password := user.Password
 	password := user.Password
 	username := strings.TrimSpace(user.Username)
 	username := strings.TrimSpace(user.Username)
 	if username == "" || password == "" {
 	if username == "" || password == "" {
-		return errors.New("用户名或密码为空")
+		return ErrUserEmptyCredentials
+	}
+	// find by username or email
+	err = DB.Where("username = ? OR email = ?", username, username).First(user).Error
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return ErrInvalidCredentials
+		}
+		return fmt.Errorf("%w: %v", ErrDatabase, err)
 	}
 	}
-	// find buy username or email
-	DB.Where("username = ? OR email = ?", username, username).First(user)
 	okay := common.ValidatePasswordAndHash(password, user.Password)
 	okay := common.ValidatePasswordAndHash(password, user.Password)
 	if !okay || user.Status != common.UserStatusEnabled {
 	if !okay || user.Status != common.UserStatusEnabled {
-		return errors.New("用户名或密码错误,或用户已被封禁")
+		return ErrInvalidCredentials
 	}
 	}
 	return nil
 	return nil
 }
 }