fix(engineering-dashboard): read token from URL/meta under CSP
Remove the inline token script blocked by script-src 'self', so the live page no longer calls the snapshot API with token=undefined. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
(function () {
|
||||
const token = window.__DASHBOARD_TOKEN__;
|
||||
function resolveToken() {
|
||||
const fromURL = new URL(window.location.href).searchParams.get("token");
|
||||
if (fromURL) return fromURL;
|
||||
const meta = document.querySelector('meta[name="engineering-dashboard-token"]');
|
||||
const fromMeta = meta && meta.getAttribute("content");
|
||||
if (fromMeta && fromMeta !== "__TOKEN__") return fromMeta;
|
||||
return "";
|
||||
}
|
||||
const token = resolveToken();
|
||||
const state = {
|
||||
snap: null,
|
||||
view: "overview",
|
||||
@@ -25,7 +33,7 @@
|
||||
|
||||
function syncURL() {
|
||||
const u = new URL(window.location.href);
|
||||
u.searchParams.set("token", token);
|
||||
if (token) u.searchParams.set("token", token);
|
||||
u.searchParams.set("view", state.view);
|
||||
if (state.q) u.searchParams.set("q", state.q); else u.searchParams.delete("q");
|
||||
if (state.selectedDoc) u.searchParams.set("doc", state.selectedDoc); else u.searchParams.delete("doc");
|
||||
@@ -44,6 +52,11 @@
|
||||
}
|
||||
|
||||
async function fetchSnapshot(force) {
|
||||
if (!token) {
|
||||
el.status.textContent = "缺少访问令牌";
|
||||
renderHardError("缺少 token:请使用服务启动时打印的完整 URL(含 ?token=)打开页面");
|
||||
return false;
|
||||
}
|
||||
el.status.textContent = state.snap ? "刷新中…" : "首次加载中…";
|
||||
const url = "/api/v1/snapshot?token=" + encodeURIComponent(token) + (force ? "&refresh=1" : "");
|
||||
const headers = {};
|
||||
@@ -281,6 +294,10 @@
|
||||
|
||||
async function loop() {
|
||||
loadURL();
|
||||
if (!token) {
|
||||
renderHardError("缺少 token:请使用服务启动时打印的完整 URL(含 ?token=)打开页面");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const changed = await fetchSnapshot(true);
|
||||
if (changed || state.snap) render();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="engineering-dashboard-token" content="__TOKEN__">
|
||||
<title>个人工程中控台</title>
|
||||
<link rel="stylesheet" href="/assets/app.css?token=__TOKEN__">
|
||||
</head>
|
||||
@@ -37,7 +38,6 @@
|
||||
<div id="content"></div>
|
||||
</main>
|
||||
</div>
|
||||
<script>window.__DASHBOARD_TOKEN__ = "__TOKEN__";</script>
|
||||
<script src="/assets/app.js?token=__TOKEN__"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -128,3 +128,43 @@ func TestServer_CrossOriginDenied(t *testing.T) {
|
||||
t.Fatalf("got %d", rr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_IndexUsesMetaTokenNoInlineScript(t *testing.T) {
|
||||
c := NewCollector(t.TempDir(), &FakeRunner{})
|
||||
c.mu.Lock()
|
||||
c.current = &Snapshot{RevisionID: "r", GenerationID: "g", GeneratedAt: time.Now().UTC()}
|
||||
c.mu.Unlock()
|
||||
srv, err := NewServer(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "/?token="+srv.Token, nil)
|
||||
req.Host = "127.0.0.1:8787"
|
||||
rr := httptest.NewRecorder()
|
||||
srv.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("index: %d %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
body := rr.Body.String()
|
||||
if strings.Contains(body, "window.__DASHBOARD_TOKEN__") {
|
||||
t.Fatal("index must not use inline token script (CSP script-src 'self')")
|
||||
}
|
||||
meta := `name="engineering-dashboard-token" content="` + srv.Token + `"`
|
||||
if !strings.Contains(body, meta) {
|
||||
t.Fatalf("missing token meta: %s", body[:min(400, len(body))])
|
||||
}
|
||||
if !strings.Contains(rr.Header().Get("Content-Security-Policy"), "script-src 'self'") {
|
||||
t.Fatalf("unexpected CSP: %s", rr.Header().Get("Content-Security-Policy"))
|
||||
}
|
||||
// app.js must not reference the removed global
|
||||
js, err := readAsset("assets/app.js")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if strings.Contains(string(js), "__DASHBOARD_TOKEN__") {
|
||||
t.Fatal("app.js still references __DASHBOARD_TOKEN__")
|
||||
}
|
||||
if !strings.Contains(string(js), "engineering-dashboard-token") {
|
||||
t.Fatal("app.js should read meta engineering-dashboard-token")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user