gateway-auth-refresh-wiring.test.mjs
105 lines 5.5 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Structural wiring tests — proves the hosted gateway actually mounts the persistent-session
3 * machinery (refresh-token rotation + HttpOnly cookie + real logout) and provisions an
4 * eventual-consistency blob for the auth store. These guard the integration points; the
5 * behavioral guarantees live in the refresh-token-core, gateway-refresh-token-store, and
6 * auth-session suites.
7 */
8
9 import { test, describe } from 'node:test';
10 import assert from 'node:assert/strict';
11 import fs from 'node:fs';
12 import path from 'node:path';
13 import { fileURLToPath } from 'node:url';
14
15 const __dirname = path.dirname(fileURLToPath(import.meta.url));
16 const ROOT = path.resolve(__dirname, '..');
17
18 describe('hosted gateway wires persistent sessions', () => {
19 let src;
20 const load = () => {
21 if (!src) src = fs.readFileSync(path.join(ROOT, 'hub/gateway/server.mjs'), 'utf8');
22 return src;
23 };
24 const routeBlock = (s, route) => {
25 const start = s.indexOf(route);
26 assert.ok(start > 0, `${route} route exists`);
27 const nextRoute = s.indexOf('\napp.', start + route.length);
28 return s.slice(start, nextRoute > 0 ? nextRoute : s.length);
29 };
30
31 test('imports auth-session helpers and the gateway refresh store', () => {
32 const s = load();
33 assert.ok(s.includes("from '../auth-session.mjs'"), 'must import auth-session helpers');
34 assert.ok(s.includes("from './refresh-token-store.mjs'"), 'must import the gateway refresh store');
35 });
36
37 test('mounts POST /api/v1/auth/refresh and /api/v1/auth/logout before the proxies', () => {
38 const s = load();
39 assert.ok(/app\.post\(\s*'\/api\/v1\/auth\/refresh'/.test(s), 'must mount POST /auth/refresh');
40 assert.ok(/app\.post\(\s*'\/api\/v1\/auth\/logout'/.test(s), 'must mount POST /auth/logout');
41 // Auth routes must be registered before any bridge/canister proxy so they are handled locally.
42 const refreshIdx = s.indexOf("'/api/v1/auth/refresh'");
43 const proxyIdx = s.indexOf('proxyTo(');
44 assert.ok(refreshIdx > 0 && (proxyIdx === -1 || refreshIdx < proxyIdx), 'auth routes precede proxies');
45 });
46
47 test('answers OPTIONS preflight for the credentialed auth routes', () => {
48 const s = load();
49 assert.ok(/app\.options\(\[\s*'\/api\/v1\/auth\/refresh'/.test(s), 'must handle OPTIONS preflight');
50 });
51
52 test('refresh route uses createRefreshHandler with a sub-only access-token signer', () => {
53 const s = load();
54 const block = s.slice(s.indexOf("'/api/v1/auth/refresh'"), s.indexOf("'/api/v1/auth/refresh'") + 400);
55 assert.ok(block.includes('createRefreshHandler'), 'refresh route must use createRefreshHandler');
56 assert.ok(block.includes('issueAccessTokenForSub'), 'refresh route must re-mint from sub alone');
57 });
58
59 test('both OAuth callbacks issue a refresh cookie before redirect', () => {
60 const s = load();
61 const google = routeBlock(s, "'/auth/callback/google'");
62 const github = routeBlock(s, "'/auth/callback/github'");
63 assert.ok(google.includes('issueRefreshCookieSafe'), 'google callback issues cookie');
64 assert.ok(github.includes('issueRefreshCookieSafe'), 'github callback issues cookie');
65 });
66
67 test('cookie policy adapts SameSite to cross-origin deployments', () => {
68 const s = load();
69 const block = s.slice(s.indexOf('function refreshCookiePolicy'), s.indexOf('function refreshCookiePolicy') + 400);
70 assert.ok(block.includes("'none'") && block.includes("'lax'"), 'policy chooses None (cross-origin) vs Lax (same-origin)');
71 });
72
73 test('logout uses createLogoutHandler (server-side revocation)', () => {
74 const s = load();
75 // The first occurrence is the OPTIONS preflight array; the POST route is the later one.
76 const postIdx = s.lastIndexOf("'/api/v1/auth/logout'");
77 const block = s.slice(postIdx, postIdx + 300);
78 assert.ok(block.includes('createLogoutHandler'), 'logout must use createLogoutHandler');
79 });
80
81 test('refresh-cookie issuance logs success and surfaces failures (no silent swallow)', () => {
82 const s = load();
83 const start = s.indexOf('async function issueRefreshCookieSafe');
84 assert.ok(start > 0, 'issueRefreshCookieSafe must exist');
85 const block = s.slice(start, start + 1200);
86 // Must log a real error in the catch, not swallow it with a bare noop.
87 assert.ok(/catch\s*\(\s*err\s*\)/.test(block), 'catch must bind the error');
88 assert.ok(block.includes('console.error'), 'a refresh-store write failure must be logged');
89 assert.ok(block.includes('authBlobPresent'), 'failure log must record whether the auth blob was provisioned');
90 assert.doesNotMatch(block, /catch\s*\(\s*_\s*\)\s*\{\s*\/\/[^\n]*\n\s*\}/, 'must not silently swallow the error');
91 });
92
93 test('Netlify function provisions the gateway-auth blob (eventual consistency) and cleans it up', () => {
94 const fn = fs.readFileSync(path.join(ROOT, 'netlify/functions/gateway.mjs'), 'utf8');
95 assert.ok(fn.includes("name: 'gateway-auth'"), 'must provision the gateway-auth store');
96 // Strong consistency is unavailable in Lambda-compat mode (no uncachedEdgeURL → BlobsConsistencyError),
97 // so the auth store must use eventual consistency like billing. See refresh-token-store.mjs.
98 assert.ok(
99 /name: 'gateway-auth',\s*consistency:\s*'eventual'/.test(fn),
100 'auth store must use eventual consistency (strong is unsupported in Lambda-compat mode)',
101 );
102 assert.doesNotMatch(fn, /name: 'gateway-auth',\s*consistency:\s*'strong'/, 'must not request strong consistency');
103 assert.ok(fn.includes('delete globalThis.__knowtation_gateway_auth_blob'), 'must clean up the global');
104 });
105 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago