Files
HomeDash/admin-server/gateway_import_test.go
HomeDash Bot b99319b38b chore(sanitize): 备案号与域名一律可配置,仓库不再预置真实域名/备案号/内网地址
用户要求:备案号与域名都应当可配置,仓库内容要脱敏。

功能(不只是替换字面量):
- 被代理页面注入的备案号改为读后台「站点信息」的 icp/gongan,**未配置就不注入**;
  原先写死具体备案号,等于每个部署者都会把别人的备案信息印到自己页面上
- 注入内容做 nginx 安全处理(去引号/反斜杠/换行/美元符),并有测试覆盖
- auth.go:PORTAL_COOKIE_DOMAIN / PORTAL_HOME_URL 不再预置域名,
  cookie 默认 host-only、首页默认相对路径 "/"
- gateway.go:默认 loginUrl 不再预置域名(受保护路由缺配置会明确报错,不静默生成跳不出去的登录页)
- 首装脚本在给了 NAVI_DOMAIN 时自动注入 PORTAL_COOKIE_DOMAIN/PORTAL_HOME_URL,
  避免"去掉写死默认值"导致跨子域单点登录失效

脱敏:内网 IP(10.0.0.10)与真实备案号清零;真实域名仅剩门户页内容 8 处(待定);
测试夹具/示例/错误页/脚本/后台占位提示全部通用化。

README:新增「跨子域单点登录需要的两个环境变量」;环境变量表改为 NAVI_HOST 必填。
测试:新增备案号注入三条(用配置、未配置不注入、危险字符清理)。
2026-09-12 10:10:52 +08:00

148 lines
4.1 KiB
Go

package main
import (
"os"
"strings"
"testing"
)
const sampleConf = `map $host $backend {
hostnames;
default "";
gitea.example.com http://127.0.0.1:3000;
login.example.com http://127.0.0.1:4001;
dashy.example.com http://127.0.0.1:3003;
modulerouter.example.com http://10.0.0.5:8081;
}
server {
listen 3080 ssl;
server_name example.com www.example.com;
root /usr/share/nginx/html;
error_page 404 /404.html;
}
server {
listen 3080 ssl;
server_name gitea.example.com;
location / {
sub_filter '</footer>' 'x';
proxy_pass $backend; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
proxy_ssl_verify off;
}
}
server {
listen 3080 ssl;
server_name login.example.com;
location / { proxy_pass $backend; }
}
server {
listen 3080 ssl;
server_name modulerouter.example.com dify.example.com;
location / {
postpone_output 0; proxy_buffering off;
proxy_pass $backend;
}
}
server {
listen 3080 ssl;
server_name dashy.example.com;
auth_request /_auth;
error_page 401 = @login;
location @login { return 302 https://login.example.com/login; }
location /search { auth_request off; proxy_pass $backend; }
location ~ ^/(api|autocompleter|static)/ { auth_request off; proxy_pass $backend; }
location / { proxy_pass $backend; }
}
server {
listen 3080 ssl default_server;
server_name _;
root /usr/share/nginx/html;
location / { return 404; }
}
`
func TestParseNginxConf(t *testing.T) {
routes, err := parseNginxConf(sampleConf)
if err != nil {
t.Fatalf("解析失败: %v", err)
}
byHost := map[string]Route{}
for _, r := range routes {
byHost[r.Host] = r
}
if _, ok := byHost["_"]; ok {
t.Fatalf("不应把默认段当成服务")
}
d, ok := byHost["dashy.example.com"]
if !ok || !d.RequireLogin || d.Upstream != "http://127.0.0.1:3003" {
t.Fatalf("dashy 解析不对: %+v", d)
}
if len(d.BypassPaths) == 0 {
t.Fatalf("dashy 应解析出免认证路径: %+v", d.BypassPaths)
}
g := byHost["gitea.example.com"]
if g.RequireLogin || !g.InjectBeian || !g.InsecureTLS || !g.Websocket {
t.Fatalf("gitea 解析不对: %+v", g)
}
m := byHost["modulerouter.example.com"]
if m.RequireLogin || !m.Streaming {
t.Fatalf("modulerouter 应解析为免认证 + 流式: %+v", m)
}
if _, ok := byHost["dify.example.com"]; !ok {
t.Fatalf("同一段里多个域名都要生成路由")
}
if l := byHost["login.example.com"]; l.Kind != "auth" || l.Upstream != "" {
t.Fatalf("login 应为内置 auth: %+v", l)
}
if p := byHost["example.com"]; p.Kind != "portal" {
t.Fatalf("静态门户段应为 portal: %+v", p)
}
for _, r := range routes {
if r.NavEnabled {
t.Fatalf("导入不应默认接管导航: %+v", r)
}
}
}
func TestParseNginxConfRejectsGarbage(t *testing.T) {
if _, err := parseNginxConf(" "); err == nil {
t.Fatalf("空内容应报错")
}
if _, err := parseNginxConf("hello world"); err == nil {
t.Fatalf("没有任何 server_name 应报错")
}
}
func TestPortalAuthStore(t *testing.T) {
dir := t.TempDir()
oldConf := confFile
confFile = dir + "/conf.yml"
defer func() { confFile = oldConf }()
if err := savePortalAuthFile("jianf", "s3cret-pass"); err != nil {
t.Fatalf("保存失败: %v", err)
}
raw, err := os.ReadFile(portalAuthPath())
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(raw), "s3cret-pass") {
t.Fatalf("不得写入明文口令: %s", raw)
}
if !checkPortalCredentials("jianf", "s3cret-pass") {
t.Fatalf("正确口令应通过")
}
if checkPortalCredentials("jianf", "wrong") || checkPortalCredentials("other", "s3cret-pass") {
t.Fatalf("错误口令/用户名不得通过")
}
if err := savePortalAuthFile("admin2", "another-pass"); err != nil {
t.Fatal(err)
}
if checkPortalCredentials("jianf", "s3cret-pass") || !checkPortalCredentials("admin2", "another-pass") {
t.Fatalf("改账号后校验未按新值")
}
user, source := portalAuthConfigured()
if user != "admin2" || !strings.Contains(source, "数据卷") {
t.Fatalf("配置来源不对: %s %s", user, source)
}
}