refactor(ui): inject the session server-side instead of fetching it client-side
The pages are rendered by the server, which already holds the session — so /fingerprint, /cropper and the landing now embed window.__SESSION__ (JSON, with '<' escaped) instead of each fetching /auth/session on load: - /fingerprint gets token, identity, agentUrl and the permitted domains (the same cached context the endpoint returned); /cropper gets identity - landing nav is now server-rendered (Sign in vs user chip + Sign out); its client fetch script is deleted - per-user HTML is sent with Cache-Control: no-store and Vary: Cookie - express.static runs with index:false so '/' can be session-aware while assets stay session-free - GET /auth/session remains as the documented JSON contract (and the canonical place the domain-filter caching is exercised in tests) Client code shrinks by a fetch + error path per page; 3 new tests (injection, escaping, landing header) — 43 total.
This commit is contained in:
@@ -230,7 +230,9 @@ test('crop gate: permitted operator loads the page; denied operator does not', a
|
||||
const cookies = await login(ok.base);
|
||||
const page = await fetch(`${ok.base}/cropper`, { headers: { cookie: cookies } });
|
||||
assert.equal(page.status, 200);
|
||||
assert.match(await page.text(), /Smart Photo Editor|Image Cropper/);
|
||||
const html = await page.text();
|
||||
assert.match(html, /Smart Photo Editor|Image Cropper/);
|
||||
assert.match(html, /window\.__SESSION__ = \{/, 'session injected server-side');
|
||||
} finally {
|
||||
await ok.stop();
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from './helpers.js';
|
||||
|
||||
// Fake YOUreka portal: web-auth session + openfga verify.
|
||||
async function startFakePortal({ cookieStatus = 'Activated', allowed = true, authErrorMessage } = {}) {
|
||||
async function startFakePortal({ cookieStatus = 'Activated', allowed = true, authErrorMessage, firstName = 'Ada' } = {}) {
|
||||
return startServer((req, res) => {
|
||||
let raw = '';
|
||||
req.on('data', (c) => (raw += c));
|
||||
@@ -35,7 +35,7 @@ async function startFakePortal({ cookieStatus = 'Activated', allowed = true, aut
|
||||
status: cookieStatus,
|
||||
cookie: makePbCookie(makeJwt({ expiresInSec: 3600 })),
|
||||
phone: '+2348068287222',
|
||||
firstName: 'Ada',
|
||||
firstName,
|
||||
lastName: 'Obi',
|
||||
});
|
||||
}
|
||||
@@ -280,3 +280,50 @@ test('upstream error text is HTML-escaped on the error page', async () => {
|
||||
await stop();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
test('pages receive the session injected server-side (no client fetch, no-store)', async () => {
|
||||
const { base, stop } = await bootApp();
|
||||
try {
|
||||
const cookies = await login(base);
|
||||
const res = await fetch(`${base}/fingerprint`, { headers: { cookie: cookies } });
|
||||
assert.equal(res.status, 200);
|
||||
assert.match(res.headers.get('cache-control') || '', /no-store/);
|
||||
const html = await res.text();
|
||||
assert.match(html, /window\.__SESSION__ = \{/);
|
||||
assert.match(html, /2348068287222/);
|
||||
assert.match(html, /"domains"/, 'permitted domains injected too');
|
||||
} finally {
|
||||
await stop();
|
||||
}
|
||||
});
|
||||
|
||||
test('injected session values cannot break out of the script tag', async () => {
|
||||
const { base, stop } = await bootApp({}, { firstName: 'Ada</script><script>alert(1)</script>' });
|
||||
try {
|
||||
const cookies = await login(base);
|
||||
const html = await (await fetch(`${base}/fingerprint`, { headers: { cookie: cookies } })).text();
|
||||
const injected = html.slice(html.indexOf('window.__SESSION__'), html.indexOf('</head>'));
|
||||
const payload = injected.slice(injected.indexOf('= ') + 2, injected.indexOf(';</script>'));
|
||||
assert.ok(!payload.includes('</script>'), 'raw </script> must not appear inside the injected JSON');
|
||||
assert.match(payload, /\\u003c/, 'markup is escaped in the payload');
|
||||
} finally {
|
||||
await stop();
|
||||
}
|
||||
});
|
||||
|
||||
test('landing header is server-rendered for the session', async () => {
|
||||
const { base, stop } = await bootApp();
|
||||
try {
|
||||
const anon = await (await fetch(`${base}/`)).text();
|
||||
assert.match(anon, /href="\/auth\/login">Sign in</);
|
||||
assert.ok(!anon.includes('<!--session-nav-->'), 'marker is replaced');
|
||||
|
||||
const cookies = await login(base);
|
||||
const authed = await (await fetch(`${base}/`, { headers: { cookie: cookies } })).text();
|
||||
assert.match(authed, /Ada/);
|
||||
assert.match(authed, /href="\/auth\/logout">Sign out</);
|
||||
} finally {
|
||||
await stop();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user