gateway-bootstrap-child.mjs
82 lines 3.6 KB
Raw
sha256:da72236a2c7a121dcdc60fcca47a8ff86cc1176f6076d8d4cbc0614569737d33 fix(gateway): mount OAuth routers before API fallback Human minor ⚠ breaking 26 days ago
1 // Run the real server in a separate process, with no local .env or production data.
2 import { createRequire } from 'node:module';
3 import { randomBytes } from 'node:crypto';
4 import http from 'node:http';
5
6 const require = createRequire(new URL('../../hub/gateway/server.mjs', import.meta.url));
7 const rootRequire = createRequire(new URL('../../package.json', import.meta.url));
8 require('dotenv').config = rootRequire('dotenv').config = () => ({ parsed: {} });
9 const secret = randomBytes(32).toString('hex');
10 Object.assign(process.env, {
11 SESSION_SECRET: secret,
12 CANISTER_AUTH_SECRET: randomBytes(32).toString('hex'),
13 HUB_BASE_URL: 'http://localhost:3340', GATEWAY_PORT: '0',
14 BILLING_ENFORCE: 'false', NODE_ENV: 'test',
15 HUB_CORS_ORIGIN: 'https://knowtation.store,https://www.knowtation.store',
16 });
17 let upstreamHits = 0;
18 const upstream = http.createServer((req, res) => {
19 upstreamHits++;
20 res.setHeader('Content-Type', 'application/json');
21 res.end(JSON.stringify(req.url.startsWith('/api/v1/hosted-context')
22 ? { role: 'viewer', scope: {}, effective_canister_user_id: 'google:bootstrap-test' }
23 : { mock_upstream: true }));
24 });
25 await new Promise((resolve) => upstream.listen(0, '127.0.0.1', resolve));
26 process.env.CANISTER_URL = process.env.BRIDGE_URL = `http://127.0.0.1:${upstream.address().port}`;
27 const realFetch = globalThis.fetch;
28 globalThis.fetch = (input, options) => {
29 if (!['127.0.0.1', 'localhost', '[::1]'].includes(new URL(String(input)).hostname)) {
30 throw new Error('External network disabled in gateway bootstrap tests');
31 }
32 return realFetch(input, options);
33 };
34 const express = require('express');
35 let server;
36 let listenCalls = 0;
37 const realListen = express.application.listen;
38 express.application.listen = function (_port, callback) {
39 listenCalls++;
40 // Snapshot at the actual listen boundary, not after a startup delay.
41 const stack = this._router.stack;
42 const api = stack.map((layer, i) => layer.regexp.test('/api/v1') ? i : -1);
43 const catchAll = Math.max(...api.filter((i) => i >= 0 && !stack[i].regexp.fast_slash));
44 const mounts = (prefix) => stack.findIndex((layer) =>
45 layer.name === 'router' && layer.regexp.test(prefix) && !layer.regexp.fast_slash);
46 const order = { catchAll, native: mounts('/api/v1/auth/native'), device: mounts('/api/v1/auth/device') };
47 server = realListen.call(this, 0, '127.0.0.1', () => {
48 callback?.();
49 process.send({ event: 'ready', baseUrl: `http://127.0.0.1:${server.address().port}`, order, listenCalls });
50 });
51 return server;
52 };
53
54 try {
55 const { app } = await import('../../hub/gateway/server.mjs');
56 process.on('message', async ({ id, action, state, sub, type, scopes }) => {
57 try {
58 let result;
59 if (action === 'complete') {
60 await app._nativeOAuthProvider.completeNativeAuthorization(state, sub, {
61 redirect(location) { result = location; },
62 status() { return this; }, send() { throw new Error('Native completion rejected'); },
63 });
64 } else if (action === 'sign') {
65 result = require('jsonwebtoken').sign(
66 { sub, type: type || 'session', role: 'member', ...(scopes ? { scopes } : {}) },
67 secret, { expiresIn: '1h' },
68 );
69 } else if (action === 'upstreamHits') result = upstreamHits;
70 process.send({ id, result });
71 } catch { process.send({ id, error: 'Fixture operation failed' }); }
72 });
73 if (process.env.NETLIFY) {
74 if (listenCalls !== 0) throw new Error('Netlify import unexpectedly started a listener');
75 app.listen(0);
76 }
77 } catch {
78 process.send({ event: 'startup-failed', listenCalls });
79 upstream.close();
80 process.exitCode = 1;
81 process.disconnect();
82 }
File History 1 commit
sha256:da72236a2c7a121dcdc60fcca47a8ff86cc1176f6076d8d4cbc0614569737d33 fix(gateway): mount OAuth routers before API fallback Human minor ⚠ 26 days ago