Files
HomeDash/deploy/verify-matrix.js
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

57 lines
3.6 KiB
JavaScript

// 目标环境通过环境变量指定,仓库不预置任何真实域名/主机。
const BASE = process.env.PORTAL_BASE || 'https://dashy.example.com';
const DOM = process.env.PORTAL_COOKIE_DOMAIN || '.example.com';
const { chromium }=require('playwright-core');
function rgb(s){const m=s.match(/[\d.]+/g);return m?m.slice(0,4).map(Number):[]}
function L(s){const [r=0,g=0,b=0]=rgb(s);return Math.round(.2126*r+.7152*g+.0722*b)}
const C=process.env.PORTAL_COOKIE;
(async()=>{
const b=await chromium.connectOverCDP('http://127.0.0.1:9222');
const views=[['/','home'],['/minimal','minimal'],['/workspace','workspace']];
const sizes=[[1280,720],[1024,600],[900,520],[390,780]];
const rows=[]; let bad=0;
for(const [w,h] of sizes){
for(const [path,name] of views){
const ctx=await b.newContext({ignoreHTTPSErrors:true,viewport:{width:w,height:h}});
await ctx.addCookies([{name:'portal_session',value:C,domain:DOM,path:'/',httpOnly:true,secure:true,sameSite:'Lax'}]);
const p=await ctx.newPage(); const errs=[]; p.on('pageerror',e=>errs.push(String(e)));
await p.emulateMedia({colorScheme:'dark'}); await p.addInitScript(()=>localStorage.removeItem('navi-theme'));
await p.goto(BASE+path,{waitUntil:'networkidle',timeout:90000}); await p.waitForTimeout(3200);
const r=await p.evaluate(()=>{
const g=s=>{const e=document.querySelector(s);if(!e)return null;const c=getComputedStyle(e);return {c:c.color,bg:c.backgroundColor}};
const de=document.documentElement;
return {vScroll:de.scrollHeight>innerHeight+2,overflow:de.scrollHeight-innerHeight,
hScroll:de.scrollWidth>innerWidth+2,
title:g('header h1')||g('.minimal-home .title-and-search h1'),
sec:g('div.collapsable .section-header h3')||g('.minimal-headings-row .headings'),
item:g('a.item .text'),desc:g('a.item .description'),
side:g('nav.side-bar .side-bar-item'),content:g('.work-space .web-content'),
footer:g('.navi-footer')};
});
const t=r.title?L(r.title.c):0, s=r.sec?L(r.sec.c):0, it=r.item?L(r.item.c):0, d=r.desc?L(r.desc.c):0;
const side=r.side?L(r.side.bg):null, content=r.content?L(r.content.bg):null;
const foot=r.footer?L(r.footer.bg):null;
const row={size:`${w}x${h}`,view:name,scroll:r.vScroll?'SCROLL':'-',overflow:r.overflow,hScroll:r.hScroll?'HSCROLL':'-',title:t,sec:s,item:it,desc:d,sideBg:side,contentBg:content,footBg:foot,err:errs.length};
const fail=[];
if(r.vScroll && name!=='home')fail.push('滚动+'+r.overflow); // 首页内容长,允许滚动
if(r.hScroll)fail.push('横向滚动');
if(!t)fail.push('未取到标题');
if(t&&t<190)fail.push('深色未生效/标题亮度'+t);
if(it&&it<190)fail.push('卡片字'+it);
if(d&&d<130)fail.push('说明字'+d);
if(side!==null&&side>120)fail.push('侧栏白底'+side);
if(content!==null&&content>120)fail.push('内嵌白底'+content);
if(errs.length)fail.push('异常'+errs.length);
row.result=fail.length?('✗ '+fail.join(' / ')):'✓';
if(fail.length) bad++;
rows.push(row);
if(name!=='home'){ await p.screenshot({path:`/tmp/m-${name}-${w}x${h}.png`}); }
await ctx.close();
}
}
for(const r of rows) console.log(`${r.size.padEnd(9)} ${r.view.padEnd(10)} 滚动=${r.scroll.padEnd(7)} 溢=${String(r.overflow).padStart(4)} 标题=${String(r.title).padStart(3)} 卡片=${String(r.item).padStart(3)} 说明=${String(r.desc).padStart(3)} 侧栏底=${String(r.sideBg).padStart(3)} 内嵌底=${String(r.contentBg).padStart(3)} 备案底=${String(r.footBg).padStart(3)} ${r.result}`);
console.log(bad? `FAILED_ROWS=${bad}` : 'ALL_OK');
await b.close();
if(bad) process.exitCode=2;
})().catch(e=>{console.error(e);process.exit(1)});