收敛Smail系统通知SMTP通道并更新验收清单
完成Smail系统通知SMTP路由、验证码发送约束和相关回归测试,更新Linux生产测试记录、整改主清单及任务执行清单。
This commit is contained in:
parent
37b2ae016b
commit
07229adadc
@ -6,6 +6,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UsesSmailSystemAccount 判断通知账号是否为 Smail 系统通知账号。
|
||||
func (c NotificationConfig) UsesSmailSystemAccount() bool {
|
||||
return strings.HasPrefix(strings.ToLower(strings.TrimSpace(c.SMTPUsername)), "notify:")
|
||||
}
|
||||
|
||||
// SenderIdentity 根据系统通知账号确定发件人,不能将管理员邮箱当作 SMTP 发件人。
|
||||
func (c NotificationConfig) SenderIdentity() (string, string, error) {
|
||||
name := strings.TrimSpace(c.SenderName)
|
||||
@ -14,7 +19,7 @@ func (c NotificationConfig) SenderIdentity() (string, string, error) {
|
||||
return name, email, nil
|
||||
}
|
||||
username := strings.ToLower(strings.TrimSpace(c.SMTPUsername))
|
||||
if strings.HasPrefix(username, "notify:") {
|
||||
if c.UsesSmailSystemAccount() {
|
||||
account := strings.TrimPrefix(username, "notify:")
|
||||
parsed, err := mail.ParseAddress(account)
|
||||
parts := strings.Split(account, "@")
|
||||
|
||||
@ -3,6 +3,7 @@ package notification
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/mail"
|
||||
)
|
||||
@ -42,6 +43,15 @@ func (s *Service) SendSecurityCode(ctx context.Context, recipient, code string)
|
||||
Text: text, HTML: "<pre>" + html.EscapeString(text) + "</pre>",
|
||||
CC: []address{}, BCC: []address{}, Attrs: []any{},
|
||||
}
|
||||
if s.config.UsesSmailSystemAccount() {
|
||||
if !s.config.SMTPEnabled {
|
||||
return errors.New("Smail 系统通知账号必须启用 SMTP 通道")
|
||||
}
|
||||
if err := s.sendSMTPWithRetry(ctx, payload); err != nil {
|
||||
return fmt.Errorf("SMTP 验证码邮件发送失败:%w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if s.config.HTTPEnabled {
|
||||
if err := s.sendHTTPPayload(ctx, payload); err == nil {
|
||||
return nil
|
||||
|
||||
@ -144,3 +144,22 @@ func TestSecurityCodeSMTPAndHTTPFallback(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecurityCodeSmailSystemAccountRequiresSMTP(t *testing.T) {
|
||||
var calls atomic.Int32
|
||||
endpoint := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls.Add(1)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer endpoint.Close()
|
||||
cfg := newTestConfig(endpoint.URL)
|
||||
cfg.SMTPUsername = "notify:scert@example.test"
|
||||
cfg.SMTPEnabled = false
|
||||
service := New(cfg, nil)
|
||||
if err := service.SendSecurityCode(t.Context(), "bound@example.test", "12345678"); err == nil || !strings.Contains(err.Error(), "必须启用 SMTP") {
|
||||
t.Fatalf("Smail 系统通知未拒绝 HTTP-only 配置:%v", err)
|
||||
}
|
||||
if calls.Load() != 0 {
|
||||
t.Fatal("Smail 系统通知账号禁止调用 HTTP 通道")
|
||||
}
|
||||
}
|
||||
|
||||
@ -122,6 +122,9 @@ func (s *Service) sendHTTP(ctx context.Context, event Event) error {
|
||||
if !s.config.HTTPEnabled {
|
||||
return nil
|
||||
}
|
||||
if s.config.UsesSmailSystemAccount() {
|
||||
return errors.New("Smail 系统通知账号只能通过 SMTP 通道发送")
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -25,6 +25,16 @@ func (s *Service) Send(ctx context.Context, event Event) error {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.config.UsesSmailSystemAccount() {
|
||||
if !s.config.SMTPEnabled {
|
||||
return errors.New("Smail 系统通知账号必须启用 SMTP 通道")
|
||||
}
|
||||
payload, err := s.buildPayload(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.sendSMTPWithRetry(ctx, payload)
|
||||
}
|
||||
var errs []error
|
||||
if s.config.HTTPEnabled {
|
||||
if err := s.sendHTTP(ctx, event); err != nil {
|
||||
|
||||
@ -12,6 +12,8 @@ import (
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@ -313,6 +315,35 @@ func TestSmailSystemSenderUsesSameEnvelopeAndHeader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmailSystemNotificationUsesSMTPOnly(t *testing.T) {
|
||||
server := newSMTPTestServer(t, true, false)
|
||||
host, port := server.address()
|
||||
calls := 0
|
||||
endpoint := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
}))
|
||||
defer endpoint.Close()
|
||||
cfg := smtpTestConfig(host, port, "implicit_tls")
|
||||
cfg.SMTPUsername = "notify:scert@example.test"
|
||||
cfg.HTTPEnabled = true
|
||||
cfg.Endpoint = endpoint.URL
|
||||
cfg.HTTPPath = "/api/email/send"
|
||||
service := New(cfg, credentialReaderStub{values: map[string]string{"password": "test-only-notification-credential"}})
|
||||
service.smtpTLSConfig = server.clientTLSConfig
|
||||
if err := service.Send(context.Background(), Event{Type: "certificate_issue", Status: "success", Domain: "example.test"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if calls != 0 {
|
||||
t.Fatalf("Smail 系统通知不应调用 HTTP 通道,调用次数:%d", calls)
|
||||
}
|
||||
server.mu.Lock()
|
||||
defer server.mu.Unlock()
|
||||
if len(server.messages) != 1 {
|
||||
t.Fatalf("SMTP 系统通知数量错误:%d", len(server.messages))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSMTPStartTLS(t *testing.T) {
|
||||
server := newSMTPTestServer(t, false, true)
|
||||
host, port := server.address()
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
# Linux 生产流程测试记录
|
||||
## Smail 系统通知通道收敛专项(2026-09-30)
|
||||
|
||||
- 代码层识别 Smail `notify:` 系统通知账号,并固定使用 `notification+system@邮件域名` 作为 SMTP 发件身份;不使用管理员邮箱替代。
|
||||
- 证书任务通知和超级管理员验证码在该账号模式下都只调用 SMTP,不调用普通 HTTP 发信接口;验证码收件人仍严格限定为当前绑定邮箱。
|
||||
- SMTP 未启用时返回明确配置错误;未关闭 TLS 校验,未记录 SMTP 密码、验证码或其他真实凭据。
|
||||
- 通知专项、后端全量测试、静态检查和构建均已通过;本记录仅证明代码候选通过,尚未证明远端 Smail SMTP 参数、验证码、密码重置和生产上线流程通过。
|
||||
- 下一步:提交并构建正式候选包,使用密码 SSH 部署到 `8.137.23.95` 和 `45.136.13.241`,按生产流程清理测试数据后验证发信与密码重置;总体验收继续保持 P0-03 完成、等待复审。
|
||||
|
||||
## 恢复标准 Nginx 重载命令后正式页面复测(2026-09-30 17:39 UTC)
|
||||
|
||||
- 通过正式服务器配置页面将临时测试用的重载命令 `/usr/bin/true` 恢复为标准 Nginx 参数 `nginx`、`-s`、`reload`,保存提示“服务器配置已更新”。
|
||||
|
||||
@ -1,4 +1,14 @@
|
||||
# Scert 整改任务执行清单
|
||||
## 本轮任务看板(2026-09-30,Smail 系统通知通道收敛)
|
||||
|
||||
- [x] T2 子项:识别 Smail `notify:` 系统通知账号,固定系统通知发件身份并禁止 HTTP 通道发送。
|
||||
- [x] T2 子项:超级管理员验证码只发送绑定邮箱;系统通知账号只走 SMTP,SMTP 未启用时返回明确错误。
|
||||
- [x] T2 子项:通知专项测试、后端全量测试、`go vet`、`go build` 和差异检查通过。
|
||||
- [~] T4:提交本轮实现,编译正式 Linux 候选包并部署到第一台、第二台服务器,进行真实 SMTP 和密码重置闭环验证。
|
||||
- [~] T5:继续验证多服务器、多域名、证书推送、失败恢复、重试幂等及任务状态持久化。
|
||||
- [ ] T7:三数据库、Windows 服务、清库首次初始化、公网 DNS/ACME、备份恢复和最终生产上线结论。
|
||||
- [!] 总体验收位置不变:**P0-03 完成,等待复审**;本轮没有新增 `[✓]`。
|
||||
|
||||
## 当前任务看板(2026-09-30 17:39 UTC,恢复标准 Nginx 重载命令后正式页面复测)
|
||||
|
||||
- [x] 已通过正式服务器配置页面将临时 `/usr/bin/true` 重载命令恢复为 `nginx`、`-s`、`reload`,并保存成功。
|
||||
|
||||
@ -1,4 +1,17 @@
|
||||
# Scert 整改、合并与优化清单
|
||||
## 本轮整改进度(2026-09-30,Smail 系统通知通道收敛)
|
||||
|
||||
需求确认:继续按明日上线目标推进,但不缩减生产门禁;系统通知使用 Smail `notify:` SMTP 账号时,只允许通过 SMTP 发信,不使用普通 HTTP 发信接口,不记录真实凭据。
|
||||
|
||||
- [x] 新增 Smail 系统通知账号识别,统一生成固定的 `notification+system@邮件域名` 发件身份。
|
||||
- [x] 证书任务通知检测到 `notify:` 系统账号时只走 SMTP,不再执行 HTTP 通道,避免重复发送或普通接口伪造系统发件地址。
|
||||
- [x] 超级管理员验证码检测到 `notify:` 系统账号时只发送到绑定邮箱并只走 SMTP;SMTP 未启用时明确返回配置错误。
|
||||
- [x] 增加系统通知只走 SMTP、HTTP-only 拒绝和系统发件身份保持不变的回归测试;后端全量测试、`go vet`、`go build` 和 `git diff --check` 通过。
|
||||
- [~] 待提交本轮代码并构建正式 Linux 候选包,随后使用密码 SSH 部署到两台真实服务器,复测 Smail SMTP 验证码和密码重置闭环。
|
||||
- [~] P0-10 后置 HTTPS 失败自动恢复、进程崩溃事务接管、多目标部分失败和重试幂等仍需负向实机证据。
|
||||
- [!] 三数据库矩阵、Windows 服务、清库后首次初始化、公网 DNS/HTTPS/SNI/ACME、备份恢复和最终生产门禁仍未完成。
|
||||
- [!] 总体验收继续保持 **P0-03:完成,等待复审**,本轮不标记 `[✓]`。
|
||||
|
||||
## 当前验收总览(2026-09-30 17:39 UTC,恢复标准 Nginx 重载命令后正式页面复测)
|
||||
|
||||
- [x] 已通过正式服务器配置页面将临时测试用的重载命令 `/usr/bin/true` 恢复为标准 Nginx 参数 `nginx`、`-s`、`reload`,并保存成功。
|
||||
|
||||
Loading…
Reference in New Issue
Block a user