Files
ced4568 face0ae132 test(phase3a): add identity and RBAC end-to-end coverage
New e2e-phase3a.js (21 cases): no account enumeration, CSRF positive/negative, login rate
limiting, RBAC across all three roles, cohort-scoped instructor access (visibility + route
access), last-owner protection, live role/status re-evaluation (no separate revoke needed),
password change (old password rejected after, other sessions revoked), audit log content
(including a no-secrets-in-metadata assertion), student session RBAC/logout, and session
persistence across a REAL docker compose restart api mid-test.

New session-helper.js — a shared cookie-jar test helper (Node's fetch doesn't persist
cookies across calls) that also auto-attaches X-CSRF-Token from whichever rn_csrf_*
cookie it's holding, replacing the single-cookie-string trackers e2e-phase2a.js and
e2e-phase2b.js each had their own copy of.

e2e-phase2a.js and e2e-phase2b.js updated for the new auth model: admin login is now a real
bootstrapped OWNER_EMAIL/OWNER_PASSWORD account instead of the old shared ADMIN_PASSWORD.
All cases still passing: 10/10 and 12/12 respectively.
2026-08-18 13:36:45 -05:00

54 lines
2.2 KiB
JavaScript

// Shared cookie-jar test helper for the e2e suites (Phase 2A/2B/3A). Node's fetch doesn't
// persist cookies across calls the way a browser does, and Phase 3A's CSRF double-submit
// pattern means every state-changing request needs an X-CSRF-Token header echoing whichever
// rn_csrf_* cookie the jar is currently holding — this centralizes both instead of repeating
// slightly-different versions in every test file.
function makeSession(baseUrl) {
const jar = new Map(); // cookie name -> value
function cookieHeader() {
return [...jar].map(([k, v]) => `${k}=${v}`).join('; ');
}
function csrfToken() {
return jar.get('rn_csrf_s') || jar.get('rn_csrf_i') || '';
}
function absorbSetCookie(res) {
const cookies = typeof res.headers.getSetCookie === 'function' ? res.headers.getSetCookie() : [];
for (const c of cookies) {
const [pair] = c.split(';');
const eq = pair.indexOf('=');
if (eq === -1) continue;
const name = pair.slice(0, eq).trim();
const value = pair.slice(eq + 1).trim();
if (value === '') jar.delete(name); else jar.set(name, value);
}
}
return {
async request(method, path, body) {
const headers = { 'Content-Type': 'application/json' };
const cookies = cookieHeader();
if (cookies) headers.Cookie = cookies;
if (!['GET', 'HEAD'].includes(method)) headers['X-CSRF-Token'] = csrfToken();
const res = await fetch(`${baseUrl}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined });
absorbSetCookie(res);
const text = await res.text();
let json; try { json = JSON.parse(text); } catch { json = text; }
return { status: res.status, body: json };
},
rawFetch(path) {
const cookies = cookieHeader();
return fetch(`${baseUrl}${path}`, { headers: cookies ? { Cookie: cookies } : {} });
},
hasCookie(name) { return jar.has(name); },
getCookie(name) { return jar.get(name); },
// Lets a test simulate "this browser was never issued a CSRF cookie" or "attach the wrong
// one" for a negative CSRF test, without hand-rolling a second fetch call.
setCookie(name, value) { jar.set(name, value); },
clearCookie(name) { jar.delete(name); },
};
}
module.exports = { makeSession };