From 9762300595f8cb9bb235740c6b3255429c1b238b Mon Sep 17 00:00:00 2001 From: smail Date: Sat, 26 Sep 2026 01:20:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=87=AA=E8=BA=AB=E6=94=B6?= =?UTF-8?q?=E4=BF=A1=E5=9C=B0=E5=9D=80=E5=8F=AF=E7=BB=91=E5=AE=9A=E5=A4=96?= =?UTF-8?q?=E9=83=A8=E5=AE=89=E5=85=A8=E9=82=AE=E7=AE=B1=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/controllers/security.go | 51 +++++++++++++++++++++++++++- backend/controllers/user_security.go | 9 +++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/backend/controllers/security.go b/backend/controllers/security.go index 2e81915..bee4e61 100644 --- a/backend/controllers/security.go +++ b/backend/controllers/security.go @@ -995,8 +995,38 @@ func currentUserPrimaryEmail(userID int, email string) (bool, error) { if db.Instance == nil || userID <= 0 { return false, errors.New("database is unavailable") } + normalizedEmail, err := externalemail.NormalizeEmail(email) + if err != nil { + return false, err + } var binding models.UserEmail - return db.Instance.Where("user_id = ? AND verified_at IS NOT NULL AND status != ? AND is_primary = 1 AND (address_canonical = ? OR LOWER(address) = ?)", userID, 3, email, email).Limit(1).Get(&binding) + owned, err := db.Instance.Where("user_id = ? AND verified_at IS NOT NULL AND status != ? AND is_primary = 1 AND (address_canonical = ? OR LOWER(address) = ?)", userID, 3, normalizedEmail, normalizedEmail).Limit(1).Get(&binding) + if err != nil || owned { + return owned, err + } + + // 本地收信按账号和域名路由,新建账号不一定存在独立的主邮箱绑定记录。 + parts := strings.SplitN(normalizedEmail, "@", 2) + domains := config.CurrentMailDomains() + localDomain := strings.EqualFold(parts[1], domains.Primary) + for _, domain := range domains.Accepted { + if strings.EqualFold(parts[1], domain) { + localDomain = true + break + } + } + if !localDomain { + return false, nil + } + var user models.User + found, err := db.Instance.ID(userID).Get(&user) + if err != nil { + return false, err + } + if !found { + return false, errors.New("user is unavailable") + } + return strings.EqualFold(parts[0], user.Account), nil } func VerifyExternalEmail(ctx *context.Context, w http.ResponseWriter, req *http.Request) { @@ -1010,6 +1040,16 @@ func VerifyExternalEmail(ctx *context.Context, w http.ResponseWriter, req *http. writeSecurityError(w, response.ParamsError, "验证失败") return } + // 确认时重新检查归属,拒绝修复前创建或域名配置变化后的自身邮箱挑战。 + owned, err := currentUserPrimaryEmail(ctx.UserID, input.Email) + if err != nil { + writeSecurityError(w, response.ParamsError, "验证失败") + return + } + if owned { + writeSecurityError(w, response.ParamsError, "不能将当前邮箱绑定为外部安全邮箱") + return + } if err := runtime.verification.VerifyBound(input.ID, ctx.UserAccount, input.Email, input.Code, verification.PurposeExternalEmailBind, input.BindingID, remoteIP(req)); err != nil { writeSecurityError(w, response.ParamsError, externalEmailVerificationCheckMessage(err)) return @@ -1038,6 +1078,15 @@ func VerifyOwnExternalEmail(ctx *context.Context, w http.ResponseWriter, req *ht writeSecurityError(w, response.ParamsError, "验证失败") return } + owned, err := currentUserPrimaryEmail(ctx.UserID, normalizedEmail) + if err != nil { + writeSecurityError(w, response.ParamsError, "验证失败") + return + } + if owned { + writeSecurityError(w, response.ParamsError, "不能将当前邮箱绑定为外部安全邮箱") + return + } if err := runtime.verification.VerifyBound( input.ID, ctx.UserAccount, diff --git a/backend/controllers/user_security.go b/backend/controllers/user_security.go index f35bf34..dcd6b33 100644 --- a/backend/controllers/user_security.go +++ b/backend/controllers/user_security.go @@ -301,6 +301,15 @@ func VerifyManagedExternalEmail(ctx *context.Context, w http.ResponseWriter, req writeSecurityError(w, response.ParamsError, "验证失败") return } + owned, err := currentUserPrimaryEmail(target.ID, normalizedEmail) + if err != nil { + writeSecurityError(w, response.ParamsError, "验证失败") + return + } + if owned { + writeSecurityError(w, response.ParamsError, "不能将当前邮箱绑定为外部安全邮箱") + return + } if err := runtime.verification.VerifyBound( input.ChallengeID, target.Account,