原 portal-auth 的登录页有 prefers-color-scheme 检测 + 手动切换 + localStorage 记忆;我把它换成内置登录服务时只写了亮色,自动深浅色因此消失。 - 默认跟随系统(prefers-color-scheme),手动切换后写入 localStorage - 深浅两套 CSS 变量(背景/卡片/边框/文字/输入框/错误提示/阴影) - 右上角切换按钮,补 aria-label - 加 color-scheme + prefers-reduced-motion - 测试:断言主题检测、切换按钮、no-store;并断言 redirect 被转义(注入防护)
55 lines
1.6 KiB
Go
55 lines
1.6 KiB
Go
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/\"><script>x</script>", nil)
|
||
handlePortalLogin(rec, req)
|
||
body := rec.Body.String()
|
||
if strings.Contains(body, "<script>x</script>") {
|
||
t.Fatalf("redirect 未转义,存在注入:%s", body)
|
||
}
|
||
}
|