latchkey 2.8.0 → 2.9.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/cli.js +0 -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 +59 -0
- package/dist/src/gateway/extensions.d.ts.map +1 -0
- package/dist/src/gateway/extensions.js +170 -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 +66 -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.js +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 +398 -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,398 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { 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, loadExtensions, } 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
|
+
});
|
|
88
|
+
// ─── Integration tests via startGateway ───────────────────────────────────────
|
|
89
|
+
describe('gateway extensions integration', () => {
|
|
90
|
+
let tempDir;
|
|
91
|
+
let extensionsDir;
|
|
92
|
+
let gateway;
|
|
93
|
+
let logs;
|
|
94
|
+
let errorLogs;
|
|
95
|
+
let mockPermissionResult;
|
|
96
|
+
let lastPermissionCheckRequest;
|
|
97
|
+
let lastPermissionCheckPath;
|
|
98
|
+
function createMockConfig() {
|
|
99
|
+
return new Config((name) => {
|
|
100
|
+
if (name === 'LATCHKEY_DIRECTORY')
|
|
101
|
+
return tempDir;
|
|
102
|
+
if (name === 'LATCHKEY_ENCRYPTION_KEY')
|
|
103
|
+
return TEST_ENCRYPTION_KEY;
|
|
104
|
+
return undefined;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function createTestGateway(optionOverrides = {}) {
|
|
108
|
+
const credentialsPath = join(tempDir, 'credentials.json.enc');
|
|
109
|
+
const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
|
|
110
|
+
encryptedStorage.writeFile(credentialsPath, '{}');
|
|
111
|
+
const apiCredentialStore = new ApiCredentialStore(credentialsPath, encryptedStorage);
|
|
112
|
+
const config = createMockConfig();
|
|
113
|
+
const deps = {
|
|
114
|
+
registry: new ServiceRegistry([]),
|
|
115
|
+
config,
|
|
116
|
+
runCurl: () => ({ returncode: 0, stdout: '', stderr: '' }),
|
|
117
|
+
runCurlAsync: () => Promise.resolve({ returncode: 0, stdout: Buffer.alloc(0), stderr: '' }),
|
|
118
|
+
checkPermission: (request, configPath, _builtin) => {
|
|
119
|
+
lastPermissionCheckRequest = request;
|
|
120
|
+
lastPermissionCheckPath = configPath;
|
|
121
|
+
return Promise.resolve(mockPermissionResult);
|
|
122
|
+
},
|
|
123
|
+
confirm: () => Promise.resolve(true),
|
|
124
|
+
exit: (code) => {
|
|
125
|
+
throw new Error(`process.exit(${String(code)})`);
|
|
126
|
+
},
|
|
127
|
+
log: (message) => {
|
|
128
|
+
logs.push(message);
|
|
129
|
+
},
|
|
130
|
+
errorLog: (message) => {
|
|
131
|
+
errorLogs.push(message);
|
|
132
|
+
},
|
|
133
|
+
version: '0.0.0-test',
|
|
134
|
+
};
|
|
135
|
+
const options = {
|
|
136
|
+
port: 0,
|
|
137
|
+
host: 'localhost',
|
|
138
|
+
maxBodySize: 10 * 1024 * 1024,
|
|
139
|
+
password: null,
|
|
140
|
+
permissionsOverrideSigningKey: derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY),
|
|
141
|
+
...optionOverrides,
|
|
142
|
+
};
|
|
143
|
+
return startGateway(deps, apiCredentialStore, encryptedStorage, options);
|
|
144
|
+
}
|
|
145
|
+
function getHost() {
|
|
146
|
+
if (gateway === undefined)
|
|
147
|
+
throw new Error('Gateway not started');
|
|
148
|
+
const address = gateway.server.address();
|
|
149
|
+
if (address === null || typeof address === 'string') {
|
|
150
|
+
throw new Error('Failed to get server address');
|
|
151
|
+
}
|
|
152
|
+
const host = address.family === 'IPv6' ? `[${address.address}]` : address.address;
|
|
153
|
+
return `http://${host}:${String(address.port)}`;
|
|
154
|
+
}
|
|
155
|
+
function fetch(path, options = {}) {
|
|
156
|
+
return globalThis.fetch(`${getHost()}${path}`, options);
|
|
157
|
+
}
|
|
158
|
+
beforeEach(() => {
|
|
159
|
+
tempDir = mkdtempSync(join(tmpdir(), 'latchkey-ext-int-'));
|
|
160
|
+
extensionsDir = join(tempDir, 'extensions');
|
|
161
|
+
mkdirSync(extensionsDir, { recursive: true });
|
|
162
|
+
logs = [];
|
|
163
|
+
errorLogs = [];
|
|
164
|
+
mockPermissionResult = true;
|
|
165
|
+
lastPermissionCheckRequest = undefined;
|
|
166
|
+
lastPermissionCheckPath = undefined;
|
|
167
|
+
});
|
|
168
|
+
afterEach(async () => {
|
|
169
|
+
if (gateway) {
|
|
170
|
+
await gateway.close();
|
|
171
|
+
gateway = undefined;
|
|
172
|
+
}
|
|
173
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
174
|
+
});
|
|
175
|
+
it('serves an extension that claims a request', async () => {
|
|
176
|
+
writeExtension(extensionsDir, 'echo.mjs', `export default (req, res) => {
|
|
177
|
+
if (req.url === '/extensions/echo') {
|
|
178
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
179
|
+
res.end(JSON.stringify({ url: req.url }));
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
};`);
|
|
184
|
+
gateway = await createTestGateway();
|
|
185
|
+
const response = await fetch('/extensions/echo');
|
|
186
|
+
expect(response.status).toBe(200);
|
|
187
|
+
const body = (await response.json());
|
|
188
|
+
expect(body.url).toBe('/extensions/echo');
|
|
189
|
+
});
|
|
190
|
+
it('falls through to 404 when no extension claims the request', async () => {
|
|
191
|
+
writeExtension(extensionsDir, 'narrow.mjs', `export default (req, res) => {
|
|
192
|
+
if (req.url === '/extensions/specific') {
|
|
193
|
+
res.writeHead(200);
|
|
194
|
+
res.end();
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
};`);
|
|
199
|
+
gateway = await createTestGateway();
|
|
200
|
+
const response = await fetch('/extensions/different');
|
|
201
|
+
expect(response.status).toBe(404);
|
|
202
|
+
});
|
|
203
|
+
it('returns 404 when no extensions are installed', async () => {
|
|
204
|
+
rmSync(extensionsDir, { recursive: true, force: true });
|
|
205
|
+
gateway = await createTestGateway();
|
|
206
|
+
const response = await fetch('/extensions/anything');
|
|
207
|
+
expect(response.status).toBe(404);
|
|
208
|
+
});
|
|
209
|
+
it('tries extensions in alphabetical order; first to return true wins', async () => {
|
|
210
|
+
writeExtension(extensionsDir, 'a-claim.mjs', `export default (req, res) => {
|
|
211
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
212
|
+
res.end('a');
|
|
213
|
+
return true;
|
|
214
|
+
};`);
|
|
215
|
+
writeExtension(extensionsDir, 'b-also-claim.mjs', `export default (req, res) => {
|
|
216
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
217
|
+
res.end('b');
|
|
218
|
+
return true;
|
|
219
|
+
};`);
|
|
220
|
+
gateway = await createTestGateway();
|
|
221
|
+
const response = await fetch('/anything');
|
|
222
|
+
expect(await response.text()).toBe('a');
|
|
223
|
+
});
|
|
224
|
+
it('continues to the next extension when one returns false', async () => {
|
|
225
|
+
writeExtension(extensionsDir, 'a-defer.mjs', `export default (req, res) => false;`);
|
|
226
|
+
writeExtension(extensionsDir, 'b-claim.mjs', `export default (req, res) => {
|
|
227
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
228
|
+
res.end('handled-by-b');
|
|
229
|
+
return true;
|
|
230
|
+
};`);
|
|
231
|
+
gateway = await createTestGateway();
|
|
232
|
+
const response = await fetch('/whatever');
|
|
233
|
+
expect(await response.text()).toBe('handled-by-b');
|
|
234
|
+
});
|
|
235
|
+
it('preserves query string for the handler and in the permission check', async () => {
|
|
236
|
+
writeExtension(extensionsDir, 'q.mjs', `export default (req, res) => {
|
|
237
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
238
|
+
res.end(req.url);
|
|
239
|
+
return true;
|
|
240
|
+
};`);
|
|
241
|
+
gateway = await createTestGateway();
|
|
242
|
+
const response = await fetch('/extensions/q?x=1&y=2');
|
|
243
|
+
expect(await response.text()).toBe('/extensions/q?x=1&y=2');
|
|
244
|
+
const synthesizedUrl = lastPermissionCheckRequest?.url ?? '';
|
|
245
|
+
expect(synthesizedUrl).toContain('?x=1&y=2');
|
|
246
|
+
expect(synthesizedUrl).toContain(EXTENSION_PLACEHOLDER_HOST);
|
|
247
|
+
});
|
|
248
|
+
it('returns 403 when the permission check denies the request, before any extension is invoked', async () => {
|
|
249
|
+
writeExtension(extensionsDir, 'should-not-run.mjs', `export default (req, res) => {
|
|
250
|
+
res.writeHead(200);
|
|
251
|
+
res.end('should not happen');
|
|
252
|
+
return true;
|
|
253
|
+
};`);
|
|
254
|
+
mockPermissionResult = false;
|
|
255
|
+
gateway = await createTestGateway();
|
|
256
|
+
const response = await fetch('/anything');
|
|
257
|
+
expect(response.status).toBe(403);
|
|
258
|
+
const body = (await response.json());
|
|
259
|
+
expect(body.error.toLowerCase()).toContain('not permitted');
|
|
260
|
+
});
|
|
261
|
+
it('returns 500 and logs when an extension throws', async () => {
|
|
262
|
+
writeExtension(extensionsDir, 'boom.mjs', `export default () => {
|
|
263
|
+
throw new Error('boom!');
|
|
264
|
+
};`);
|
|
265
|
+
gateway = await createTestGateway();
|
|
266
|
+
const response = await fetch('/extensions/boom');
|
|
267
|
+
expect(response.status).toBe(500);
|
|
268
|
+
expect(errorLogs.some((message) => message.includes('boom!'))).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
it('does not call later extensions after one throws', async () => {
|
|
271
|
+
writeExtension(extensionsDir, 'a-throws.mjs', `export default () => {
|
|
272
|
+
throw new Error('first failed');
|
|
273
|
+
};`);
|
|
274
|
+
writeExtension(extensionsDir, 'b-claim.mjs', `export default (req, res) => {
|
|
275
|
+
res.writeHead(200);
|
|
276
|
+
res.end('b');
|
|
277
|
+
return true;
|
|
278
|
+
};`);
|
|
279
|
+
gateway = await createTestGateway();
|
|
280
|
+
const response = await fetch('/path');
|
|
281
|
+
expect(response.status).toBe(500);
|
|
282
|
+
});
|
|
283
|
+
it('does not let extensions intercept the health endpoint', async () => {
|
|
284
|
+
writeExtension(extensionsDir, 'broad.mjs', `export default (req, res) => {
|
|
285
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
286
|
+
res.end('hijacked');
|
|
287
|
+
return true;
|
|
288
|
+
};`);
|
|
289
|
+
gateway = await createTestGateway();
|
|
290
|
+
const response = await fetch('/');
|
|
291
|
+
expect(response.status).toBe(200);
|
|
292
|
+
const body = (await response.json());
|
|
293
|
+
expect(body.status).toBe('ok');
|
|
294
|
+
});
|
|
295
|
+
it('does not offer /gateway/<malformed-url> requests to extensions', async () => {
|
|
296
|
+
writeExtension(extensionsDir, 'broad.mjs', `export default (req, res) => {
|
|
297
|
+
res.writeHead(200);
|
|
298
|
+
res.end('hijacked');
|
|
299
|
+
return true;
|
|
300
|
+
};`);
|
|
301
|
+
gateway = await createTestGateway();
|
|
302
|
+
const response = await fetch('/gateway/not-a-url');
|
|
303
|
+
expect(response.status).toBe(400);
|
|
304
|
+
});
|
|
305
|
+
it('does not offer /latchkey/ requests to extensions', async () => {
|
|
306
|
+
writeExtension(extensionsDir, 'broad.mjs', `export default (req, res) => {
|
|
307
|
+
res.writeHead(200);
|
|
308
|
+
res.end('hijacked');
|
|
309
|
+
return true;
|
|
310
|
+
};`);
|
|
311
|
+
gateway = await createTestGateway();
|
|
312
|
+
const response = await fetch('/latchkey/', {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: { 'Content-Type': 'application/json' },
|
|
315
|
+
body: JSON.stringify({ command: 'auth list' }),
|
|
316
|
+
});
|
|
317
|
+
// Should be handled by the latchkey RPC endpoint, not the extension.
|
|
318
|
+
expect(response.status).toBe(200);
|
|
319
|
+
});
|
|
320
|
+
it('fails startup when an extension is malformed', async () => {
|
|
321
|
+
writeExtension(extensionsDir, 'broken.mjs', 'syntax error (((');
|
|
322
|
+
await expect(createTestGateway()).rejects.toThrow(ExtensionLoadError);
|
|
323
|
+
});
|
|
324
|
+
it('still requires the gateway password for extension routes', async () => {
|
|
325
|
+
writeExtension(extensionsDir, 'pw.mjs', `export default (req, res) => {
|
|
326
|
+
res.writeHead(200);
|
|
327
|
+
res.end('ok');
|
|
328
|
+
return true;
|
|
329
|
+
};`);
|
|
330
|
+
gateway = await createTestGateway({ password: 'sekret' });
|
|
331
|
+
const denied = await fetch('/extensions/pw');
|
|
332
|
+
expect(denied.status).toBe(401);
|
|
333
|
+
const allowed = await fetch('/extensions/pw', {
|
|
334
|
+
headers: { [GATEWAY_PASSWORD_HEADER]: 'sekret' },
|
|
335
|
+
});
|
|
336
|
+
expect(allowed.status).toBe(200);
|
|
337
|
+
});
|
|
338
|
+
it('honors the permissions-override JWT for extension routes', async () => {
|
|
339
|
+
writeExtension(extensionsDir, 'perm.mjs', `export default (req, res) => {
|
|
340
|
+
res.writeHead(200);
|
|
341
|
+
res.end('ok');
|
|
342
|
+
return true;
|
|
343
|
+
};`);
|
|
344
|
+
const overridePath = join(tempDir, 'override-permissions.json');
|
|
345
|
+
writeFileSync(overridePath, '{"rules":[]}', 'utf-8');
|
|
346
|
+
const signingKey = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
|
|
347
|
+
const jwt = createPermissionsOverrideJwt(overridePath, signingKey);
|
|
348
|
+
gateway = await createTestGateway();
|
|
349
|
+
const response = await fetch('/extensions/perm', {
|
|
350
|
+
headers: { [PERMISSIONS_OVERRIDE_HEADER]: jwt },
|
|
351
|
+
});
|
|
352
|
+
expect(response.status).toBe(200);
|
|
353
|
+
expect(lastPermissionCheckPath).toBe(overridePath);
|
|
354
|
+
});
|
|
355
|
+
it('returns 401 for an invalid permissions-override JWT', async () => {
|
|
356
|
+
writeExtension(extensionsDir, 'p.mjs', `export default (req, res) => {
|
|
357
|
+
res.writeHead(200);
|
|
358
|
+
res.end('ok');
|
|
359
|
+
return true;
|
|
360
|
+
};`);
|
|
361
|
+
gateway = await createTestGateway();
|
|
362
|
+
const response = await fetch('/extensions/p', {
|
|
363
|
+
headers: { [PERMISSIONS_OVERRIDE_HEADER]: 'not.a.jwt' },
|
|
364
|
+
});
|
|
365
|
+
expect(response.status).toBe(401);
|
|
366
|
+
});
|
|
367
|
+
it('supports handlers that complete asynchronously after reading the request body', async () => {
|
|
368
|
+
writeExtension(extensionsDir, 'echo-body.mjs', `export default (req, res) =>
|
|
369
|
+
new Promise((resolve) => {
|
|
370
|
+
let body = '';
|
|
371
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
372
|
+
req.on('end', () => {
|
|
373
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
374
|
+
res.end(body.toUpperCase());
|
|
375
|
+
resolve(true);
|
|
376
|
+
});
|
|
377
|
+
});`);
|
|
378
|
+
gateway = await createTestGateway();
|
|
379
|
+
const response = await fetch('/extensions/echo-body', {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
body: 'hello',
|
|
382
|
+
});
|
|
383
|
+
expect(response.status).toBe(200);
|
|
384
|
+
expect(await response.text()).toBe('HELLO');
|
|
385
|
+
});
|
|
386
|
+
it('logs the extension request line with the actual response status', async () => {
|
|
387
|
+
writeExtension(extensionsDir, 'log.mjs', `export default (req, res) => {
|
|
388
|
+
res.writeHead(202);
|
|
389
|
+
res.end();
|
|
390
|
+
return true;
|
|
391
|
+
};`);
|
|
392
|
+
gateway = await createTestGateway();
|
|
393
|
+
const response = await fetch('/extensions/log');
|
|
394
|
+
await response.text();
|
|
395
|
+
expect(logs).toContain('GET /extensions/log -> 202 (extension)');
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
//# 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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"}
|
|
@@ -2,7 +2,11 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
2
2
|
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
+
import { parseCurlArgs } from '@imbue-ai/detent';
|
|
5
6
|
import { checkPermission, PermissionCheckError } from '../src/permissions.js';
|
|
7
|
+
function requestFromCurl(args) {
|
|
8
|
+
return parseCurlArgs(args);
|
|
9
|
+
}
|
|
6
10
|
describe('checkPermission', () => {
|
|
7
11
|
let tempDir;
|
|
8
12
|
beforeEach(() => {
|
|
@@ -13,7 +17,7 @@ describe('checkPermission', () => {
|
|
|
13
17
|
});
|
|
14
18
|
it('should allow requests when no config file exists', async () => {
|
|
15
19
|
const configPath = join(tempDir, 'nonexistent', 'permissions.json');
|
|
16
|
-
const result = await checkPermission(['-X', 'GET', 'https://api.example.com/anything'], configPath);
|
|
20
|
+
const result = await checkPermission(requestFromCurl(['-X', 'GET', 'https://api.example.com/anything']), configPath);
|
|
17
21
|
expect(result).toBe(true);
|
|
18
22
|
});
|
|
19
23
|
it('should allow requests matching a permission rule', async () => {
|
|
@@ -35,7 +39,7 @@ describe('checkPermission', () => {
|
|
|
35
39
|
},
|
|
36
40
|
rules: [{ 'example-api': ['example-read'] }],
|
|
37
41
|
}));
|
|
38
|
-
const result = await checkPermission(['https://api.example.com/users'], configPath);
|
|
42
|
+
const result = await checkPermission(requestFromCurl(['https://api.example.com/users']), configPath);
|
|
39
43
|
expect(result).toBe(true);
|
|
40
44
|
});
|
|
41
45
|
it('should deny requests not matching any permission rule', async () => {
|
|
@@ -57,7 +61,7 @@ describe('checkPermission', () => {
|
|
|
57
61
|
},
|
|
58
62
|
rules: [{ 'example-api': ['example-read'] }],
|
|
59
63
|
}));
|
|
60
|
-
const result = await checkPermission(['-X', 'POST', 'https://api.example.com/users'], configPath);
|
|
64
|
+
const result = await checkPermission(requestFromCurl(['-X', 'POST', 'https://api.example.com/users']), configPath);
|
|
61
65
|
expect(result).toBe(false);
|
|
62
66
|
});
|
|
63
67
|
it('should deny requests to unrecognized domains when rules exist', async () => {
|
|
@@ -79,20 +83,20 @@ describe('checkPermission', () => {
|
|
|
79
83
|
},
|
|
80
84
|
rules: [{ 'example-api': ['example-read'] }],
|
|
81
85
|
}));
|
|
82
|
-
const result = await checkPermission(['https://api.other.com/something'], configPath);
|
|
86
|
+
const result = await checkPermission(requestFromCurl(['https://api.other.com/something']), configPath);
|
|
83
87
|
expect(result).toBe(false);
|
|
84
88
|
});
|
|
85
89
|
it('should throw PermissionCheckError for invalid config files', async () => {
|
|
86
90
|
const configPath = join(tempDir, 'permissions.json');
|
|
87
91
|
writeFileSync(configPath, 'not valid json');
|
|
88
|
-
await expect(checkPermission(['https://api.example.com/anything'], configPath)).rejects.toThrow(PermissionCheckError);
|
|
92
|
+
await expect(checkPermission(requestFromCurl(['https://api.example.com/anything']), configPath)).rejects.toThrow(PermissionCheckError);
|
|
89
93
|
});
|
|
90
94
|
it('should accept URLs without a scheme (defaulting to http://) when rules allow them', async () => {
|
|
91
95
|
const configPath = join(tempDir, 'permissions.json');
|
|
92
96
|
writeFileSync(configPath, JSON.stringify({
|
|
93
97
|
rules: [{ any: ['any'] }],
|
|
94
98
|
}));
|
|
95
|
-
const result = await checkPermission(['www.seznam.cz'], configPath);
|
|
99
|
+
const result = await checkPermission(requestFromCurl(['www.seznam.cz']), configPath);
|
|
96
100
|
expect(result).toBe(true);
|
|
97
101
|
});
|
|
98
102
|
it('should throw PermissionCheckError when a rule references an unknown schema', async () => {
|
|
@@ -100,15 +104,15 @@ describe('checkPermission', () => {
|
|
|
100
104
|
writeFileSync(configPath, JSON.stringify({
|
|
101
105
|
rules: [{ 'non-existent-schema': ['any'] }],
|
|
102
106
|
}));
|
|
103
|
-
await expect(checkPermission(['https://api.example.com/anything'], configPath, true)).rejects.toThrow(PermissionCheckError);
|
|
107
|
+
await expect(checkPermission(requestFromCurl(['https://api.example.com/anything']), configPath, true)).rejects.toThrow(PermissionCheckError);
|
|
104
108
|
});
|
|
105
109
|
it('should allow all requests with the any/any rule', async () => {
|
|
106
110
|
const configPath = join(tempDir, 'permissions.json');
|
|
107
111
|
writeFileSync(configPath, JSON.stringify({
|
|
108
112
|
rules: [{ any: ['any'] }],
|
|
109
113
|
}));
|
|
110
|
-
const resultGet = await checkPermission(['https://api.example.com/anything'], configPath);
|
|
111
|
-
const resultPost = await checkPermission(['-X', 'POST', '-d', '{"key":"value"}', 'https://api.other.com/resource'], configPath);
|
|
114
|
+
const resultGet = await checkPermission(requestFromCurl(['https://api.example.com/anything']), configPath);
|
|
115
|
+
const resultPost = await checkPermission(requestFromCurl(['-X', 'POST', '-d', '{"key":"value"}', 'https://api.other.com/resource']), configPath);
|
|
112
116
|
expect(resultGet).toBe(true);
|
|
113
117
|
expect(resultPost).toBe(true);
|
|
114
118
|
});
|
|
@@ -117,7 +121,7 @@ describe('checkPermission', () => {
|
|
|
117
121
|
writeFileSync(configPath, JSON.stringify({
|
|
118
122
|
rules: [],
|
|
119
123
|
}));
|
|
120
|
-
const result = await checkPermission(['https://api.example.com/anything'], configPath);
|
|
124
|
+
const result = await checkPermission(requestFromCurl(['https://api.example.com/anything']), configPath);
|
|
121
125
|
expect(result).toBe(false);
|
|
122
126
|
});
|
|
123
127
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.test.js","sourceRoot":"","sources":["../../tests/permissions.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE9E,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,IAAI,OAAe,CAAC;IAEpB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,4BAA4B,CAAC,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAChE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,kBAAkB,CAAC,CAAC;QAEpE,MAAM,MAAM,GAAG,MAAM,eAAe,CAClC,CAAC,IAAI,EAAE,KAAK,EAAE,kCAAkC,CAAC,
|
|
1
|
+
{"version":3,"file":"permissions.test.js","sourceRoot":"","sources":["../../tests/permissions.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Optional `x-latchkey-gateway-permission-pointer` header support.
|
|
3
|
-
*
|
|
4
|
-
* The header carries a minimal HS256 JWT whose only payload field is
|
|
5
|
-
* `permissionsConfig`, an absolute path to a `permissions.json` file.
|
|
6
|
-
* When the gateway receives such a header on a `/gateway/...` request and
|
|
7
|
-
* the JWT is valid, it uses the referenced permissions config instead of
|
|
8
|
-
* the default one for that single request.
|
|
9
|
-
*
|
|
10
|
-
* The signing key is derived from the Latchkey encryption key via HKDF-like
|
|
11
|
-
* HMAC-SHA256 with a domain-separation label, so the encryption key itself
|
|
12
|
-
* is never used to sign or verify these JWTs directly.
|
|
13
|
-
*/
|
|
14
|
-
/**
|
|
15
|
-
* HTTP header used to carry the permission-pointer JWT. Lowercased to match
|
|
16
|
-
* how Node's `http.IncomingMessage.headers` exposes header names.
|
|
17
|
-
*/
|
|
18
|
-
export declare const PERMISSION_POINTER_HEADER = "x-latchkey-gateway-permission-pointer";
|
|
19
|
-
export declare class InvalidPermissionPointerError extends Error {
|
|
20
|
-
constructor(message: string);
|
|
21
|
-
}
|
|
22
|
-
export declare class PermissionPointerFileMissingError extends Error {
|
|
23
|
-
constructor(filePath: string);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Derive the HS256 signing key used for permission-pointer JWTs from the
|
|
27
|
-
* Latchkey encryption key. The encryption key is base64-encoded; the
|
|
28
|
-
* derived key is the raw HMAC-SHA256 output (32 bytes).
|
|
29
|
-
*/
|
|
30
|
-
export declare function derivePermissionPointerSigningKey(encryptionKeyBase64: string): Buffer;
|
|
31
|
-
/**
|
|
32
|
-
* Build a permission-pointer JWT for the given absolute path. The path is
|
|
33
|
-
* not validated here; callers that want to ensure the file exists must do
|
|
34
|
-
* so before calling this function.
|
|
35
|
-
*/
|
|
36
|
-
export declare function createPermissionPointerJwt(permissionsConfigPath: string, signingKey: Buffer): string;
|
|
37
|
-
interface PermissionPointerPayload {
|
|
38
|
-
readonly permissionsConfig: string;
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Verify a permission-pointer JWT and return its payload. Throws
|
|
42
|
-
* `InvalidPermissionPointerError` on any structural, signature, or content
|
|
43
|
-
* issue (i.e. anything that should be reported as "the JWT is invalid").
|
|
44
|
-
*
|
|
45
|
-
* This intentionally does not check that the referenced file exists; that
|
|
46
|
-
* concern is handled by `resolvePermissionPointer` so that file-system
|
|
47
|
-
* errors can be reported separately from JWT errors.
|
|
48
|
-
*/
|
|
49
|
-
export declare function verifyPermissionPointerJwt(token: string, signingKey: Buffer): PermissionPointerPayload;
|
|
50
|
-
/**
|
|
51
|
-
* Verify a permission-pointer JWT and additionally require the referenced
|
|
52
|
-
* file to exist as a regular file. Returns the absolute path on success.
|
|
53
|
-
*/
|
|
54
|
-
export declare function resolvePermissionPointer(token: string, signingKey: Buffer): string;
|
|
55
|
-
export {};
|
|
56
|
-
//# sourceMappingURL=permissionPointer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"permissionPointer.d.ts","sourceRoot":"","sources":["../../../src/gateway/permissionPointer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH;;;GAGG;AACH,eAAO,MAAM,yBAAyB,0CAA0C,CAAC;AAYjF,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,QAAQ,EAAE,MAAM;CAI7B;AAkBD;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAGrF;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,qBAAqB,EAAE,MAAM,EAC7B,UAAU,EAAE,MAAM,GACjB,MAAM,CAWR;AAED,UAAU,wBAAwB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;CACpC;AAsCD;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GACjB,wBAAwB,CAqD1B;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAMlF"}
|