diff --git a/controller/misc.go b/controller/misc.go index 29b3a5c5e180..7c6f9820f37d 100644 --- a/controller/misc.go +++ b/controller/misc.go @@ -8,6 +8,7 @@ import ( "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/constant" + "github.com/QuantumNous/new-api/i18n" "github.com/QuantumNous/new-api/logger" "github.com/QuantumNous/new-api/middleware" "github.com/QuantumNous/new-api/model" @@ -231,18 +232,12 @@ func GetHomePageContent(c *gin.Context) { func SendEmailVerification(c *gin.Context) { email := c.Query("email") if err := common.Validate.Var(email, "required,email"); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + common.ApiErrorI18n(c, i18n.MsgInvalidParams) return } parts := strings.Split(email, "@") if len(parts) != 2 { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的邮箱地址", - }) + common.ApiErrorI18n(c, i18n.MsgAuthEmailInvalid) return } localPart := parts[0] @@ -256,37 +251,38 @@ func SendEmailVerification(c *gin.Context) { } } if !allowed { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "The administrator has enabled the email domain name whitelist, and your email address is not allowed due to special symbols or it's not in the whitelist.", - }) + common.ApiErrorI18n(c, i18n.MsgAuthEmailDomainRestricted) return } } if common.EmailAliasRestrictionEnabled { containsSpecialSymbols := strings.Contains(localPart, "+") || strings.Contains(localPart, ".") if containsSpecialSymbols { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "管理员已启用邮箱地址别名限制,您的邮箱地址由于包含特殊符号而被拒绝。", - }) + common.ApiErrorI18n(c, i18n.MsgAuthEmailAliasRestricted) return } } if model.IsEmailAlreadyTaken(email) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "邮箱地址已被占用", - }) + common.ApiErrorI18n(c, i18n.MsgAuthEmailTaken) return } code := common.GenerateVerificationCode(6) common.RegisterVerificationCodeWithKey(email, code, common.EmailVerificationPurpose) - subject := fmt.Sprintf("%s邮箱验证邮件", common.SystemName) - content := fmt.Sprintf("

您好,你正在进行%s邮箱验证。

"+ - "

您的验证码为: %s

"+ - "

验证码 %d 分钟内有效,如果不是本人操作,请忽略。

", common.SystemName, code, common.VerificationValidMinutes) + subject := i18n.T(c, i18n.MsgAuthEmailVerificationSubject, map[string]any{ + "SystemName": common.SystemName, + }) + content := fmt.Sprintf("

%s

%s

%s

", + i18n.T(c, i18n.MsgAuthEmailVerificationIntro, map[string]any{ + "SystemName": common.SystemName, + }), + i18n.T(c, i18n.MsgAuthEmailVerificationCode, map[string]any{ + "Code": code, + }), + i18n.T(c, i18n.MsgAuthEmailVerificationExpiry, map[string]any{ + "Minutes": common.VerificationValidMinutes, + }), + ) err := common.SendEmail(subject, email, content) if err != nil { common.ApiError(c, err) @@ -302,21 +298,30 @@ func SendEmailVerification(c *gin.Context) { func SendPasswordResetEmail(c *gin.Context) { email := c.Query("email") if err := common.Validate.Var(email, "required,email"); err != nil { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + common.ApiErrorI18n(c, i18n.MsgInvalidParams) return } if model.IsEmailAlreadyTaken(email) { code := common.GenerateVerificationCode(0) common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose) link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", system_setting.ServerAddress, email, code) - subject := fmt.Sprintf("%s密码重置", common.SystemName) - content := fmt.Sprintf("

您好,你正在进行%s密码重置。

"+ - "

点击 此处 进行密码重置。

"+ - "

如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:
%s

"+ - "

重置链接 %d 分钟内有效,如果不是本人操作,请忽略。

", common.SystemName, link, link, common.VerificationValidMinutes) + subject := i18n.T(c, i18n.MsgAuthPasswordResetSubject, map[string]any{ + "SystemName": common.SystemName, + }) + content := fmt.Sprintf("

%s

%s

%s

%s

", + i18n.T(c, i18n.MsgAuthPasswordResetIntro, map[string]any{ + "SystemName": common.SystemName, + }), + i18n.T(c, i18n.MsgAuthPasswordResetClick, map[string]any{ + "Link": link, + }), + i18n.T(c, i18n.MsgAuthPasswordResetCopy, map[string]any{ + "Link": link, + }), + i18n.T(c, i18n.MsgAuthPasswordResetExpiry, map[string]any{ + "Minutes": common.VerificationValidMinutes, + }), + ) err := common.SendEmail(subject, email, content) if err != nil { logger.LogError(c.Request.Context(), fmt.Sprintf("failed to send password reset email to %s: %s", email, err.Error())) @@ -337,17 +342,11 @@ func ResetPassword(c *gin.Context) { var req PasswordResetRequest err := json.NewDecoder(c.Request.Body).Decode(&req) if req.Email == "" || req.Token == "" { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "无效的参数", - }) + common.ApiErrorI18n(c, i18n.MsgInvalidParams) return } if !common.VerifyCodeWithKey(req.Email, req.Token, common.PasswordResetPurpose) { - c.JSON(http.StatusOK, gin.H{ - "success": false, - "message": "重置链接非法或已过期", - }) + common.ApiErrorI18n(c, i18n.MsgAuthPasswordResetInvalid) return } password := common.GenerateVerificationCode(12) diff --git a/i18n/keys.go b/i18n/keys.go index 8e551d2ea657..ec4844cfb22b 100644 --- a/i18n/keys.go +++ b/i18n/keys.go @@ -40,6 +40,28 @@ const ( MsgAuthInsufficientPrivilege = "auth.insufficient_privilege" ) +// Public auth and verification messages +const ( + MsgAuthEmailInvalid = "auth.email_invalid" + MsgAuthEmailDomainRestricted = "auth.email_domain_restricted" + MsgAuthEmailAliasRestricted = "auth.email_alias_restricted" + MsgAuthEmailTaken = "auth.email_taken" + MsgAuthEmailVerificationSubject = "auth.email_verification_subject" + MsgAuthEmailVerificationIntro = "auth.email_verification_intro" + MsgAuthEmailVerificationCode = "auth.email_verification_code" + MsgAuthEmailVerificationExpiry = "auth.email_verification_expiry" + MsgAuthPasswordResetSubject = "auth.password_reset_subject" + MsgAuthPasswordResetIntro = "auth.password_reset_intro" + MsgAuthPasswordResetClick = "auth.password_reset_click" + MsgAuthPasswordResetCopy = "auth.password_reset_copy" + MsgAuthPasswordResetExpiry = "auth.password_reset_expiry" + MsgAuthPasswordResetInvalid = "auth.password_reset_invalid" + MsgAuthEmailRateLimitWait = "auth.email_rate_limit_wait" + MsgAuthEmailRateLimitRetry = "auth.email_rate_limit_retry" + MsgAuthTurnstileTokenEmpty = "auth.turnstile_token_empty" + MsgAuthTurnstileFailed = "auth.turnstile_failed" +) + // Token related messages const ( MsgTokenNameTooLong = "token.name_too_long" diff --git a/i18n/locales/en.yaml b/i18n/locales/en.yaml index 064abc708420..bd07951b4c21 100644 --- a/i18n/locales/en.yaml +++ b/i18n/locales/en.yaml @@ -32,6 +32,24 @@ auth.user_id_format_error: "Unauthorized, New-Api-User header format error" auth.user_id_mismatch: "Unauthorized, New-Api-User does not match logged in user" auth.user_banned: "User has been banned" auth.insufficient_privilege: "Unauthorized, insufficient privileges" +auth.email_invalid: "Invalid email address" +auth.email_domain_restricted: "The administrator has enabled the email domain whitelist, and your email address is not allowed because it contains special symbols or is not in the whitelist." +auth.email_alias_restricted: "The administrator has enabled email alias restrictions, and your email address was rejected because it contains special symbols." +auth.email_taken: "Email address is already in use" +auth.email_verification_subject: "{{.SystemName}} email verification" +auth.email_verification_intro: "Hello, you are verifying your email address for {{.SystemName}}." +auth.email_verification_code: "Your verification code is: {{.Code}}" +auth.email_verification_expiry: "This code is valid for {{.Minutes}} minutes. If you did not request this, you can ignore this email." +auth.password_reset_subject: "{{.SystemName}} password reset" +auth.password_reset_intro: "Hello, you requested a password reset for {{.SystemName}}." +auth.password_reset_click: "Click here to reset your password." +auth.password_reset_copy: "If the link is not clickable, copy and paste this URL into your browser:
{{.Link}}" +auth.password_reset_expiry: "This reset link is valid for {{.Minutes}} minutes. If you did not request this, you can ignore this email." +auth.password_reset_invalid: "The reset link is invalid or has expired" +auth.email_rate_limit_wait: "Too many requests. Please wait {{.Seconds}} seconds and try again" +auth.email_rate_limit_retry: "Too many requests. Please try again later" +auth.turnstile_token_empty: "Turnstile token is empty" +auth.turnstile_failed: "Turnstile verification failed. Please refresh and try again." # Token messages token.name_too_long: "Token name is too long" diff --git a/i18n/locales/zh-CN.yaml b/i18n/locales/zh-CN.yaml index b05ca743f33a..f59137d81462 100644 --- a/i18n/locales/zh-CN.yaml +++ b/i18n/locales/zh-CN.yaml @@ -33,6 +33,24 @@ auth.user_id_format_error: "无权进行此操作,New-Api-User 格式错误" auth.user_id_mismatch: "无权进行此操作,New-Api-User 与登录用户不匹配" auth.user_banned: "用户已被封禁" auth.insufficient_privilege: "无权进行此操作,权限不足" +auth.email_invalid: "无效的邮箱地址" +auth.email_domain_restricted: "管理员已启用邮箱域名白名单,您的邮箱地址由于包含特殊符号或不在白名单中而被拒绝。" +auth.email_alias_restricted: "管理员已启用邮箱地址别名限制,您的邮箱地址由于包含特殊符号而被拒绝。" +auth.email_taken: "邮箱地址已被占用" +auth.email_verification_subject: "{{.SystemName}}邮箱验证邮件" +auth.email_verification_intro: "您好,你正在进行{{.SystemName}}邮箱验证。" +auth.email_verification_code: "您的验证码为: {{.Code}}" +auth.email_verification_expiry: "验证码 {{.Minutes}} 分钟内有效,如果不是本人操作,请忽略。" +auth.password_reset_subject: "{{.SystemName}}密码重置" +auth.password_reset_intro: "您好,你正在进行{{.SystemName}}密码重置。" +auth.password_reset_click: "点击 此处 进行密码重置。" +auth.password_reset_copy: "如果链接无法点击,请尝试点击下面的链接或将其复制到浏览器中打开:
{{.Link}}" +auth.password_reset_expiry: "重置链接 {{.Minutes}} 分钟内有效,如果不是本人操作,请忽略。" +auth.password_reset_invalid: "重置链接非法或已过期" +auth.email_rate_limit_wait: "发送过于频繁,请等待 {{.Seconds}} 秒后再试" +auth.email_rate_limit_retry: "发送过于频繁,请稍后再试" +auth.turnstile_token_empty: "Turnstile token 为空" +auth.turnstile_failed: "Turnstile 校验失败,请刷新重试!" # Token messages token.name_too_long: "令牌名称过长" diff --git a/i18n/locales/zh-TW.yaml b/i18n/locales/zh-TW.yaml index 6ae222b711eb..f7cfafe58da8 100644 --- a/i18n/locales/zh-TW.yaml +++ b/i18n/locales/zh-TW.yaml @@ -33,6 +33,24 @@ auth.user_id_format_error: "無權進行此操作,New-Api-User 格式錯誤" auth.user_id_mismatch: "無權進行此操作,New-Api-User 與登入使用者不匹配" auth.user_banned: "使用者已被封禁" auth.insufficient_privilege: "無權進行此操作,權限不足" +auth.email_invalid: "無效的信箱位址" +auth.email_domain_restricted: "管理員已啟用信箱網域白名單,您的信箱位址由於包含特殊符號或不在白名單中而被拒絕。" +auth.email_alias_restricted: "管理員已啟用信箱位址別名限制,您的信箱位址由於包含特殊符號而被拒絕。" +auth.email_taken: "信箱位址已被占用" +auth.email_verification_subject: "{{.SystemName}}信箱驗證郵件" +auth.email_verification_intro: "您好,您正在進行{{.SystemName}}信箱驗證。" +auth.email_verification_code: "您的驗證碼為: {{.Code}}" +auth.email_verification_expiry: "驗證碼 {{.Minutes}} 分鐘內有效,如果不是本人操作,請忽略。" +auth.password_reset_subject: "{{.SystemName}}密碼重設" +auth.password_reset_intro: "您好,您正在進行{{.SystemName}}密碼重設。" +auth.password_reset_click: "點擊 此處 進行密碼重設。" +auth.password_reset_copy: "如果連結無法點擊,請嘗試點擊下面的連結或將其複製到瀏覽器中開啟:
{{.Link}}" +auth.password_reset_expiry: "重設連結 {{.Minutes}} 分鐘內有效,如果不是本人操作,請忽略。" +auth.password_reset_invalid: "重設連結非法或已過期" +auth.email_rate_limit_wait: "傳送過於頻繁,請等待 {{.Seconds}} 秒後再試" +auth.email_rate_limit_retry: "傳送過於頻繁,請稍後再試" +auth.turnstile_token_empty: "Turnstile token 為空" +auth.turnstile_failed: "Turnstile 驗證失敗,請重新整理後再試!" # Token messages token.name_too_long: "令牌名稱過長" diff --git a/middleware/email-verification-rate-limit.go b/middleware/email-verification-rate-limit.go index 470d7731cb0f..7f172ba8871d 100644 --- a/middleware/email-verification-rate-limit.go +++ b/middleware/email-verification-rate-limit.go @@ -2,11 +2,11 @@ package middleware import ( "context" - "fmt" "net/http" "time" "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/i18n" "github.com/gin-gonic/gin" ) @@ -49,7 +49,9 @@ func redisEmailVerificationRateLimiter(c *gin.Context) { c.JSON(http.StatusTooManyRequests, gin.H{ "success": false, - "message": fmt.Sprintf("发送过于频繁,请等待 %d 秒后再试", waitSeconds), + "message": common.TranslateMessage(c, i18n.MsgAuthEmailRateLimitWait, map[string]any{ + "Seconds": waitSeconds, + }), }) c.Abort() } @@ -60,7 +62,7 @@ func memoryEmailVerificationRateLimiter(c *gin.Context) { if !inMemoryRateLimiter.Request(key, EmailVerificationMaxRequests, EmailVerificationDuration) { c.JSON(http.StatusTooManyRequests, gin.H{ "success": false, - "message": "发送过于频繁,请稍后再试", + "message": common.TranslateMessage(c, i18n.MsgAuthEmailRateLimitRetry), }) c.Abort() return diff --git a/middleware/turnstile-check.go b/middleware/turnstile-check.go index af87fad4423c..0b6595e72580 100644 --- a/middleware/turnstile-check.go +++ b/middleware/turnstile-check.go @@ -6,6 +6,7 @@ import ( "net/url" "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/i18n" "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" ) @@ -27,7 +28,7 @@ func TurnstileCheck() gin.HandlerFunc { if response == "" { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": "Turnstile token 为空", + "message": common.TranslateMessage(c, i18n.MsgAuthTurnstileTokenEmpty), }) c.Abort() return @@ -61,7 +62,7 @@ func TurnstileCheck() gin.HandlerFunc { if !res.Success { c.JSON(http.StatusOK, gin.H{ "success": false, - "message": "Turnstile 校验失败,请刷新重试!", + "message": common.TranslateMessage(c, i18n.MsgAuthTurnstileFailed), }) c.Abort() return @@ -70,7 +71,7 @@ func TurnstileCheck() gin.HandlerFunc { err = session.Save() if err != nil { c.JSON(http.StatusOK, gin.H{ - "message": "无法保存会话信息,请重试", + "message": common.TranslateMessage(c, i18n.MsgUserSessionSaveFailed), "success": false, }) return