phase8-p1b-local-login-flow.test.mjs
97 lines 3.3 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Phase 8 P1b — integration tier: bootstrap → login → session → roles (§9).
3 */
4
5 import { describe, test, before, after } from 'node:test';
6 import assert from 'node:assert/strict';
7 import jwt from 'jsonwebtoken';
8 import {
9 makeTempDataDir,
10 enableOfflineLockedTestEnv,
11 createLocalAuthTestApp,
12 bootstrapTestAdmin,
13 TEST_PASSPHRASE,
14 TEST_SECRET,
15 } from '../helpers/phase8-p1b-local-auth-harness.mjs';
16 import { readRolesObject } from '../../hub/roles.mjs';
17
18 function bridgeEffectiveRole(uid, storedRoles) {
19 const VALID = new Set(['admin', 'editor', 'viewer', 'evaluator']);
20 if (!uid) return 'member';
21 const stored = storedRoles && storedRoles[uid];
22 if (stored && VALID.has(stored)) return stored;
23 return 'member';
24 }
25
26 describe('phase8-p1b local login flow (integration)', () => {
27 /** @type {{ dataDir: string, cleanup: () => void }} */
28 let tmp;
29 /** @type {{ baseUrl: string, close: () => Promise<void> } | null} */
30 let hub = null;
31
32 before(async () => {
33 tmp = await makeTempDataDir();
34 enableOfflineLockedTestEnv(tmp.dataDir);
35 await bootstrapTestAdmin(tmp.dataDir);
36 hub = await createLocalAuthTestApp(tmp.dataDir);
37 });
38
39 after(async () => {
40 if (hub) await hub.close();
41 tmp.cleanup();
42 delete process.env.KNOWTATION_OFFLINE_LOCKED_AUTH;
43 delete process.env.KNOWTATION_OFFLINE_LOCKED_AUTH_TEST_SHIPPED;
44 });
45
46 test('OAuth routes return 403 when gate on', async () => {
47 for (const route of ['/api/v1/auth/login', '/auth/login']) {
48 const res = await fetch(`${hub.baseUrl}${route}`);
49 assert.equal(res.status, 403);
50 const body = await res.json();
51 assert.equal(body.code, 'OAUTH_DISABLED');
52 }
53 });
54
55 test('providers reports local only', async () => {
56 const res = await fetch(`${hub.baseUrl}/api/v1/auth/providers`);
57 const body = await res.json();
58 assert.deepEqual(body, { google: false, github: false, local: true });
59 });
60
61 test('POST login → JWT → session → roles', async () => {
62 const loginRes = await fetch(`${hub.baseUrl}/api/v1/auth/local/login`, {
63 method: 'POST',
64 headers: { 'Content-Type': 'application/json' },
65 body: JSON.stringify({ username: 'admin', passphrase: TEST_PASSPHRASE }),
66 });
67 assert.equal(loginRes.status, 200);
68 const { token, expiresIn } = await loginRes.json();
69 assert.ok(token);
70 assert.equal(expiresIn, '24h');
71
72 const sessionRes = await fetch(`${hub.baseUrl}/api/v1/auth/session`, {
73 headers: { Authorization: `Bearer ${token}` },
74 });
75 assert.equal(sessionRes.status, 200);
76 const session = await sessionRes.json();
77 assert.equal(session.sub, 'local:admin_001');
78 assert.equal(session.provider, 'local');
79 assert.equal(session.role, 'admin');
80
81 const rolesRes = await fetch(`${hub.baseUrl}/api/v1/roles`, {
82 headers: { Authorization: `Bearer ${token}` },
83 });
84 assert.equal(rolesRes.status, 200);
85 const rolesBody = await rolesRes.json();
86 assert.equal(rolesBody.roles['local:admin_001'], 'admin');
87
88 const payload = jwt.verify(token, TEST_SECRET);
89 assert.equal(payload.sub, 'local:admin_001');
90 });
91
92 test('bridge effectiveRole resolves local sub', () => {
93 const roles = readRolesObject(tmp.dataDir);
94 const role = bridgeEffectiveRole('local:admin_001', roles);
95 assert.equal(role, 'admin');
96 });
97 });
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago