From c597fa19913613a210c6c4219ee53e222b4e9f62 Mon Sep 17 00:00:00 2001 From: HomeDash Bot Date: Fri, 11 Sep 2026 23:14:45 +0800 Subject: [PATCH] =?UTF-8?q?fix(auth):=20=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=E8=87=AA=E5=8A=A8=E6=B7=B1=E6=B5=85=E8=89=B2?= =?UTF-8?q?=EF=BC=88=E6=88=91=E5=9C=A8=E9=87=8D=E5=86=99=E6=A8=A1=E6=9D=BF?= =?UTF-8?q?=E6=97=B6=E9=80=A0=E6=88=90=E7=9A=84=E5=9B=9E=E5=BD=92=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原 portal-auth 的登录页有 prefers-color-scheme 检测 + 手动切换 + localStorage 记忆;我把它换成内置登录服务时只写了亮色,自动深浅色因此消失。 - 默认跟随系统(prefers-color-scheme),手动切换后写入 localStorage - 深浅两套 CSS 变量(背景/卡片/边框/文字/输入框/错误提示/阴影) - 右上角切换按钮,补 aria-label - 加 color-scheme + prefers-reduced-motion - 测试:断言主题检测、切换按钮、no-store;并断言 redirect 被转义(注入防护) --- admin-server/auth.go | 29 ++++++++++++++++++--- admin-server/auth_test.go | 54 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 admin-server/auth_test.go diff --git a/admin-server/auth.go b/admin-server/auth.go index 978d243..49ed2fd 100644 --- a/admin-server/auth.go +++ b/admin-server/auth.go @@ -139,9 +139,32 @@ func safePortalRedirect(raw string) string { var portalLoginTemplate = template.Must(template.New("login").Parse(` -统一门户登录

统一门户

登录后访问受保护的内部服务

{{if .Error}}
{{.Error}}
{{end}}
`)) +统一门户登录 + +
+ +

统一门户

登录后访问受保护的内部服务

+{{if .Error}}
{{.Error}}
{{end}} +
+
`)) func renderPortalLogin(w http.ResponseWriter, redirect, errText string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") diff --git a/admin-server/auth_test.go b/admin-server/auth_test.go new file mode 100644 index 0000000..82cc518 --- /dev/null +++ b/admin-server/auth_test.go @@ -0,0 +1,54 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" +) + +// 登录页必须跟随系统深浅色,并提供手动切换(原 portal-auth 的行为, +// 重写模板时曾漏掉)。 +func TestLoginPageThemeSupport(t *testing.T) { + rec := httptest.NewRecorder() + renderPortalLogin(rec, "https://dashy.example.com/", "") + body := rec.Body.String() + for _, want := range []string{ + "prefers-color-scheme:dark", "[data-theme=\"dark\"]", "localStorage.setItem('theme'", + "toggle", "aria-label", + } { + if want == "toggle" { + if !strings.Contains(body, "切换深浅色") { + t.Fatalf("缺少手动切换按钮") + } + continue + } + if !strings.Contains(body, want) { + t.Fatalf("登录页缺少 %q", want) + } + } + if !strings.Contains(rec.Header().Get("Content-Type"), "text/html") { + t.Fatalf("Content-Type 不对") + } + if rec.Header().Get("Cache-Control") != "no-store" { + t.Fatalf("登录页不应被缓存") + } +} + +// redirect 必须被 HTML 转义后写入表单 action,避免注入。 +func TestLoginPageEscapesRedirect(t *testing.T) { + dir := t.TempDir() + oldConf, oldURL := confFile, historyBaseURL + confFile, historyBaseURL = dir+"/conf.yml", "http://127.0.0.1:1" + defer func() { confFile, historyBaseURL = oldConf, oldURL }() + _ = os.WriteFile(confFile, []byte("sections: []\n"), 0o644) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/login?redirect=https://evil.example/\">", nil) + handlePortalLogin(rec, req) + body := rec.Body.String() + if strings.Contains(body, "") { + t.Fatalf("redirect 未转义,存在注入:%s", body) + } +}