修复附件下载和安全邮箱认证边界
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-06 06:06:15 +08:00
parent 20d30d5f23
commit f6566c7aa9
3 changed files with 34 additions and 4 deletions

View File

@ -36,12 +36,12 @@ func GetAttachments(ctx *context.Context, w http.ResponseWriter, req *http.Reque
func Download(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
urlInfos := strings.Split(strings.Trim(req.URL.Path, "/"), "/")
if len(urlInfos) != 5 {
if len(urlInfos) != 4 || urlInfos[0] != "attachments" || urlInfos[1] != "download" {
response.NewErrorResponse(response.ParamsError, "", "").FPrint(w)
return
}
emailId := cast.ToInt(urlInfos[3])
index := cast.ToInt(urlInfos[4])
emailId := cast.ToInt(urlInfos[2])
index := cast.ToInt(urlInfos[3])
fileName, content, err := attachments.GetAttachmentsByIndex(ctx, emailId, index)
if err != nil {

View File

@ -3,6 +3,7 @@ package controllers
import (
"crypto/rand"
"encoding/hex"
"errors"
"net"
"net/http"
"net/url"
@ -12,6 +13,7 @@ import (
"git.pcux.cn/xuans/Smail/config"
"git.pcux.cn/xuans/Smail/db"
"git.pcux.cn/xuans/Smail/dto/response"
"git.pcux.cn/xuans/Smail/models"
"git.pcux.cn/xuans/Smail/services/access"
"git.pcux.cn/xuans/Smail/services/credentials"
"git.pcux.cn/xuans/Smail/services/externalemail"
@ -633,11 +635,31 @@ func BindExternalEmail(ctx *context.Context, w http.ResponseWriter, req *http.Re
writeSecurityError(w, response.NeedLogin, "请先登录")
return
}
if !ctx.RecentAuth {
writeSecurityError(w, response.NoAccessPrivileges, "此操作需要最近十分钟内完成登录,请重新登录后再试")
return
}
var input externalEmailRequest
if err := decodeJSONRequest(w, req, &input); err != nil {
writeSecurityError(w, response.ParamsError, "请求无效")
return
}
normalizedEmail, err := externalemail.NormalizeEmail(input.Email)
if err != nil {
writeSecurityError(w, response.ParamsError, "外部邮箱绑定失败")
return
}
owned, err := currentUserPrimaryEmail(ctx.UserID, normalizedEmail)
if err != nil {
log.WithError(err).Error("check current user primary email failed")
writeSecurityError(w, response.ServerError, "外部邮箱绑定失败")
return
}
if owned {
writeSecurityError(w, response.ParamsError, "不能将当前邮箱绑定为外部安全邮箱")
return
}
input.Email = normalizedEmail
item, err := securityRuntime.external.Bind(externalemail.BindRequest{UserID: ctx.UserID, Email: input.Email, Type: input.Type})
if err != nil {
writeSecurityError(w, response.ParamsError, "外部邮箱绑定失败")
@ -652,6 +674,14 @@ func BindExternalEmail(ctx *context.Context, w http.ResponseWriter, req *http.Re
response.NewSuccessResponse(map[string]any{"id": item.ID, "challenge_id": challenge.ID, "status": item.Status, "email": maskEmail(input.Email), "expires_at": challenge.ExpiresAt.UTC()}).FPrint(w)
}
func currentUserPrimaryEmail(userID int, email string) (bool, error) {
if db.Instance == nil || userID <= 0 {
return false, errors.New("database is unavailable")
}
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)
}
func VerifyExternalEmail(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
if ctx == nil || ctx.UserID <= 0 || securityRuntime.external == nil || securityRuntime.verification == nil {
writeSecurityError(w, response.NeedLogin, "请先登录")

View File

@ -193,7 +193,7 @@ func mustChangePasswordAllowedPath(path string) bool {
func publicSecurityPath(path string) bool {
switch normalizedAPIPath(path) {
case "/api/ping", "/api/login", "/api/security/login/verify", "/api/security/recovery/begin", "/api/security/recovery/verify", "/api/security/recovery/complete", "/api/security/recovery/webauthn/begin", "/api/security/recovery/webauthn/verify", "/api/security/webauthn/login/begin", "/api/security/webauthn/login/finish":
case "/api/ping", "/api/login", "/api/security/login/verify", "/api/security/login/resend", "/api/security/recovery/begin", "/api/security/recovery/verify", "/api/security/recovery/complete", "/api/security/recovery/webauthn/begin", "/api/security/recovery/webauthn/verify", "/api/security/webauthn/login/begin", "/api/security/webauthn/login/finish":
return true
default:
return false