fix: 遵循用户发件人原样配置不篡改发信地址并升级版本号至v1.52.3
Some checks failed
Some checks failed
This commit is contained in:
parent
206c90451c
commit
7d83fbc093
16
README.md
16
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 .
|
||||
|
||||
@ -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" {
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
package buildinfo
|
||||
|
||||
const Version = "1.52.2"
|
||||
const Version = "1.52.3"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:<notification+system@example.test>") {
|
||||
if !strings.HasPrefix(envelope, "MAIL FROM:<scert@example.test>") {
|
||||
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("验è¯<EFBFBD>ç <EFBFBD>ä¸<EFBFBD>å¾—å<EFBFBD>‘é€<EFBFBD>给普通通知收件äº?)
|
||||
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 系统通知数é‡<EFBFBD>错误ï¼?d", len(server.messages))
|
||||
t.Fatalf("SMTP 系统通知数量错误:%d", len(server.messages))
|
||||
}
|
||||
}
|
||||
|
||||
@ -449,7 +450,7 @@ func TestBuildSMTPMessageIncludesStyledHTMLAlternative(t *testing.T) {
|
||||
"<p style=\"color:red\">HTML 通知</p>",
|
||||
} {
|
||||
if !strings.Contains(message, value) {
|
||||
t.Fatalf("SMTP multipart 邮件缺少 %q�s", value, message)
|
||||
t.Fatalf("SMTP multipart 邮件缺少 %q:%s", value, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@ -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"
|
||||
},
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -85,7 +85,7 @@ defineEmits(["select", "settings"]);
|
||||
<div v-if="currentUser" class="navigation-account-card" aria-label="当前登录账号">
|
||||
<div class="navigation-account-header">
|
||||
<span class="navigation-account-label">当前账号</span>
|
||||
<span class="navigation-version-label">版本 {{ version ? (version.startsWith("v") || version.startsWith("V") ? version : "V" + version) : "V1.52.2" }}</span>
|
||||
<span class="navigation-version-label">版本 {{ version ? (version.startsWith("v") || version.startsWith("V") ? version : "V" + version) : "V1.52.3" }}</span>
|
||||
</div>
|
||||
<div class="navigation-account-row">
|
||||
<strong class="navigation-account-name" :title="currentUser.account">{{ currentUser.account || "未命名账号" }}</strong>
|
||||
|
||||
@ -60,7 +60,7 @@ test("系统设置导航仅对当前已确认的超级管理员显示", async ()
|
||||
const html = await renderComponent("../Dashboard/DashboardNavigation.vue", {
|
||||
activeSection: "overview", sections: [],
|
||||
currentUser: role ? { account: "测试", role } : null,
|
||||
version: "1.52.2",
|
||||
version: "1.52.3",
|
||||
roleLabels: workspaceRoleLabels,
|
||||
loggingOut: false, error: "", logout() {},
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user