From 7d83fbc09357ad6c06b7c4cd4ee017f9b65f1d2c Mon Sep 17 00:00:00 2001 From: xuans Date: Sun, 18 Oct 2026 22:08:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=81=B5=E5=BE=AA=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=8F=91=E4=BB=B6=E4=BA=BA=E5=8E=9F=E6=A0=B7=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=B8=8D=E7=AF=A1=E6=94=B9=E5=8F=91=E4=BF=A1=E5=9C=B0=E5=9D=80?= =?UTF-8?q?=E5=B9=B6=E5=8D=87=E7=BA=A7=E7=89=88=E6=9C=AC=E5=8F=B7=E8=87=B3?= =?UTF-8?q?v1.52.3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++++---- .../internal/bootstrap/notification_test.go | 2 +- backend/internal/buildinfo/version.go | 2 +- .../internal/config/notification_sender.go | 37 ++++++++++++++----- .../config/notification_sender_test.go | 15 ++++---- backend/internal/notification/smtp_test.go | 13 ++++--- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- .../views/Dashboard/DashboardNavigation.vue | 2 +- .../UserManagement/UserWorkspace.test.js | 2 +- 10 files changed, 57 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index ccd46d1..64e1b39 100644 --- a/README.md +++ b/README.md @@ -143,11 +143,11 @@ go build -trimpath -o ../bin/scert-admin ./cmd/scert-admin 项目配备统一的打包脚本 `deploy/package.mjs`,产物严格遵循 **`项目名+版本+架构`** 的命名规范: ```bash -# 打包 Linux amd64 归档包 (生成 Scert_v1.52.2_linux_amd64.tar.gz) -node deploy/package.mjs --target=linux_amd64 --version=v1.52.2 +# 打包 Linux amd64 归档包 (生成 Scert_v1.52.3_linux_amd64.tar.gz) +node deploy/package.mjs --target=linux_amd64 --version=v1.52.3 -# 打包 Windows amd64 压缩包 (生成 Scert_v1.52.2_windows_amd64.zip) -node deploy/package.mjs --target=windows_amd64 --version=v1.52.2 +# 打包 Windows amd64 压缩包 (生成 Scert_v1.52.3_windows_amd64.zip) +node deploy/package.mjs --target=windows_amd64 --version=v1.52.3 ``` 发布包内包含: @@ -157,7 +157,7 @@ node deploy/package.mjs --target=windows_amd64 --version=v1.52.2 - `systemd/` 与 `nginx/` 配置示例模板 - `SHA256SUMS` 完整性校验文件 -推送版本标签(例如 `git tag v1.52.2 && git push Scert v1.52.2`)后,GitHub 与 Gitea 会自动触发 Actions 矩阵编译,生成各架构的发布包并自动挂载至 Release 资产清单中。 +推送版本标签(例如 `git tag v1.52.3 && git push Scert v1.52.3`)后,GitHub 与 Gitea 会自动触发 Actions 矩阵编译,生成各架构的发布包并自动挂载至 Release 资产清单中。 --- @@ -167,8 +167,8 @@ node deploy/package.mjs --target=windows_amd64 --version=v1.52.2 1. 从 Release 页面下载对应架构的发布包并解压: ```bash - tar -zxvf Scert_v1.52.2_linux_amd64.tar.gz - cd Scert_v1.52.2_linux_amd64 + tar -zxvf Scert_v1.52.3_linux_amd64.tar.gz + cd Scert_v1.52.3_linux_amd64 ``` 2. 执行安装脚本: @@ -188,7 +188,7 @@ node deploy/package.mjs --target=windows_amd64 --version=v1.52.2 ### Windows -1. 解压 `Scert_v1.52.2_windows_amd64.zip`。 +1. 解压 `Scert_v1.52.3_windows_amd64.zip`。 2. 以管理员权限启动 PowerShell 并执行: ```powershell powershell -ExecutionPolicy Bypass -File .\install-windows.ps1 -SourceDirectory . diff --git a/backend/internal/bootstrap/notification_test.go b/backend/internal/bootstrap/notification_test.go index e7aa6d5..9528bd1 100644 --- a/backend/internal/bootstrap/notification_test.go +++ b/backend/internal/bootstrap/notification_test.go @@ -59,7 +59,7 @@ func TestCompleterStoresNotificationCredentialsWithoutPlaintext(t *testing.T) { t.Fatalf("通知选项未完整保存:%+v", stored.Notification) } httpCredentials, err := fixture.store.Get(t.Context(), stored.Notification.HTTPCredentialRef) - if stored.Notification.SenderEmail != "notification+system@example.test" || stored.Notification.SenderName != "系统通知" { + if stored.Notification.SenderEmail != "smtp-owner@example.test" || stored.Notification.SenderName != "Scert" { t.Fatal("初始化没有保存 Smail 系统通知发件人") } if err != nil || httpCredentials["password"] != "private-http-password" { diff --git a/backend/internal/buildinfo/version.go b/backend/internal/buildinfo/version.go index 505ac8b..702bcf6 100644 --- a/backend/internal/buildinfo/version.go +++ b/backend/internal/buildinfo/version.go @@ -1,3 +1,3 @@ package buildinfo -const Version = "1.52.2" +const Version = "1.52.3" diff --git a/backend/internal/config/notification_sender.go b/backend/internal/config/notification_sender.go index aa053f8..6256d67 100644 --- a/backend/internal/config/notification_sender.go +++ b/backend/internal/config/notification_sender.go @@ -11,13 +11,26 @@ func (c NotificationConfig) UsesSmailSystemAccount() bool { return strings.HasPrefix(strings.ToLower(strings.TrimSpace(c.SMTPUsername)), "notify:") } -// SenderIdentity 根据系统通知账号确定发件人,不能将管理员邮箱当作 SMTP 发件人。 +// SenderIdentity 根据用户配置或通知账号确定发件人。 +// 遵循“用户怎么填就用什么”的原则,原样使用用户填写的发件人或发信账号,不擅自拼接或篡改发信地址。 func (c NotificationConfig) SenderIdentity() (string, string, error) { name := strings.TrimSpace(c.SenderName) email := strings.TrimSpace(c.SenderEmail) if !c.SMTPEnabled { return name, email, nil } + // 如果用户明确填写了发件人邮箱,则原样优先使用 + if email != "" { + parsed, err := mail.ParseAddress(email) + if err != nil || parsed.Address != email { + return "", "", errors.New("发件人邮箱地址格式无效") + } + if name == "" { + name = "Scert" + } + return name, email, nil + } + username := strings.ToLower(strings.TrimSpace(c.SMTPUsername)) if c.UsesSmailSystemAccount() { account := strings.TrimPrefix(username, "notify:") @@ -27,15 +40,19 @@ func (c NotificationConfig) SenderIdentity() (string, string, error) { strings.ContainsAny(account, "\r\n<>()") { return "", "", errors.New("Smail 系统通知用户名必须使用 notify:账号@邮件域名 格式") } - // Smail 保留该发件地址;不能使用登录账号、管理员邮箱或自定义名称替代。 - return "系统通知", "notification+system@" + parts[1], nil - } - if email == "" { - parsed, err := mail.ParseAddress(username) - if err != nil || parsed.Address != username || parsed.Name != "" { - return "", "", errors.New("请填写 Smail 系统通知用户名;兼容 SMTP 账号必须是完整邮箱地址") + // 用户未单独指定发件人邮箱时,原样使用 notify: 后的账号邮箱地址,发件人名称优先使用用户配置的 SenderName + if name == "" { + name = "系统通知" } - email = username + return name, account, nil } - return name, email, nil + + parsed, err := mail.ParseAddress(username) + if err != nil || parsed.Address != username || parsed.Name != "" { + return "", "", errors.New("请填写 Smail 系统通知用户名;兼容 SMTP 账号必须是完整邮箱地址") + } + if name == "" { + name = "Scert" + } + return name, username, nil } diff --git a/backend/internal/config/notification_sender_test.go b/backend/internal/config/notification_sender_test.go index 26ab456..40490f1 100644 --- a/backend/internal/config/notification_sender_test.go +++ b/backend/internal/config/notification_sender_test.go @@ -4,16 +4,17 @@ import "testing" func TestNotificationSenderIdentity(t *testing.T) { for _, item := range []struct { - name, username, sender, wantName, wantEmail string + name, username, senderName, senderEmail, wantName, wantEmail string }{ - {"system", "notify:scert@example.test", "", "系统通知", "notification+system@example.test"}, - {"stale_admin", "notify:scert@example.test", "admin@other.test", "系统通知", "notification+system@example.test"}, - {"normalized", " NOTIFY:SCERT@EXAMPLE.TEST ", "", "系统通知", "notification+system@example.test"}, - {"smtp_compatibility", "sender@example.test", "", "Scert", "sender@example.test"}, - {"explicit_legacy", "smtp-login", "sender@example.test", "Scert", "sender@example.test"}, + {"system_default_name", "notify:scert@example.test", "", "", "系统通知", "scert@example.test"}, + {"system_custom_name", "notify:scert@example.test", "Scert通知", "", "Scert通知", "scert@example.test"}, + {"system_explicit_sender", "notify:scert@example.test", "Scert", "custom@example.test", "Scert", "custom@example.test"}, + {"normalized", " NOTIFY:SCERT@EXAMPLE.TEST ", "", "", "系统通知", "scert@example.test"}, + {"smtp_compatibility", "sender@example.test", "Scert", "", "Scert", "sender@example.test"}, + {"explicit_legacy", "smtp-login", "Scert", "sender@example.test", "Scert", "sender@example.test"}, } { t.Run(item.name, func(t *testing.T) { - cfg := NotificationConfig{SMTPEnabled: true, SMTPUsername: item.username, SenderName: "Scert", SenderEmail: item.sender} + cfg := NotificationConfig{SMTPEnabled: true, SMTPUsername: item.username, SenderName: item.senderName, SenderEmail: item.senderEmail} name, email, err := cfg.SenderIdentity() if err != nil || name != item.wantName || email != item.wantEmail { t.Fatalf("发件身份不匹配:name=%q email=%q err=%v", name, email, err) diff --git a/backend/internal/notification/smtp_test.go b/backend/internal/notification/smtp_test.go index 36d3aa0..482be5c 100644 --- a/backend/internal/notification/smtp_test.go +++ b/backend/internal/notification/smtp_test.go @@ -286,7 +286,8 @@ func TestSmailSystemSenderUsesSameEnvelopeAndHeader(t *testing.T) { host, port := server.address() cfg := smtpTestConfig(host, port, "starttls") cfg.SMTPUsername = "notify:scert@example.test" - cfg.SenderEmail = "admin@other.test" + cfg.SenderName = "系统通知" + cfg.SenderEmail = "" service := New(cfg, credentialReaderStub{values: map[string]string{"password": "test-only-notification-credential"}}) service.smtpTLSConfig = server.clientTLSConfig var err error @@ -301,15 +302,15 @@ func TestSmailSystemSenderUsesSameEnvelopeAndHeader(t *testing.T) { server.mu.Lock() envelope := server.mailFrom server.mu.Unlock() - if !strings.HasPrefix(envelope, "MAIL FROM:") { + if !strings.HasPrefix(envelope, "MAIL FROM:") { t.Fatal("SMTP 信封没有使用系统通知地址") } message := string(server.message()) - if !strings.Contains(message, formatSMTPAddress(address{Name: "系统通知", Email: "notification+system@example.test"})) || strings.Contains(message, "admin@other.test") { + if !strings.Contains(message, formatSMTPAddress(address{Name: "系统通知", Email: "scert@example.test"})) { t.Fatal("邮件头没有使用固定系统通知身份,或泄漏旧管理员发件地址") } if security && strings.Contains(message, "receiver@example.com") { - t.Fatal("验证码不得发送给普通通知收件äº?) + t.Fatal("验证码不得发送给普通通知收件人") } }) } @@ -340,7 +341,7 @@ func TestSmailSystemNotificationUsesSMTPOnly(t *testing.T) { server.mu.Lock() defer server.mu.Unlock() if len(server.messages) != 1 { - t.Fatalf("SMTP 系统通知数量错误ï¼?d", len(server.messages)) + t.Fatalf("SMTP 系统通知数量错误:%d", len(server.messages)) } } @@ -449,7 +450,7 @@ func TestBuildSMTPMessageIncludesStyledHTMLAlternative(t *testing.T) { "

HTML 通知

", } { if !strings.Contains(message, value) { - t.Fatalf("SMTP multipart 邮件缺少 %qï¼?s", value, message) + t.Fatalf("SMTP multipart 邮件缺少 %q:%s", value, message) } } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d29916f..61712e0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "scert-frontend", - "version": "1.52.2", + "version": "1.52.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scert-frontend", - "version": "1.52.2", + "version": "1.52.3", "dependencies": { "vue": "3.5.41" }, diff --git a/frontend/package.json b/frontend/package.json index 9e5d9b9..665baa0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "scert-frontend", "private": true, - "version": "1.52.2", + "version": "1.52.3", "type": "module", "packageManager": "npm@11.17.0", "engines": { diff --git a/frontend/src/views/Dashboard/DashboardNavigation.vue b/frontend/src/views/Dashboard/DashboardNavigation.vue index 611ac73..5e14e73 100644 --- a/frontend/src/views/Dashboard/DashboardNavigation.vue +++ b/frontend/src/views/Dashboard/DashboardNavigation.vue @@ -85,7 +85,7 @@ defineEmits(["select", "settings"]);