refactor(mail): 统一邮件认证策略请求与返回格式。

- 将 `auth-policy` 调整为读取 `login`、`remote` 等字段,并返回数值型 `status`。
- 支持按邮箱定位用户与邮箱箱体,补充 IMAP/POP3 认证策略校验。
- 更新 Dovecot 与 Postfix 配置,移除原有的发件人 BCC 依赖。
- 补充相关测试并同步部署说明。
This commit is contained in:
LanQin_
2026-06-23 21:52:05 +08:00
parent 8d434b9ca8
commit f2457c63ee
6 changed files with 68 additions and 25 deletions
+40 -21
View File
@@ -599,48 +599,67 @@ func (a *App) checkAndRecordProtocolRate(ctx context.Context, user *User, mb *Ma
func (a *App) handleAuthPolicy(w http.ResponseWriter, r *http.Request) {
var req struct {
Protocol string `json:"protocol"`
Username string `json:"username"`
IP string `json:"ip"`
Login string `json:"login"`
Protocol string `json:"protocol"`
Username string `json:"username"`
IP string `json:"ip"`
Remote string `json:"remote"`
Success *bool `json:"success"`
PolicyReject *bool `json:"policy_reject"`
}
if err := decodeJSON(r, &req); err != nil {
w.WriteHeader(http.StatusCreated)
respondJSON(w, http.StatusCreated, map[string]string{"status": "allow"})
respondJSON(w, http.StatusOK, map[string]int{"status": 0})
return
}
var user *User
if req.Username != "" {
var passHash string
user, passHash, _ = a.userByEmail(r.Context(), req.Username)
_ = passHash
var mailbox *Mailbox
login := normalizeEmail(req.Login)
if login == "" {
login = normalizeEmail(req.Username)
}
if login != "" {
if mb, err := a.mailboxByAddress(r.Context(), login); err == nil {
mailbox = mb
user, _ = a.userByID(r.Context(), mb.UserID)
}
if user == nil {
var passHash string
user, passHash, _ = a.userByEmail(r.Context(), login)
_ = passHash
}
}
if user == nil || user.Disabled {
w.WriteHeader(http.StatusCreated)
respondJSON(w, http.StatusCreated, map[string]string{"status": "deny", "reason": "user not found or disabled"})
respondJSON(w, http.StatusOK, map[string]any{"status": -1, "msg": "user not found or disabled"})
return
}
if user.Role == "admin" {
w.WriteHeader(http.StatusCreated)
respondJSON(w, http.StatusCreated, map[string]string{"status": "allow"})
respondJSON(w, http.StatusOK, map[string]int{"status": 0})
return
}
limits := user.Limits
var err error
switch req.Protocol {
case "imap", "IMAP":
switch strings.ToLower(req.Protocol) {
case "imap":
if limits.IMAPMinuteLimit > 0 {
err = a.checkAndRecordProtocolRate(r.Context(), user, nil, "imap_events", 0, limits.IMAPMinuteLimit)
if mailbox == nil {
err = errors.New("mailbox not found")
} else {
err = a.checkAndRecordProtocolRate(r.Context(), user, mailbox, "imap_events", 0, limits.IMAPMinuteLimit)
}
}
case "pop3", "POP3":
case "pop3":
if limits.POP3MinuteLimit > 0 {
err = a.checkAndRecordProtocolRate(r.Context(), user, nil, "pop3_events", 0, limits.POP3MinuteLimit)
if mailbox == nil {
err = errors.New("mailbox not found")
} else {
err = a.checkAndRecordProtocolRate(r.Context(), user, mailbox, "pop3_events", 0, limits.POP3MinuteLimit)
}
}
}
w.WriteHeader(http.StatusCreated)
if err != nil {
respondJSON(w, http.StatusCreated, map[string]any{"status": "deny", "reason": err.Error()})
respondJSON(w, http.StatusOK, map[string]any{"status": -1, "msg": err.Error()})
} else {
respondJSON(w, http.StatusCreated, map[string]any{"status": "allow"})
respondJSON(w, http.StatusOK, map[string]int{"status": 0})
}
}