latchkey 2.7.3 → 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.
Files changed (119) hide show
  1. package/README.md +55 -5
  2. package/dist/scripts/cryptFile.js +2 -2
  3. package/dist/scripts/cryptFile.js.map +1 -1
  4. package/dist/scripts/recordBrowserSession.js +3 -2
  5. package/dist/scripts/recordBrowserSession.js.map +1 -1
  6. package/dist/src/cli.js +5 -4
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/cliCommands.d.ts +1 -1
  9. package/dist/src/cliCommands.d.ts.map +1 -1
  10. package/dist/src/cliCommands.js +44 -6
  11. package/dist/src/cliCommands.js.map +1 -1
  12. package/dist/src/config.d.ts +34 -0
  13. package/dist/src/config.d.ts.map +1 -1
  14. package/dist/src/config.js +53 -0
  15. package/dist/src/config.js.map +1 -1
  16. package/dist/src/curlInjection.d.ts +1 -1
  17. package/dist/src/curlInjection.d.ts.map +1 -1
  18. package/dist/src/curlInjection.js +16 -1
  19. package/dist/src/curlInjection.js.map +1 -1
  20. package/dist/src/encryptedStorage.d.ts +9 -25
  21. package/dist/src/encryptedStorage.d.ts.map +1 -1
  22. package/dist/src/encryptedStorage.js +9 -52
  23. package/dist/src/encryptedStorage.js.map +1 -1
  24. package/dist/src/encryption.d.ts +45 -0
  25. package/dist/src/encryption.d.ts.map +1 -1
  26. package/dist/src/encryption.js +69 -0
  27. package/dist/src/encryption.js.map +1 -1
  28. package/dist/src/gateway/client.d.ts +12 -2
  29. package/dist/src/gateway/client.d.ts.map +1 -1
  30. package/dist/src/gateway/client.js +31 -4
  31. package/dist/src/gateway/client.js.map +1 -1
  32. package/dist/src/gateway/extensions.d.ts +59 -0
  33. package/dist/src/gateway/extensions.d.ts.map +1 -0
  34. package/dist/src/gateway/extensions.js +170 -0
  35. package/dist/src/gateway/extensions.js.map +1 -0
  36. package/dist/src/gateway/gatewayEndpoint.d.ts +22 -1
  37. package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
  38. package/dist/src/gateway/gatewayEndpoint.js +52 -15
  39. package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
  40. package/dist/src/gateway/password.d.ts +16 -0
  41. package/dist/src/gateway/password.d.ts.map +1 -0
  42. package/dist/src/gateway/password.js +24 -0
  43. package/dist/src/gateway/password.js.map +1 -0
  44. package/dist/src/gateway/permissionsOverride.d.ts +65 -0
  45. package/dist/src/gateway/permissionsOverride.d.ts.map +1 -0
  46. package/dist/src/gateway/permissionsOverride.js +171 -0
  47. package/dist/src/gateway/permissionsOverride.js.map +1 -0
  48. package/dist/src/gateway/server.d.ts.map +1 -1
  49. package/dist/src/gateway/server.js +100 -15
  50. package/dist/src/gateway/server.js.map +1 -1
  51. package/dist/src/index.d.ts +2 -2
  52. package/dist/src/index.d.ts.map +1 -1
  53. package/dist/src/index.js +2 -2
  54. package/dist/src/index.js.map +1 -1
  55. package/dist/src/oauthUtils.d.ts +11 -2
  56. package/dist/src/oauthUtils.d.ts.map +1 -1
  57. package/dist/src/oauthUtils.js +25 -4
  58. package/dist/src/oauthUtils.js.map +1 -1
  59. package/dist/src/permissions.d.ts +3 -6
  60. package/dist/src/permissions.d.ts.map +1 -1
  61. package/dist/src/permissions.js +6 -13
  62. package/dist/src/permissions.js.map +1 -1
  63. package/dist/src/serviceRegistry.d.ts.map +1 -1
  64. package/dist/src/serviceRegistry.js +2 -1
  65. package/dist/src/serviceRegistry.js.map +1 -1
  66. package/dist/src/services/index.d.ts +1 -0
  67. package/dist/src/services/index.d.ts.map +1 -1
  68. package/dist/src/services/index.js +1 -0
  69. package/dist/src/services/index.js.map +1 -1
  70. package/dist/src/services/notion-mcp.d.ts +29 -0
  71. package/dist/src/services/notion-mcp.d.ts.map +1 -0
  72. package/dist/src/services/notion-mcp.js +156 -0
  73. package/dist/src/services/notion-mcp.js.map +1 -0
  74. package/dist/src/services/notion.d.ts.map +1 -1
  75. package/dist/src/services/notion.js +3 -2
  76. package/dist/src/services/notion.js.map +1 -1
  77. package/dist/src/version.d.ts +1 -1
  78. package/dist/src/version.js +1 -1
  79. package/dist/tests/apiCredentialStore.test.js +2 -2
  80. package/dist/tests/apiCredentialStore.test.js.map +1 -1
  81. package/dist/tests/cli.test.js +98 -53
  82. package/dist/tests/cli.test.js.map +1 -1
  83. package/dist/tests/config.test.js +37 -0
  84. package/dist/tests/config.test.js.map +1 -1
  85. package/dist/tests/encryptedStorage.test.js +19 -39
  86. package/dist/tests/encryptedStorage.test.js.map +1 -1
  87. package/dist/tests/gateway.test.js +184 -7
  88. package/dist/tests/gateway.test.js.map +1 -1
  89. package/dist/tests/gatewayClient.test.js +74 -0
  90. package/dist/tests/gatewayClient.test.js.map +1 -1
  91. package/dist/tests/gatewayExtensions.test.d.ts +2 -0
  92. package/dist/tests/gatewayExtensions.test.d.ts.map +1 -0
  93. package/dist/tests/gatewayExtensions.test.js +398 -0
  94. package/dist/tests/gatewayExtensions.test.js.map +1 -0
  95. package/dist/tests/latchkeyEndpoint.test.js +7 -6
  96. package/dist/tests/latchkeyEndpoint.test.js.map +1 -1
  97. package/dist/tests/migrations.test.js +2 -2
  98. package/dist/tests/migrations.test.js.map +1 -1
  99. package/dist/tests/oauthUtils.test.d.ts +2 -0
  100. package/dist/tests/oauthUtils.test.d.ts.map +1 -0
  101. package/dist/tests/oauthUtils.test.js +63 -0
  102. package/dist/tests/oauthUtils.test.js.map +1 -0
  103. package/dist/tests/permissions.test.js +14 -10
  104. package/dist/tests/permissions.test.js.map +1 -1
  105. package/dist/tests/permissionsOverride.test.d.ts +2 -0
  106. package/dist/tests/permissionsOverride.test.d.ts.map +1 -0
  107. package/dist/tests/permissionsOverride.test.js +136 -0
  108. package/dist/tests/permissionsOverride.test.js.map +1 -0
  109. package/dist/tests/resolveEncryptionKey.test.d.ts +2 -0
  110. package/dist/tests/resolveEncryptionKey.test.d.ts.map +1 -0
  111. package/dist/tests/resolveEncryptionKey.test.js +26 -0
  112. package/dist/tests/resolveEncryptionKey.test.js.map +1 -0
  113. package/dist/tests/sharedOperations.test.js +34 -50
  114. package/dist/tests/sharedOperations.test.js.map +1 -1
  115. package/package.json +2 -2
  116. package/dist/tests/encryptedStorageKeyGeneration.test.d.ts +0 -2
  117. package/dist/tests/encryptedStorageKeyGeneration.test.d.ts.map +0 -1
  118. package/dist/tests/encryptedStorageKeyGeneration.test.js +0 -22
  119. package/dist/tests/encryptedStorageKeyGeneration.test.js.map +0 -1
@@ -0,0 +1,63 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { startOAuthCallbackServer, generateCodeVerifier, generateCodeChallenge, exchangeCodeForTokens, refreshAccessToken, OAuthTokenExchangeError, OAuthCallbackServerTimeoutError, } from '../src/oauthUtils.js';
3
+ import * as curl from '../src/curl.js';
4
+ afterEach(() => {
5
+ vi.restoreAllMocks();
6
+ });
7
+ void startOAuthCallbackServer;
8
+ void OAuthTokenExchangeError;
9
+ void OAuthCallbackServerTimeoutError;
10
+ describe('startOAuthCallbackServer', () => {
11
+ it.todo('add tests');
12
+ });
13
+ describe('generateCodeVerifier', () => {
14
+ it('just runs', () => {
15
+ generateCodeVerifier();
16
+ });
17
+ });
18
+ describe('generateCodeChallenge', () => {
19
+ it('just runs', () => {
20
+ generateCodeChallenge('dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk');
21
+ });
22
+ });
23
+ describe('exchangeCodeForTokens', () => {
24
+ it('with PKCE: includes code_verifier in body', () => {
25
+ const spy = vi.spyOn(curl, 'runCaptured').mockImplementation(() => {
26
+ throw new Error('STOP');
27
+ });
28
+ expect(() => exchangeCodeForTokens('https://api.notion.com/v1/oauth/token', 'auth-code-abc123', 'test-client-id', 'test-client-secret', 'http://localhost:12345/oauth2callback', 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk')).toThrow('STOP');
29
+ const args = spy.mock.calls[0][0];
30
+ const body = args[args.indexOf('-d') + 1];
31
+ expect(body).toBe('code=auth-code-abc123&client_id=test-client-id&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2Foauth2callback&grant_type=authorization_code&client_secret=test-client-secret&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk');
32
+ });
33
+ it('without PKCE: omits code_verifier from body', () => {
34
+ const spy = vi.spyOn(curl, 'runCaptured').mockImplementation(() => {
35
+ throw new Error('STOP');
36
+ });
37
+ expect(() => exchangeCodeForTokens('https://api.notion.com/v1/oauth/token', 'auth-code-abc123', 'test-client-id', 'test-client-secret', 'http://localhost:12345/oauth2callback')).toThrow('STOP');
38
+ const args = spy.mock.calls[0][0];
39
+ const body = args[args.indexOf('-d') + 1];
40
+ expect(body).toBe('code=auth-code-abc123&client_id=test-client-id&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2Foauth2callback&grant_type=authorization_code&client_secret=test-client-secret');
41
+ });
42
+ });
43
+ describe('refreshAccessToken', () => {
44
+ it('confidential client: includes client_secret in body', () => {
45
+ const spy = vi.spyOn(curl, 'runCaptured').mockImplementation(() => {
46
+ throw new Error('STOP');
47
+ });
48
+ expect(() => refreshAccessToken('https://api.notion.com/v1/oauth/token', 'refresh-token-xyz789', 'test-client-id', 'test-client-secret')).toThrow('STOP');
49
+ const args = spy.mock.calls[0][0];
50
+ const body = args[args.indexOf('-d') + 1];
51
+ expect(body).toBe('refresh_token=refresh-token-xyz789&client_id=test-client-id&grant_type=refresh_token&client_secret=test-client-secret');
52
+ });
53
+ it('public client: omits client_secret from body', () => {
54
+ const spy = vi.spyOn(curl, 'runCaptured').mockImplementation(() => {
55
+ throw new Error('STOP');
56
+ });
57
+ expect(() => refreshAccessToken('https://api.notion.com/v1/oauth/token', 'refresh-token-xyz789', 'test-client-id', '')).toThrow('STOP');
58
+ const args = spy.mock.calls[0][0];
59
+ const body = args[args.indexOf('-d') + 1];
60
+ expect(body).toBe('refresh_token=refresh-token-xyz789&client_id=test-client-id&grant_type=refresh_token');
61
+ });
62
+ });
63
+ //# sourceMappingURL=oauthUtils.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauthUtils.test.js","sourceRoot":"","sources":["../../tests/oauthUtils.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":"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"}
1
+ {"version":3,"file":"permissions.test.js","sourceRoot":"","sources":["../../tests/permissions.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=permissionsOverride.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionsOverride.test.d.ts","sourceRoot":"","sources":["../../tests/permissionsOverride.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,136 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { createHmac } from 'node:crypto';
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { tmpdir } from 'node:os';
6
+ import { createPermissionsOverrideJwt, derivePermissionsOverrideSigningKey, InvalidPermissionsOverrideError, PERMISSIONS_OVERRIDE_HEADER, PermissionsOverrideFileMissingError, resolvePermissionsOverride, verifyPermissionsOverrideJwt, } from '../src/gateway/permissionsOverride.js';
7
+ const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
8
+ const OTHER_ENCRYPTION_KEY = 'b3RoZXJrZXlvdGhlcmtleW90aGVya2V5b3RoZXJrZXk=';
9
+ describe('PERMISSIONS_OVERRIDE_HEADER', () => {
10
+ it('is the lowercase header name', () => {
11
+ expect(PERMISSIONS_OVERRIDE_HEADER).toBe('x-latchkey-gateway-permissions-override');
12
+ });
13
+ });
14
+ describe('derivePermissionsOverrideSigningKey', () => {
15
+ it('produces a deterministic 32-byte key', () => {
16
+ const a = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
17
+ const b = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
18
+ expect(a.length).toBe(32);
19
+ expect(a.equals(b)).toBe(true);
20
+ });
21
+ it('produces different keys for different master keys', () => {
22
+ const a = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
23
+ const b = derivePermissionsOverrideSigningKey(OTHER_ENCRYPTION_KEY);
24
+ expect(a.equals(b)).toBe(false);
25
+ });
26
+ it('does not equal the encryption key itself', () => {
27
+ const derived = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
28
+ const master = Buffer.from(TEST_ENCRYPTION_KEY, 'base64');
29
+ expect(derived.equals(master)).toBe(false);
30
+ });
31
+ });
32
+ describe('createPermissionsOverrideJwt / verifyPermissionsOverrideJwt', () => {
33
+ const signingKey = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
34
+ it('round-trips an absolute path', () => {
35
+ const token = createPermissionsOverrideJwt('/etc/latchkey/permissions.json', signingKey);
36
+ const payload = verifyPermissionsOverrideJwt(token, signingKey);
37
+ expect(payload).toEqual({ permissionsConfig: '/etc/latchkey/permissions.json' });
38
+ });
39
+ it('produces a three-segment JWT', () => {
40
+ const token = createPermissionsOverrideJwt('/x.json', signingKey);
41
+ expect(token.split('.')).toHaveLength(3);
42
+ });
43
+ it('uses HS256/JWT in the header', () => {
44
+ const token = createPermissionsOverrideJwt('/x.json', signingKey);
45
+ const header = token.split('.')[0];
46
+ const json = Buffer.from(header, 'base64url').toString('utf-8');
47
+ expect(JSON.parse(json)).toEqual({ alg: 'HS256', typ: 'JWT' });
48
+ });
49
+ it('payload contains only the permissionsConfig field', () => {
50
+ const token = createPermissionsOverrideJwt('/x.json', signingKey);
51
+ const payload = token.split('.')[1];
52
+ const json = Buffer.from(payload, 'base64url').toString('utf-8');
53
+ expect(JSON.parse(json)).toEqual({ permissionsConfig: '/x.json' });
54
+ });
55
+ it('rejects creation for non-absolute paths', () => {
56
+ expect(() => createPermissionsOverrideJwt('relative/path.json', signingKey)).toThrow(InvalidPermissionsOverrideError);
57
+ });
58
+ it('rejects tokens signed with a different key', () => {
59
+ const otherKey = derivePermissionsOverrideSigningKey(OTHER_ENCRYPTION_KEY);
60
+ const token = createPermissionsOverrideJwt('/x.json', otherKey);
61
+ expect(() => verifyPermissionsOverrideJwt(token, signingKey)).toThrow(InvalidPermissionsOverrideError);
62
+ });
63
+ it('rejects tokens with a tampered payload', () => {
64
+ const token = createPermissionsOverrideJwt('/x.json', signingKey);
65
+ const [header, , signature] = token.split('.');
66
+ const tamperedPayload = Buffer.from(JSON.stringify({ permissionsConfig: '/y.json' }), 'utf-8').toString('base64url');
67
+ const tampered = `${header}.${tamperedPayload}.${signature}`;
68
+ expect(() => verifyPermissionsOverrideJwt(tampered, signingKey)).toThrow(InvalidPermissionsOverrideError);
69
+ });
70
+ it('rejects tokens that do not have three segments', () => {
71
+ expect(() => verifyPermissionsOverrideJwt('a.b', signingKey)).toThrow(InvalidPermissionsOverrideError);
72
+ expect(() => verifyPermissionsOverrideJwt('a.b.c.d', signingKey)).toThrow(InvalidPermissionsOverrideError);
73
+ });
74
+ function base64Url(value) {
75
+ return Buffer.from(value, 'utf-8').toString('base64url');
76
+ }
77
+ function buildSignedToken(headerJson, payloadJson) {
78
+ const headerSegment = base64Url(headerJson);
79
+ const payloadSegment = base64Url(payloadJson);
80
+ const signature = createHmac('sha256', signingKey)
81
+ .update(`${headerSegment}.${payloadSegment}`)
82
+ .digest('base64url');
83
+ return `${headerSegment}.${payloadSegment}.${signature}`;
84
+ }
85
+ it('rejects tokens whose payload is not valid JSON', () => {
86
+ // Sign a payload that is base64url of "not-json" so we hit the JSON parse
87
+ // error rather than the signature mismatch error first.
88
+ const headerSegment = base64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
89
+ const payloadSegment = base64Url('not-json');
90
+ const signature = createHmac('sha256', signingKey)
91
+ .update(`${headerSegment}.${payloadSegment}`)
92
+ .digest('base64url');
93
+ const token = `${headerSegment}.${payloadSegment}.${signature}`;
94
+ expect(() => verifyPermissionsOverrideJwt(token, signingKey)).toThrow(InvalidPermissionsOverrideError);
95
+ });
96
+ it('rejects payloads without permissionsConfig', () => {
97
+ const token = buildSignedToken(JSON.stringify({ alg: 'HS256', typ: 'JWT' }), JSON.stringify({ other: '/x.json' }));
98
+ expect(() => verifyPermissionsOverrideJwt(token, signingKey)).toThrow(InvalidPermissionsOverrideError);
99
+ });
100
+ it('rejects payloads whose permissionsConfig is not absolute', () => {
101
+ const token = buildSignedToken(JSON.stringify({ alg: 'HS256', typ: 'JWT' }), JSON.stringify({ permissionsConfig: 'relative.json' }));
102
+ expect(() => verifyPermissionsOverrideJwt(token, signingKey)).toThrow(InvalidPermissionsOverrideError);
103
+ });
104
+ it('rejects headers with the wrong algorithm', () => {
105
+ const token = buildSignedToken(JSON.stringify({ alg: 'none', typ: 'JWT' }), JSON.stringify({ permissionsConfig: '/x.json' }));
106
+ expect(() => verifyPermissionsOverrideJwt(token, signingKey)).toThrow(InvalidPermissionsOverrideError);
107
+ });
108
+ });
109
+ describe('resolvePermissionsOverride', () => {
110
+ const signingKey = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
111
+ let tempDir;
112
+ beforeEach(() => {
113
+ tempDir = mkdtempSync(join(tmpdir(), 'latchkey-pp-test-'));
114
+ });
115
+ afterEach(() => {
116
+ rmSync(tempDir, { recursive: true, force: true });
117
+ });
118
+ it('returns the path when the file exists', () => {
119
+ const path = join(tempDir, 'permissions.json');
120
+ writeFileSync(path, '{}');
121
+ const token = createPermissionsOverrideJwt(path, signingKey);
122
+ expect(resolvePermissionsOverride(token, signingKey)).toBe(path);
123
+ });
124
+ it('throws PermissionsOverrideFileMissingError when the file is absent', () => {
125
+ const path = join(tempDir, 'does-not-exist.json');
126
+ const token = createPermissionsOverrideJwt(path, signingKey);
127
+ expect(() => resolvePermissionsOverride(token, signingKey)).toThrow(PermissionsOverrideFileMissingError);
128
+ });
129
+ it('throws PermissionsOverrideFileMissingError when the path is a directory', () => {
130
+ const path = join(tempDir, 'subdir');
131
+ mkdirSync(path);
132
+ const token = createPermissionsOverrideJwt(path, signingKey);
133
+ expect(() => resolvePermissionsOverride(token, signingKey)).toThrow(PermissionsOverrideFileMissingError);
134
+ });
135
+ });
136
+ //# sourceMappingURL=permissionsOverride.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"permissionsOverride.test.js","sourceRoot":"","sources":["../../tests/permissionsOverride.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=resolveEncryptionKey.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveEncryptionKey.test.d.ts","sourceRoot":"","sources":["../../tests/resolveEncryptionKey.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,26 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { EncryptionKeyLostError, generateKey, resolveEncryptionKey } from '../src/encryption.js';
3
+ vi.mock('../src/keychain.js', async (importOriginal) => {
4
+ const original = await importOriginal();
5
+ return {
6
+ ...original,
7
+ retrieveFromKeychain: () => Promise.resolve(null),
8
+ storeInKeychain: () => Promise.resolve(undefined),
9
+ };
10
+ });
11
+ describe('resolveEncryptionKey', () => {
12
+ it('returns the override verbatim and does not touch the keychain', async () => {
13
+ const override = generateKey();
14
+ await expect(resolveEncryptionKey({ encryptionKeyOverride: override })).resolves.toBe(override);
15
+ });
16
+ it('throws EncryptionKeyLostError when allowKeyGeneration is false and keychain has no key', async () => {
17
+ await expect(resolveEncryptionKey({ allowKeyGeneration: false })).rejects.toThrow(EncryptionKeyLostError);
18
+ });
19
+ it('generates a new key when allowKeyGeneration is true and keychain has no key', async () => {
20
+ await expect(resolveEncryptionKey({ allowKeyGeneration: true })).resolves.toMatch(/.+/);
21
+ });
22
+ it('generates a new key when allowKeyGeneration is unset and keychain has no key', async () => {
23
+ await expect(resolveEncryptionKey({})).resolves.toMatch(/.+/);
24
+ });
25
+ });
26
+ //# sourceMappingURL=resolveEncryptionKey.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveEncryptionKey.test.js","sourceRoot":"","sources":["../../tests/resolveEncryptionKey.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjG,EAAE,CAAC,IAAI,CAAC,oBAAoB,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE;IACrD,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAuC,CAAC;IAC7E,OAAO;QACL,GAAG,QAAQ;QACX,oBAAoB,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QACjD,eAAe,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;KAClD,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,+DAA+D,EAAE,KAAK,IAAI,EAAE;QAC7E,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAC/B,MAAM,MAAM,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wFAAwF,EAAE,KAAK,IAAI,EAAE;QACtG,MAAM,MAAM,CAAC,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAC/E,sBAAsB,CACvB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,KAAK,IAAI,EAAE;QAC3F,MAAM,MAAM,CAAC,oBAAoB,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8EAA8E,EAAE,KAAK,IAAI,EAAE;QAC5F,MAAM,MAAM,CAAC,oBAAoB,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -13,8 +13,8 @@ import { Config } from '../src/config.js';
13
13
  import { servicesList, servicesInfo, authList, authBrowser, authBrowserPrepare, UnknownServiceError, PreparationRequiredError, } from '../src/sharedOperations.js';
14
14
  import { BrowserFlowsNotSupportedError } from '../src/playwrightUtils.js';
15
15
  const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
16
- async function writeSecureFile(path, content) {
17
- const storage = await EncryptedStorage.create({ encryptionKeyOverride: TEST_ENCRYPTION_KEY });
16
+ function writeSecureFile(path, content) {
17
+ const storage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
18
18
  storage.writeFile(path, content);
19
19
  }
20
20
  function createMockService(overrides = {}) {
@@ -51,49 +51,47 @@ describe('operations', () => {
51
51
  afterEach(() => {
52
52
  rmSync(tempDir, { recursive: true, force: true });
53
53
  });
54
- async function createApiCredentialStore(credentialsData = {}) {
54
+ function createApiCredentialStore(credentialsData = {}) {
55
55
  const storePath = join(tempDir, 'credentials.json');
56
- await writeSecureFile(storePath, JSON.stringify(credentialsData));
57
- const encryptedStorage = await EncryptedStorage.create({
58
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
59
- });
56
+ writeSecureFile(storePath, JSON.stringify(credentialsData));
57
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
60
58
  return new ApiCredentialStore(storePath, encryptedStorage);
61
59
  }
62
60
  describe('servicesList', () => {
63
- it('should return sorted service names', async () => {
61
+ it('should return sorted service names', () => {
64
62
  const serviceA = createMockService({ name: 'zzz-service' });
65
63
  const serviceB = createMockService({ name: 'aaa-service' });
66
64
  const registry = new ServiceRegistry([serviceA, serviceB]);
67
- const store = await createApiCredentialStore();
65
+ const store = createApiCredentialStore();
68
66
  const config = createMockConfig();
69
67
  const result = servicesList(registry, store, config, {});
70
68
  expect(result).toEqual(['aaa-service', 'zzz-service']);
71
69
  });
72
- it('should filter to builtin services only', async () => {
70
+ it('should filter to builtin services only', () => {
73
71
  const builtinService = createMockService({ name: 'slack' });
74
72
  const registeredService = new RegisteredService('my-gitlab', 'https://gitlab.example.com');
75
73
  const registry = new ServiceRegistry([builtinService]);
76
74
  registry.addService(registeredService);
77
- const store = await createApiCredentialStore();
75
+ const store = createApiCredentialStore();
78
76
  const config = createMockConfig();
79
77
  const result = servicesList(registry, store, config, { builtin: true });
80
78
  expect(result).toContain('slack');
81
79
  expect(result).not.toContain('my-gitlab');
82
80
  });
83
- it('should filter to viable services with stored credentials', async () => {
81
+ it('should filter to viable services with stored credentials', () => {
84
82
  const service = createMockService({ name: 'slack', getSession: undefined });
85
83
  const registry = new ServiceRegistry([service]);
86
- const store = await createApiCredentialStore({
84
+ const store = createApiCredentialStore({
87
85
  slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
88
86
  });
89
87
  const config = createMockConfig({ browserDisabled: true });
90
88
  const result = servicesList(registry, store, config, { viable: true });
91
89
  expect(result).toContain('slack');
92
90
  });
93
- it('should exclude non-viable services without credentials or browser', async () => {
91
+ it('should exclude non-viable services without credentials or browser', () => {
94
92
  const service = createMockService({ name: 'nologin', getSession: undefined });
95
93
  const registry = new ServiceRegistry([service]);
96
- const store = await createApiCredentialStore({});
94
+ const store = createApiCredentialStore({});
97
95
  const config = createMockConfig();
98
96
  const result = servicesList(registry, store, config, { viable: true });
99
97
  expect(result).not.toContain('nologin');
@@ -103,7 +101,7 @@ describe('operations', () => {
103
101
  it('should return service info', async () => {
104
102
  const service = createMockService();
105
103
  const registry = new ServiceRegistry([service]);
106
- const store = await createApiCredentialStore();
104
+ const store = createApiCredentialStore();
107
105
  const config = createMockConfig();
108
106
  const info = await servicesInfo(registry, store, config, 'slack');
109
107
  expect(info.type).toBe('built-in');
@@ -115,14 +113,14 @@ describe('operations', () => {
115
113
  });
116
114
  it('should throw UnknownServiceError for unknown service', async () => {
117
115
  const registry = new ServiceRegistry([]);
118
- const store = await createApiCredentialStore();
116
+ const store = createApiCredentialStore();
119
117
  const config = createMockConfig();
120
118
  await expect(servicesInfo(registry, store, config, 'unknown')).rejects.toThrow(UnknownServiceError);
121
119
  });
122
120
  it('should exclude browser from authOptions when browser disabled', async () => {
123
121
  const service = createMockService();
124
122
  const registry = new ServiceRegistry([service]);
125
- const store = await createApiCredentialStore();
123
+ const store = createApiCredentialStore();
126
124
  const config = createMockConfig({ browserDisabled: true });
127
125
  const info = await servicesInfo(registry, store, config, 'slack');
128
126
  expect(info.authOptions).toEqual(['set']);
@@ -131,7 +129,7 @@ describe('operations', () => {
131
129
  const registeredService = new RegisteredService('my-gitlab', 'https://gitlab.example.com');
132
130
  const registry = new ServiceRegistry([]);
133
131
  registry.addService(registeredService);
134
- const store = await createApiCredentialStore();
132
+ const store = createApiCredentialStore();
135
133
  const config = createMockConfig();
136
134
  const info = await servicesInfo(registry, store, config, 'my-gitlab');
137
135
  expect(info.type).toBe('user-registered');
@@ -141,7 +139,7 @@ describe('operations', () => {
141
139
  it('should return stored credentials with status', async () => {
142
140
  const service = createMockService();
143
141
  const registry = new ServiceRegistry([service]);
144
- const store = await createApiCredentialStore({
142
+ const store = createApiCredentialStore({
145
143
  slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
146
144
  });
147
145
  const result = await authList(registry, store);
@@ -152,13 +150,13 @@ describe('operations', () => {
152
150
  });
153
151
  it('should return empty object when no credentials stored', async () => {
154
152
  const registry = new ServiceRegistry([]);
155
- const store = await createApiCredentialStore({});
153
+ const store = createApiCredentialStore({});
156
154
  const result = await authList(registry, store);
157
155
  expect(Object.keys(result)).toHaveLength(0);
158
156
  });
159
157
  it('should treat unknown services as valid', async () => {
160
158
  const registry = new ServiceRegistry([]);
161
- const store = await createApiCredentialStore({
159
+ const store = createApiCredentialStore({
162
160
  unknown: { objectType: 'rawCurl', curlArguments: ['-H', 'X-Token: secret'] },
163
161
  });
164
162
  const result = await authList(registry, store);
@@ -171,20 +169,16 @@ describe('operations', () => {
171
169
  describe('authBrowser', () => {
172
170
  it('should throw UnknownServiceError for unknown service', async () => {
173
171
  const registry = new ServiceRegistry([]);
174
- const store = await createApiCredentialStore();
175
- const encryptedStorage = await EncryptedStorage.create({
176
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
177
- });
172
+ const store = createApiCredentialStore();
173
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
178
174
  const config = createMockConfig();
179
175
  await expect(authBrowser(registry, store, encryptedStorage, config, 'unknown')).rejects.toThrow(UnknownServiceError);
180
176
  });
181
177
  it('should throw BrowserFlowsNotSupportedError when service has no browser support', async () => {
182
178
  const service = createMockService({ getSession: undefined });
183
179
  const registry = new ServiceRegistry([service]);
184
- const store = await createApiCredentialStore();
185
- const encryptedStorage = await EncryptedStorage.create({
186
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
187
- });
180
+ const store = createApiCredentialStore();
181
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
188
182
  const config = createMockConfig();
189
183
  await expect(authBrowser(registry, store, encryptedStorage, config, 'slack')).rejects.toThrow(BrowserFlowsNotSupportedError);
190
184
  });
@@ -196,10 +190,8 @@ describe('operations', () => {
196
190
  }),
197
191
  });
198
192
  const registry = new ServiceRegistry([service]);
199
- const store = await createApiCredentialStore({});
200
- const encryptedStorage = await EncryptedStorage.create({
201
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
202
- });
193
+ const store = createApiCredentialStore({});
194
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
203
195
  const config = createMockConfig();
204
196
  await expect(authBrowser(registry, store, encryptedStorage, config, 'slack')).rejects.toThrow(PreparationRequiredError);
205
197
  });
@@ -207,10 +199,8 @@ describe('operations', () => {
207
199
  describe('authBrowserPrepare', () => {
208
200
  it('should throw UnknownServiceError for unknown service', async () => {
209
201
  const registry = new ServiceRegistry([]);
210
- const store = await createApiCredentialStore();
211
- const encryptedStorage = await EncryptedStorage.create({
212
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
213
- });
202
+ const store = createApiCredentialStore();
203
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
214
204
  const config = createMockConfig();
215
205
  await expect(authBrowserPrepare(registry, store, encryptedStorage, config, 'unknown')).rejects.toThrow(UnknownServiceError);
216
206
  });
@@ -222,10 +212,8 @@ describe('operations', () => {
222
212
  }),
223
213
  });
224
214
  const registry = new ServiceRegistry([service]);
225
- const store = await createApiCredentialStore();
226
- const encryptedStorage = await EncryptedStorage.create({
227
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
228
- });
215
+ const store = createApiCredentialStore();
216
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
229
217
  const config = createMockConfig();
230
218
  const result = await authBrowserPrepare(registry, store, encryptedStorage, config, 'slack');
231
219
  expect(result.alreadyPrepared).toBe(true);
@@ -238,12 +226,10 @@ describe('operations', () => {
238
226
  }),
239
227
  });
240
228
  const registry = new ServiceRegistry([service]);
241
- const store = await createApiCredentialStore({
229
+ const store = createApiCredentialStore({
242
230
  slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
243
231
  });
244
- const encryptedStorage = await EncryptedStorage.create({
245
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
246
- });
232
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
247
233
  const config = createMockConfig();
248
234
  const result = await authBrowserPrepare(registry, store, encryptedStorage, config, 'slack');
249
235
  expect(result.alreadyPrepared).toBe(true);
@@ -251,10 +237,8 @@ describe('operations', () => {
251
237
  it('should return alreadyPrepared true when service has no getSession', async () => {
252
238
  const service = createMockService({ getSession: undefined });
253
239
  const registry = new ServiceRegistry([service]);
254
- const store = await createApiCredentialStore();
255
- const encryptedStorage = await EncryptedStorage.create({
256
- encryptionKeyOverride: TEST_ENCRYPTION_KEY,
257
- });
240
+ const store = createApiCredentialStore();
241
+ const encryptedStorage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
258
242
  const config = createMockConfig();
259
243
  const result = await authBrowserPrepare(registry, store, encryptedStorage, config, 'slack');
260
244
  expect(result.alreadyPrepared).toBe(true);