phase8-p1b-local-auth-harness.mjs
114 lines 3.6 KB
Raw
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor ⚠ breaking 10 days ago
1 /**
2 * Shared harness for Phase 8 P1b offline-locked auth tests.
3 */
4
5 import fs from 'fs';
6 import path from 'path';
7 import os from 'os';
8 import express from 'express';
9 import jwt from 'jsonwebtoken';
10 import { registerLocalAuthRoutes } from '../../hub/lib/local-auth-routes.mjs';
11 import { oauthDisabledGuard } from '../../hub/lib/local-auth-oauth-guard.mjs';
12 import { bootstrapAdminCli } from '../../hub/lib/local-auth-bootstrap.mjs';
13
14 export const TEST_PASSPHRASE = 'SecureHub123!Aa';
15 export const TEST_SECRET = 'test-jwt-secret-at-least-32-chars-long';
16 export const TEST_JWT_EXPIRY = '24h';
17
18 /**
19 * @returns {Promise<{ dataDir: string, cleanup: () => void }>}
20 */
21 export async function makeTempDataDir() {
22 const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'p1b-local-auth-'));
23 return {
24 dataDir,
25 cleanup: () => {
26 try {
27 fs.rmSync(dataDir, { recursive: true, force: true });
28 } catch (_) {
29 /* ignore */
30 }
31 },
32 };
33 }
34
35 /**
36 * Enable gate-on test paths without flipping shipped flag.
37 */
38 export function enableOfflineLockedTestEnv(dataDir) {
39 process.env.NODE_ENV = 'test';
40 process.env.KNOWTATION_OFFLINE_LOCKED_AUTH = 'enabled';
41 process.env.KNOWTATION_OFFLINE_LOCKED_AUTH_TEST_SHIPPED = '1';
42 process.env.KNOWTATION_GATEWAY_DATA_DIR = dataDir;
43 process.env.HUB_JWT_SECRET = TEST_SECRET;
44 process.env.SESSION_SECRET = TEST_SECRET;
45 }
46
47 /**
48 * @param {string} dataDir
49 * @returns {Promise<{ app: import('express').Express, baseUrl: string, close: () => Promise<void> }>}
50 */
51 export async function createLocalAuthTestApp(dataDir) {
52 const app = express();
53 app.use(express.json());
54 const offlineLockedActive = true;
55 app.get('/api/v1/auth/providers', (_req, res) => {
56 res.json({ google: false, github: false, local: true });
57 });
58 app.get('/api/v1/auth/login', oauthDisabledGuard(true, dataDir), (_req, res) => {
59 res.status(403).json({ code: 'OAUTH_DISABLED' });
60 });
61 app.get('/auth/login', oauthDisabledGuard(true, dataDir), (_req, res) => {
62 res.status(403).json({ code: 'OAUTH_DISABLED' });
63 });
64 registerLocalAuthRoutes(app, {
65 dataDir,
66 sessionSecret: TEST_SECRET,
67 jwtExpiry: TEST_JWT_EXPIRY,
68 offlineLockedActive,
69 });
70 app.get('/api/v1/auth/session', (req, res) => {
71 const auth = req.headers.authorization;
72 if (!auth?.startsWith('Bearer ')) return res.status(401).json({ code: 'UNAUTHORIZED' });
73 try {
74 const payload = jwt.verify(auth.slice(7), TEST_SECRET);
75 return res.json(payload);
76 } catch (_) {
77 return res.status(401).json({ code: 'UNAUTHORIZED' });
78 }
79 });
80 app.get('/api/v1/roles', (req, res) => {
81 const auth = req.headers.authorization;
82 if (!auth?.startsWith('Bearer ')) return res.status(401).json({ code: 'UNAUTHORIZED' });
83 try {
84 jwt.verify(auth.slice(7), TEST_SECRET);
85 } catch (_) {
86 return res.status(401).json({ code: 'UNAUTHORIZED' });
87 }
88 const rolesFile = path.join(dataDir, 'hub_roles.json');
89 const roles = fs.existsSync(rolesFile) ? JSON.parse(fs.readFileSync(rolesFile, 'utf8')).roles : {};
90 res.json({ roles });
91 });
92
93 const server = await new Promise((resolve) => {
94 const s = app.listen(0, '127.0.0.1', () => resolve(s));
95 });
96 const addr = server.address();
97 const baseUrl = `http://127.0.0.1:${addr.port}`;
98 return {
99 app,
100 baseUrl,
101 close: () =>
102 new Promise((resolve, reject) => {
103 server.close((err) => (err ? reject(err) : resolve()));
104 }),
105 };
106 }
107
108 /**
109 * @param {string} dataDir
110 * @param {string} [username]
111 */
112 export async function bootstrapTestAdmin(dataDir, username = 'admin') {
113 return bootstrapAdminCli(dataDir, username, TEST_PASSPHRASE);
114 }
File History 1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf mirror: GitHub Phase A durable MCP OAuth (#270) Human minor 10 days ago