latchkey 2.8.0 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/src/cliCommands.d.ts +1 -1
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/config.d.ts +5 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +8 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/curlInjection.d.ts +1 -1
- package/dist/src/curlInjection.d.ts.map +1 -1
- package/dist/src/curlInjection.js +16 -1
- package/dist/src/curlInjection.js.map +1 -1
- package/dist/src/gateway/extensions.d.ts +88 -0
- package/dist/src/gateway/extensions.d.ts.map +1 -0
- package/dist/src/gateway/extensions.js +247 -0
- package/dist/src/gateway/extensions.js.map +1 -0
- package/dist/src/gateway/gatewayEndpoint.d.ts +11 -1
- package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.js +40 -39
- package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
- package/dist/src/gateway/permissionsOverride.d.ts +9 -0
- package/dist/src/gateway/permissionsOverride.d.ts.map +1 -1
- package/dist/src/gateway/permissionsOverride.js +14 -0
- package/dist/src/gateway/permissionsOverride.js.map +1 -1
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +75 -14
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/permissions.d.ts +3 -6
- package/dist/src/permissions.d.ts.map +1 -1
- package/dist/src/permissions.js +6 -13
- package/dist/src/permissions.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tests/cli.test.js +3 -0
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/gateway.test.js +14 -0
- package/dist/tests/gateway.test.js.map +1 -1
- package/dist/tests/gatewayExtensions.test.d.ts +2 -0
- package/dist/tests/gatewayExtensions.test.d.ts.map +1 -0
- package/dist/tests/gatewayExtensions.test.js +604 -0
- package/dist/tests/gatewayExtensions.test.js.map +1 -0
- package/dist/tests/permissions.test.js +14 -10
- package/dist/tests/permissions.test.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/gateway/permissionPointer.d.ts +0 -56
- package/dist/src/gateway/permissionPointer.d.ts.map +0 -1
- package/dist/src/gateway/permissionPointer.js +0 -171
- package/dist/src/gateway/permissionPointer.js.map +0 -1
- package/dist/tests/encryptedStorageKeyGeneration.test.d.ts +0 -2
- package/dist/tests/encryptedStorageKeyGeneration.test.d.ts.map +0 -1
- package/dist/tests/encryptedStorageKeyGeneration.test.js +0 -23
- package/dist/tests/encryptedStorageKeyGeneration.test.js.map +0 -1
- package/dist/tests/permissionPointer.test.d.ts +0 -2
- package/dist/tests/permissionPointer.test.d.ts.map +0 -1
- package/dist/tests/permissionPointer.test.js +0 -152
- package/dist/tests/permissionPointer.test.js.map +0 -1
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { ApiCredentialStore } from '../src/apiCredentials/store.js';
|
|
6
|
+
import { Config } from '../src/config.js';
|
|
7
|
+
import { EncryptedStorage } from '../src/encryptedStorage.js';
|
|
8
|
+
import { ServiceRegistry } from '../src/serviceRegistry.js';
|
|
9
|
+
import { startGateway } from '../src/gateway/server.js';
|
|
10
|
+
import { derivePermissionsOverrideSigningKey, createPermissionsOverrideJwt, PERMISSIONS_OVERRIDE_HEADER, } from '../src/gateway/permissionsOverride.js';
|
|
11
|
+
import { EXTENSION_PLACEHOLDER_HOST, ExtensionLoadError, ExtensionStartError, loadExtensions, startExtensions, stopExtensions, } from '../src/gateway/extensions.js';
|
|
12
|
+
import { GATEWAY_PASSWORD_HEADER } from '../src/gateway/password.js';
|
|
13
|
+
const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
|
|
14
|
+
function writeExtension(directory, fileName, source) {
|
|
15
|
+
const filePath = join(directory, fileName);
|
|
16
|
+
writeFileSync(filePath, source, 'utf-8');
|
|
17
|
+
return filePath;
|
|
18
|
+
}
|
|
19
|
+
// ─── Unit tests: loadExtensions ───────────────────────────────────────────────
|
|
20
|
+
describe('loadExtensions', () => {
|
|
21
|
+
let tempDir;
|
|
22
|
+
let extensionsDir;
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
tempDir = mkdtempSync(join(tmpdir(), 'latchkey-ext-test-'));
|
|
25
|
+
extensionsDir = join(tempDir, 'extensions');
|
|
26
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
27
|
+
});
|
|
28
|
+
afterEach(() => {
|
|
29
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
30
|
+
});
|
|
31
|
+
it('returns an empty list when the directory does not exist', async () => {
|
|
32
|
+
const extensions = await loadExtensions(join(tempDir, 'nonexistent'));
|
|
33
|
+
expect(extensions).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
it('returns an empty list when the path is a file, not a directory', async () => {
|
|
36
|
+
const filePath = join(tempDir, 'not-a-dir');
|
|
37
|
+
writeFileSync(filePath, 'noop', 'utf-8');
|
|
38
|
+
const extensions = await loadExtensions(filePath);
|
|
39
|
+
expect(extensions).toEqual([]);
|
|
40
|
+
});
|
|
41
|
+
it('skips files with unsupported suffixes (including .js)', async () => {
|
|
42
|
+
writeExtension(extensionsDir, 'README.txt', 'not an extension');
|
|
43
|
+
writeExtension(extensionsDir, 'config.json', '{}');
|
|
44
|
+
// .js is intentionally not supported: extensions must be `.mjs` so Node
|
|
45
|
+
// never has to fall back to the CommonJS-then-ESM reparse, which emits
|
|
46
|
+
// a MODULE_TYPELESS_PACKAGE_JSON warning.
|
|
47
|
+
writeExtension(extensionsDir, 'looks-like-an-extension.js', `export default () => false;`);
|
|
48
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
49
|
+
expect(extensions).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
it('loads a single .mjs extension', async () => {
|
|
52
|
+
writeExtension(extensionsDir, 'hello.mjs', `export default async (req, res) => {
|
|
53
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
54
|
+
res.end('hi');
|
|
55
|
+
return true;
|
|
56
|
+
};`);
|
|
57
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
58
|
+
expect(extensions).toHaveLength(1);
|
|
59
|
+
expect(extensions[0].sourceFile).toContain('hello.mjs');
|
|
60
|
+
expect(typeof extensions[0].handler).toBe('function');
|
|
61
|
+
});
|
|
62
|
+
it('loads multiple files in deterministic alphabetical order', async () => {
|
|
63
|
+
writeExtension(extensionsDir, 'b-second.mjs', `export default () => false;`);
|
|
64
|
+
writeExtension(extensionsDir, 'a-first.mjs', `export default () => false;`);
|
|
65
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
66
|
+
expect(extensions.map((extension) => extension.sourceFile.endsWith('a-first.mjs'))).toEqual([
|
|
67
|
+
true,
|
|
68
|
+
false,
|
|
69
|
+
]);
|
|
70
|
+
expect(extensions.map((extension) => extension.sourceFile.endsWith('b-second.mjs'))).toEqual([
|
|
71
|
+
false,
|
|
72
|
+
true,
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
it('throws ExtensionLoadError when a file has a syntax error', async () => {
|
|
76
|
+
writeExtension(extensionsDir, 'broken.mjs', 'this is not valid javascript ((((');
|
|
77
|
+
await expect(loadExtensions(extensionsDir)).rejects.toThrow(ExtensionLoadError);
|
|
78
|
+
});
|
|
79
|
+
it('throws ExtensionLoadError when there is no default export', async () => {
|
|
80
|
+
writeExtension(extensionsDir, 'no-default.mjs', `export const handler = () => {};`);
|
|
81
|
+
await expect(loadExtensions(extensionsDir)).rejects.toThrow(/must export a default function/);
|
|
82
|
+
});
|
|
83
|
+
it('throws ExtensionLoadError when the default export is not a function', async () => {
|
|
84
|
+
writeExtension(extensionsDir, 'object-default.mjs', `export default { handler: () => {} };`);
|
|
85
|
+
await expect(loadExtensions(extensionsDir)).rejects.toThrow(/must export a default function/);
|
|
86
|
+
});
|
|
87
|
+
it('exposes optional start and stop hooks when the module exports them', async () => {
|
|
88
|
+
writeExtension(extensionsDir, 'hooks.mjs', `export default () => false;
|
|
89
|
+
export const start = async () => {};
|
|
90
|
+
export const stop = () => {};`);
|
|
91
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
92
|
+
expect(extensions).toHaveLength(1);
|
|
93
|
+
expect(typeof extensions[0].start).toBe('function');
|
|
94
|
+
expect(typeof extensions[0].stop).toBe('function');
|
|
95
|
+
});
|
|
96
|
+
it('leaves start and stop undefined when the module does not export them', async () => {
|
|
97
|
+
writeExtension(extensionsDir, 'nohooks.mjs', `export default () => false;`);
|
|
98
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
99
|
+
expect(extensions[0].start).toBeUndefined();
|
|
100
|
+
expect(extensions[0].stop).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
it('throws ExtensionLoadError when start is exported but is not a function', async () => {
|
|
103
|
+
writeExtension(extensionsDir, 'badstart.mjs', `export default () => false;
|
|
104
|
+
export const start = 'not-a-function';`);
|
|
105
|
+
await expect(loadExtensions(extensionsDir)).rejects.toThrow(/exports 'start' but it is not a function/);
|
|
106
|
+
});
|
|
107
|
+
it('throws ExtensionLoadError when stop is exported but is not a function', async () => {
|
|
108
|
+
writeExtension(extensionsDir, 'badstop.mjs', `export default () => false;
|
|
109
|
+
export const stop = 42;`);
|
|
110
|
+
await expect(loadExtensions(extensionsDir)).rejects.toThrow(/exports 'stop' but it is not a function/);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
// ─── Unit tests: startExtensions / stopExtensions ────────────────────────────
|
|
114
|
+
describe('startExtensions / stopExtensions', () => {
|
|
115
|
+
let tempDir;
|
|
116
|
+
let extensionsDir;
|
|
117
|
+
beforeEach(() => {
|
|
118
|
+
tempDir = mkdtempSync(join(tmpdir(), 'latchkey-ext-lifecycle-'));
|
|
119
|
+
extensionsDir = join(tempDir, 'extensions');
|
|
120
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
121
|
+
});
|
|
122
|
+
afterEach(() => {
|
|
123
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
124
|
+
});
|
|
125
|
+
function makeFakeDeps(errorLogs) {
|
|
126
|
+
return {
|
|
127
|
+
registry: new ServiceRegistry([]),
|
|
128
|
+
config: new Config(() => undefined),
|
|
129
|
+
runCurl: () => ({ returncode: 0, stdout: '', stderr: '' }),
|
|
130
|
+
runCurlAsync: () => Promise.resolve({ returncode: 0, stdout: Buffer.alloc(0), stderr: '' }),
|
|
131
|
+
checkPermission: () => Promise.resolve(true),
|
|
132
|
+
confirm: () => Promise.resolve(true),
|
|
133
|
+
exit: (code) => {
|
|
134
|
+
throw new Error(`process.exit(${String(code)})`);
|
|
135
|
+
},
|
|
136
|
+
log: (_message) => {
|
|
137
|
+
// intentionally ignored in lifecycle tests
|
|
138
|
+
},
|
|
139
|
+
errorLog: (message) => {
|
|
140
|
+
errorLogs.push(message);
|
|
141
|
+
},
|
|
142
|
+
version: '0.0.0-test',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
it('calls start on every extension in load order', async () => {
|
|
146
|
+
const recordPath = join(tempDir, 'order.txt');
|
|
147
|
+
writeExtension(extensionsDir, 'a.mjs', `import { appendFileSync } from 'node:fs';
|
|
148
|
+
export default () => false;
|
|
149
|
+
export const start = () => appendFileSync(${JSON.stringify(recordPath)}, 'a-start\\n');`);
|
|
150
|
+
writeExtension(extensionsDir, 'b.mjs', `import { appendFileSync } from 'node:fs';
|
|
151
|
+
export default () => false;
|
|
152
|
+
export const start = () => appendFileSync(${JSON.stringify(recordPath)}, 'b-start\\n');`);
|
|
153
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
154
|
+
await startExtensions(extensions);
|
|
155
|
+
const { readFileSync } = await import('node:fs');
|
|
156
|
+
expect(readFileSync(recordPath, 'utf-8')).toBe('a-start\nb-start\n');
|
|
157
|
+
});
|
|
158
|
+
it('awaits async start hooks before resuming', async () => {
|
|
159
|
+
const recordPath = join(tempDir, 'async-start.txt');
|
|
160
|
+
writeExtension(extensionsDir, 'slow.mjs', `import { writeFileSync } from 'node:fs';
|
|
161
|
+
export default () => false;
|
|
162
|
+
export const start = () => new Promise((resolve) => {
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
writeFileSync(${JSON.stringify(recordPath)}, 'started');
|
|
165
|
+
resolve();
|
|
166
|
+
}, 20);
|
|
167
|
+
});`);
|
|
168
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
169
|
+
await startExtensions(extensions);
|
|
170
|
+
const { readFileSync } = await import('node:fs');
|
|
171
|
+
expect(readFileSync(recordPath, 'utf-8')).toBe('started');
|
|
172
|
+
});
|
|
173
|
+
it('throws ExtensionStartError when a start hook throws and aborts subsequent starts', async () => {
|
|
174
|
+
const recordPath = join(tempDir, 'aborted.txt');
|
|
175
|
+
writeExtension(extensionsDir, 'a-boom.mjs', `export default () => false;
|
|
176
|
+
export const start = () => { throw new Error('start-failed'); };`);
|
|
177
|
+
writeExtension(extensionsDir, 'b-should-not-run.mjs', `import { writeFileSync } from 'node:fs';
|
|
178
|
+
export default () => false;
|
|
179
|
+
export const start = () => writeFileSync(${JSON.stringify(recordPath)}, 'b-started');`);
|
|
180
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
181
|
+
await expect(startExtensions(extensions)).rejects.toThrow(ExtensionStartError);
|
|
182
|
+
await expect(startExtensions(extensions)).rejects.toThrow(/start-failed/);
|
|
183
|
+
expect(existsSync(recordPath)).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
it('calls stop on every extension in load order', async () => {
|
|
186
|
+
const recordPath = join(tempDir, 'stop-order.txt');
|
|
187
|
+
writeExtension(extensionsDir, 'a.mjs', `import { appendFileSync } from 'node:fs';
|
|
188
|
+
export default () => false;
|
|
189
|
+
export const stop = () => appendFileSync(${JSON.stringify(recordPath)}, 'a-stop\\n');`);
|
|
190
|
+
writeExtension(extensionsDir, 'b.mjs', `import { appendFileSync } from 'node:fs';
|
|
191
|
+
export default () => false;
|
|
192
|
+
export const stop = () => appendFileSync(${JSON.stringify(recordPath)}, 'b-stop\\n');`);
|
|
193
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
194
|
+
const errorLogs = [];
|
|
195
|
+
await stopExtensions(extensions, makeFakeDeps(errorLogs));
|
|
196
|
+
const { readFileSync } = await import('node:fs');
|
|
197
|
+
expect(readFileSync(recordPath, 'utf-8')).toBe('a-stop\nb-stop\n');
|
|
198
|
+
expect(errorLogs).toEqual([]);
|
|
199
|
+
});
|
|
200
|
+
it('logs and swallows errors thrown by stop hooks, continuing with subsequent extensions', async () => {
|
|
201
|
+
const recordPath = join(tempDir, 'stop-continued.txt');
|
|
202
|
+
writeExtension(extensionsDir, 'a-boom.mjs', `export default () => false;
|
|
203
|
+
export const stop = () => { throw new Error('stop-failed'); };`);
|
|
204
|
+
writeExtension(extensionsDir, 'b-still-runs.mjs', `import { writeFileSync } from 'node:fs';
|
|
205
|
+
export default () => false;
|
|
206
|
+
export const stop = () => writeFileSync(${JSON.stringify(recordPath)}, 'b-stopped');`);
|
|
207
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
208
|
+
const errorLogs = [];
|
|
209
|
+
await stopExtensions(extensions, makeFakeDeps(errorLogs));
|
|
210
|
+
const { readFileSync } = await import('node:fs');
|
|
211
|
+
expect(readFileSync(recordPath, 'utf-8')).toBe('b-stopped');
|
|
212
|
+
expect(errorLogs.some((message) => message.includes('stop-failed'))).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
it('skips extensions without lifecycle hooks', async () => {
|
|
215
|
+
writeExtension(extensionsDir, 'plain.mjs', `export default () => false;`);
|
|
216
|
+
const extensions = await loadExtensions(extensionsDir);
|
|
217
|
+
const errorLogs = [];
|
|
218
|
+
await expect(startExtensions(extensions)).resolves.toBeUndefined();
|
|
219
|
+
await expect(stopExtensions(extensions, makeFakeDeps(errorLogs))).resolves.toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
// ─── Integration tests via startGateway ───────────────────────────────────────
|
|
223
|
+
describe('gateway extensions integration', () => {
|
|
224
|
+
let tempDir;
|
|
225
|
+
let extensionsDir;
|
|
226
|
+
let gateway;
|
|
227
|
+
let logs;
|
|
228
|
+
let errorLogs;
|
|
229
|
+
let mockPermissionResult;
|
|
230
|
+
let lastPermissionCheckRequest;
|
|
231
|
+
let lastPermissionCheckPath;
|
|
232
|
+
function createMockConfig() {
|
|
233
|
+
return new Config((name) => {
|
|
234
|
+
if (name === 'LATCHKEY_DIRECTORY')
|
|
235
|
+
return tempDir;
|
|
236
|
+
if (name === 'LATCHKEY_ENCRYPTION_KEY')
|
|
237
|
+
return TEST_ENCRYPTION_KEY;
|
|
238
|
+
return undefined;
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function createTestGateway(optionOverrides = {}) {
|
|
242
|
+
const credentialsPath = join(tempDir, 'credentials.json.enc');
|
|
243
|
+
const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
|
|
244
|
+
encryptedStorage.writeFile(credentialsPath, '{}');
|
|
245
|
+
const apiCredentialStore = new ApiCredentialStore(credentialsPath, encryptedStorage);
|
|
246
|
+
const config = createMockConfig();
|
|
247
|
+
const deps = {
|
|
248
|
+
registry: new ServiceRegistry([]),
|
|
249
|
+
config,
|
|
250
|
+
runCurl: () => ({ returncode: 0, stdout: '', stderr: '' }),
|
|
251
|
+
runCurlAsync: () => Promise.resolve({ returncode: 0, stdout: Buffer.alloc(0), stderr: '' }),
|
|
252
|
+
checkPermission: (request, configPath, _builtin) => {
|
|
253
|
+
lastPermissionCheckRequest = request;
|
|
254
|
+
lastPermissionCheckPath = configPath;
|
|
255
|
+
return Promise.resolve(mockPermissionResult);
|
|
256
|
+
},
|
|
257
|
+
confirm: () => Promise.resolve(true),
|
|
258
|
+
exit: (code) => {
|
|
259
|
+
throw new Error(`process.exit(${String(code)})`);
|
|
260
|
+
},
|
|
261
|
+
log: (message) => {
|
|
262
|
+
logs.push(message);
|
|
263
|
+
},
|
|
264
|
+
errorLog: (message) => {
|
|
265
|
+
errorLogs.push(message);
|
|
266
|
+
},
|
|
267
|
+
version: '0.0.0-test',
|
|
268
|
+
};
|
|
269
|
+
const options = {
|
|
270
|
+
port: 0,
|
|
271
|
+
host: 'localhost',
|
|
272
|
+
maxBodySize: 10 * 1024 * 1024,
|
|
273
|
+
password: null,
|
|
274
|
+
permissionsOverrideSigningKey: derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY),
|
|
275
|
+
...optionOverrides,
|
|
276
|
+
};
|
|
277
|
+
return startGateway(deps, apiCredentialStore, encryptedStorage, options);
|
|
278
|
+
}
|
|
279
|
+
function getHost() {
|
|
280
|
+
if (gateway === undefined)
|
|
281
|
+
throw new Error('Gateway not started');
|
|
282
|
+
const address = gateway.server.address();
|
|
283
|
+
if (address === null || typeof address === 'string') {
|
|
284
|
+
throw new Error('Failed to get server address');
|
|
285
|
+
}
|
|
286
|
+
const host = address.family === 'IPv6' ? `[${address.address}]` : address.address;
|
|
287
|
+
return `http://${host}:${String(address.port)}`;
|
|
288
|
+
}
|
|
289
|
+
function fetch(path, options = {}) {
|
|
290
|
+
return globalThis.fetch(`${getHost()}${path}`, options);
|
|
291
|
+
}
|
|
292
|
+
beforeEach(() => {
|
|
293
|
+
tempDir = mkdtempSync(join(tmpdir(), 'latchkey-ext-int-'));
|
|
294
|
+
extensionsDir = join(tempDir, 'extensions');
|
|
295
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
296
|
+
logs = [];
|
|
297
|
+
errorLogs = [];
|
|
298
|
+
mockPermissionResult = true;
|
|
299
|
+
lastPermissionCheckRequest = undefined;
|
|
300
|
+
lastPermissionCheckPath = undefined;
|
|
301
|
+
});
|
|
302
|
+
afterEach(async () => {
|
|
303
|
+
if (gateway) {
|
|
304
|
+
await gateway.close();
|
|
305
|
+
gateway = undefined;
|
|
306
|
+
}
|
|
307
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
308
|
+
});
|
|
309
|
+
it('serves an extension that claims a request', async () => {
|
|
310
|
+
writeExtension(extensionsDir, 'echo.mjs', `export default (req, res) => {
|
|
311
|
+
if (req.url === '/extensions/echo') {
|
|
312
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
313
|
+
res.end(JSON.stringify({ url: req.url }));
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
return false;
|
|
317
|
+
};`);
|
|
318
|
+
gateway = await createTestGateway();
|
|
319
|
+
const response = await fetch('/extensions/echo');
|
|
320
|
+
expect(response.status).toBe(200);
|
|
321
|
+
const body = (await response.json());
|
|
322
|
+
expect(body.url).toBe('/extensions/echo');
|
|
323
|
+
});
|
|
324
|
+
it('falls through to 404 when no extension claims the request', async () => {
|
|
325
|
+
writeExtension(extensionsDir, 'narrow.mjs', `export default (req, res) => {
|
|
326
|
+
if (req.url === '/extensions/specific') {
|
|
327
|
+
res.writeHead(200);
|
|
328
|
+
res.end();
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
};`);
|
|
333
|
+
gateway = await createTestGateway();
|
|
334
|
+
const response = await fetch('/extensions/different');
|
|
335
|
+
expect(response.status).toBe(404);
|
|
336
|
+
});
|
|
337
|
+
it('returns 404 when no extensions are installed', async () => {
|
|
338
|
+
rmSync(extensionsDir, { recursive: true, force: true });
|
|
339
|
+
gateway = await createTestGateway();
|
|
340
|
+
const response = await fetch('/extensions/anything');
|
|
341
|
+
expect(response.status).toBe(404);
|
|
342
|
+
});
|
|
343
|
+
it('tries extensions in alphabetical order; first to return true wins', async () => {
|
|
344
|
+
writeExtension(extensionsDir, 'a-claim.mjs', `export default (req, res) => {
|
|
345
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
346
|
+
res.end('a');
|
|
347
|
+
return true;
|
|
348
|
+
};`);
|
|
349
|
+
writeExtension(extensionsDir, 'b-also-claim.mjs', `export default (req, res) => {
|
|
350
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
351
|
+
res.end('b');
|
|
352
|
+
return true;
|
|
353
|
+
};`);
|
|
354
|
+
gateway = await createTestGateway();
|
|
355
|
+
const response = await fetch('/anything');
|
|
356
|
+
expect(await response.text()).toBe('a');
|
|
357
|
+
});
|
|
358
|
+
it('continues to the next extension when one returns false', async () => {
|
|
359
|
+
writeExtension(extensionsDir, 'a-defer.mjs', `export default (req, res) => false;`);
|
|
360
|
+
writeExtension(extensionsDir, 'b-claim.mjs', `export default (req, res) => {
|
|
361
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
362
|
+
res.end('handled-by-b');
|
|
363
|
+
return true;
|
|
364
|
+
};`);
|
|
365
|
+
gateway = await createTestGateway();
|
|
366
|
+
const response = await fetch('/whatever');
|
|
367
|
+
expect(await response.text()).toBe('handled-by-b');
|
|
368
|
+
});
|
|
369
|
+
it('preserves query string for the handler and in the permission check', async () => {
|
|
370
|
+
writeExtension(extensionsDir, 'q.mjs', `export default (req, res) => {
|
|
371
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
372
|
+
res.end(req.url);
|
|
373
|
+
return true;
|
|
374
|
+
};`);
|
|
375
|
+
gateway = await createTestGateway();
|
|
376
|
+
const response = await fetch('/extensions/q?x=1&y=2');
|
|
377
|
+
expect(await response.text()).toBe('/extensions/q?x=1&y=2');
|
|
378
|
+
const synthesizedUrl = lastPermissionCheckRequest?.url ?? '';
|
|
379
|
+
expect(synthesizedUrl).toContain('?x=1&y=2');
|
|
380
|
+
expect(synthesizedUrl).toContain(EXTENSION_PLACEHOLDER_HOST);
|
|
381
|
+
});
|
|
382
|
+
it('returns 403 when the permission check denies the request, before any extension is invoked', async () => {
|
|
383
|
+
writeExtension(extensionsDir, 'should-not-run.mjs', `export default (req, res) => {
|
|
384
|
+
res.writeHead(200);
|
|
385
|
+
res.end('should not happen');
|
|
386
|
+
return true;
|
|
387
|
+
};`);
|
|
388
|
+
mockPermissionResult = false;
|
|
389
|
+
gateway = await createTestGateway();
|
|
390
|
+
const response = await fetch('/anything');
|
|
391
|
+
expect(response.status).toBe(403);
|
|
392
|
+
const body = (await response.json());
|
|
393
|
+
expect(body.error.toLowerCase()).toContain('not permitted');
|
|
394
|
+
});
|
|
395
|
+
it('returns 500 and logs when an extension throws', async () => {
|
|
396
|
+
writeExtension(extensionsDir, 'boom.mjs', `export default () => {
|
|
397
|
+
throw new Error('boom!');
|
|
398
|
+
};`);
|
|
399
|
+
gateway = await createTestGateway();
|
|
400
|
+
const response = await fetch('/extensions/boom');
|
|
401
|
+
expect(response.status).toBe(500);
|
|
402
|
+
expect(errorLogs.some((message) => message.includes('boom!'))).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
it('does not call later extensions after one throws', async () => {
|
|
405
|
+
writeExtension(extensionsDir, 'a-throws.mjs', `export default () => {
|
|
406
|
+
throw new Error('first failed');
|
|
407
|
+
};`);
|
|
408
|
+
writeExtension(extensionsDir, 'b-claim.mjs', `export default (req, res) => {
|
|
409
|
+
res.writeHead(200);
|
|
410
|
+
res.end('b');
|
|
411
|
+
return true;
|
|
412
|
+
};`);
|
|
413
|
+
gateway = await createTestGateway();
|
|
414
|
+
const response = await fetch('/path');
|
|
415
|
+
expect(response.status).toBe(500);
|
|
416
|
+
});
|
|
417
|
+
it('does not let extensions intercept the health endpoint', async () => {
|
|
418
|
+
writeExtension(extensionsDir, 'broad.mjs', `export default (req, res) => {
|
|
419
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
420
|
+
res.end('hijacked');
|
|
421
|
+
return true;
|
|
422
|
+
};`);
|
|
423
|
+
gateway = await createTestGateway();
|
|
424
|
+
const response = await fetch('/');
|
|
425
|
+
expect(response.status).toBe(200);
|
|
426
|
+
const body = (await response.json());
|
|
427
|
+
expect(body.status).toBe('ok');
|
|
428
|
+
});
|
|
429
|
+
it('does not offer /gateway/<malformed-url> requests to extensions', async () => {
|
|
430
|
+
writeExtension(extensionsDir, 'broad.mjs', `export default (req, res) => {
|
|
431
|
+
res.writeHead(200);
|
|
432
|
+
res.end('hijacked');
|
|
433
|
+
return true;
|
|
434
|
+
};`);
|
|
435
|
+
gateway = await createTestGateway();
|
|
436
|
+
const response = await fetch('/gateway/not-a-url');
|
|
437
|
+
expect(response.status).toBe(400);
|
|
438
|
+
});
|
|
439
|
+
it('does not offer /latchkey/ requests to extensions', async () => {
|
|
440
|
+
writeExtension(extensionsDir, 'broad.mjs', `export default (req, res) => {
|
|
441
|
+
res.writeHead(200);
|
|
442
|
+
res.end('hijacked');
|
|
443
|
+
return true;
|
|
444
|
+
};`);
|
|
445
|
+
gateway = await createTestGateway();
|
|
446
|
+
const response = await fetch('/latchkey/', {
|
|
447
|
+
method: 'POST',
|
|
448
|
+
headers: { 'Content-Type': 'application/json' },
|
|
449
|
+
body: JSON.stringify({ command: 'auth list' }),
|
|
450
|
+
});
|
|
451
|
+
// Should be handled by the latchkey RPC endpoint, not the extension.
|
|
452
|
+
expect(response.status).toBe(200);
|
|
453
|
+
});
|
|
454
|
+
it('fails startup when an extension is malformed', async () => {
|
|
455
|
+
writeExtension(extensionsDir, 'broken.mjs', 'syntax error (((');
|
|
456
|
+
await expect(createTestGateway()).rejects.toThrow(ExtensionLoadError);
|
|
457
|
+
});
|
|
458
|
+
it('still requires the gateway password for extension routes', async () => {
|
|
459
|
+
writeExtension(extensionsDir, 'pw.mjs', `export default (req, res) => {
|
|
460
|
+
res.writeHead(200);
|
|
461
|
+
res.end('ok');
|
|
462
|
+
return true;
|
|
463
|
+
};`);
|
|
464
|
+
gateway = await createTestGateway({ password: 'sekret' });
|
|
465
|
+
const denied = await fetch('/extensions/pw');
|
|
466
|
+
expect(denied.status).toBe(401);
|
|
467
|
+
const allowed = await fetch('/extensions/pw', {
|
|
468
|
+
headers: { [GATEWAY_PASSWORD_HEADER]: 'sekret' },
|
|
469
|
+
});
|
|
470
|
+
expect(allowed.status).toBe(200);
|
|
471
|
+
});
|
|
472
|
+
it('honors the permissions-override JWT for extension routes', async () => {
|
|
473
|
+
writeExtension(extensionsDir, 'perm.mjs', `export default (req, res) => {
|
|
474
|
+
res.writeHead(200);
|
|
475
|
+
res.end('ok');
|
|
476
|
+
return true;
|
|
477
|
+
};`);
|
|
478
|
+
const overridePath = join(tempDir, 'override-permissions.json');
|
|
479
|
+
writeFileSync(overridePath, '{"rules":[]}', 'utf-8');
|
|
480
|
+
const signingKey = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
|
|
481
|
+
const jwt = createPermissionsOverrideJwt(overridePath, signingKey);
|
|
482
|
+
gateway = await createTestGateway();
|
|
483
|
+
const response = await fetch('/extensions/perm', {
|
|
484
|
+
headers: { [PERMISSIONS_OVERRIDE_HEADER]: jwt },
|
|
485
|
+
});
|
|
486
|
+
expect(response.status).toBe(200);
|
|
487
|
+
expect(lastPermissionCheckPath).toBe(overridePath);
|
|
488
|
+
});
|
|
489
|
+
it('returns 401 for an invalid permissions-override JWT', async () => {
|
|
490
|
+
writeExtension(extensionsDir, 'p.mjs', `export default (req, res) => {
|
|
491
|
+
res.writeHead(200);
|
|
492
|
+
res.end('ok');
|
|
493
|
+
return true;
|
|
494
|
+
};`);
|
|
495
|
+
gateway = await createTestGateway();
|
|
496
|
+
const response = await fetch('/extensions/p', {
|
|
497
|
+
headers: { [PERMISSIONS_OVERRIDE_HEADER]: 'not.a.jwt' },
|
|
498
|
+
});
|
|
499
|
+
expect(response.status).toBe(401);
|
|
500
|
+
});
|
|
501
|
+
it('supports handlers that complete asynchronously after reading the request body', async () => {
|
|
502
|
+
writeExtension(extensionsDir, 'echo-body.mjs', `export default (req, res) =>
|
|
503
|
+
new Promise((resolve) => {
|
|
504
|
+
let body = '';
|
|
505
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
506
|
+
req.on('end', () => {
|
|
507
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
508
|
+
res.end(body.toUpperCase());
|
|
509
|
+
resolve(true);
|
|
510
|
+
});
|
|
511
|
+
});`);
|
|
512
|
+
gateway = await createTestGateway();
|
|
513
|
+
const response = await fetch('/extensions/echo-body', {
|
|
514
|
+
method: 'POST',
|
|
515
|
+
body: 'hello',
|
|
516
|
+
});
|
|
517
|
+
expect(response.status).toBe(200);
|
|
518
|
+
expect(await response.text()).toBe('HELLO');
|
|
519
|
+
});
|
|
520
|
+
it('logs the extension request line with the actual response status', async () => {
|
|
521
|
+
writeExtension(extensionsDir, 'log.mjs', `export default (req, res) => {
|
|
522
|
+
res.writeHead(202);
|
|
523
|
+
res.end();
|
|
524
|
+
return true;
|
|
525
|
+
};`);
|
|
526
|
+
gateway = await createTestGateway();
|
|
527
|
+
const response = await fetch('/extensions/log');
|
|
528
|
+
await response.text();
|
|
529
|
+
expect(logs).toContain('GET /extensions/log -> 202 (extension)');
|
|
530
|
+
});
|
|
531
|
+
it('invokes extension start hooks before the HTTP listener accepts requests, and stop hooks at shutdown', async () => {
|
|
532
|
+
const markerPath = join(tempDir, 'lifecycle.txt');
|
|
533
|
+
writeExtension(extensionsDir, 'lifecycle.mjs', `import { appendFileSync } from 'node:fs';
|
|
534
|
+
export default (req, res) => {
|
|
535
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
536
|
+
res.end('ok');
|
|
537
|
+
return true;
|
|
538
|
+
};
|
|
539
|
+
export const start = () => appendFileSync(${JSON.stringify(markerPath)}, 'start\\n');
|
|
540
|
+
export const stop = () => appendFileSync(${JSON.stringify(markerPath)}, 'stop\\n');`);
|
|
541
|
+
gateway = await createTestGateway();
|
|
542
|
+
const { readFileSync } = await import('node:fs');
|
|
543
|
+
expect(readFileSync(markerPath, 'utf-8')).toBe('start\n');
|
|
544
|
+
const response = await fetch('/extensions/lifecycle');
|
|
545
|
+
expect(response.status).toBe(200);
|
|
546
|
+
await gateway.close();
|
|
547
|
+
gateway = undefined;
|
|
548
|
+
expect(readFileSync(markerPath, 'utf-8')).toBe('start\nstop\n');
|
|
549
|
+
});
|
|
550
|
+
it('aborts gateway startup when an extension start hook throws', async () => {
|
|
551
|
+
writeExtension(extensionsDir, 'cant-start.mjs', `export default () => false;
|
|
552
|
+
export const start = () => { throw new Error('nope'); };`);
|
|
553
|
+
await expect(createTestGateway()).rejects.toThrow(ExtensionStartError);
|
|
554
|
+
});
|
|
555
|
+
it('lets a stop hook proactively end long-lived streams so shutdown completes promptly', async () => {
|
|
556
|
+
writeExtension(extensionsDir, 'stream.mjs', `const openResponses = new Set();
|
|
557
|
+
export default (req, res) => {
|
|
558
|
+
if (req.url !== '/extensions/stream') return false;
|
|
559
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
560
|
+
res.flushHeaders();
|
|
561
|
+
res.write('hello\\n');
|
|
562
|
+
openResponses.add(res);
|
|
563
|
+
return new Promise((resolve) => {
|
|
564
|
+
const finish = () => {
|
|
565
|
+
openResponses.delete(res);
|
|
566
|
+
if (!res.writableEnded) res.end();
|
|
567
|
+
resolve(true);
|
|
568
|
+
};
|
|
569
|
+
req.on('close', finish);
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
export const stop = () => {
|
|
573
|
+
for (const res of openResponses) {
|
|
574
|
+
if (!res.writableEnded) res.end();
|
|
575
|
+
}
|
|
576
|
+
};`);
|
|
577
|
+
gateway = await createTestGateway();
|
|
578
|
+
// Open a long-lived stream and read the initial chunk so we know the
|
|
579
|
+
// handler is parked on the close promise.
|
|
580
|
+
const controller = new AbortController();
|
|
581
|
+
const responsePromise = fetch('/extensions/stream', { signal: controller.signal });
|
|
582
|
+
const response = await responsePromise;
|
|
583
|
+
expect(response.status).toBe(200);
|
|
584
|
+
const reader = response.body.getReader();
|
|
585
|
+
const firstChunk = await reader.read();
|
|
586
|
+
if (firstChunk.value === undefined) {
|
|
587
|
+
throw new Error('Expected an initial chunk from the streaming extension.');
|
|
588
|
+
}
|
|
589
|
+
expect(new TextDecoder().decode(firstChunk.value)).toBe('hello\n');
|
|
590
|
+
// Shutdown should complete promptly thanks to the stop hook ending
|
|
591
|
+
// the response. The force-close timeout is 10s, so anything well
|
|
592
|
+
// under that proves the stop hook actually fired.
|
|
593
|
+
const startedAt = Date.now();
|
|
594
|
+
await gateway.close();
|
|
595
|
+
gateway = undefined;
|
|
596
|
+
const elapsedMilliseconds = Date.now() - startedAt;
|
|
597
|
+
expect(elapsedMilliseconds).toBeLessThan(2000);
|
|
598
|
+
// The reader should observe end-of-stream.
|
|
599
|
+
const nextChunk = await reader.read();
|
|
600
|
+
expect(nextChunk.done).toBe(true);
|
|
601
|
+
controller.abort();
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
//# sourceMappingURL=gatewayExtensions.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gatewayExtensions.test.js","sourceRoot":"","sources":["../../tests/gatewayExtensions.test.ts"],"names":[],"mappings":"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"}
|