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.
Files changed (56) hide show
  1. package/README.md +6 -0
  2. package/dist/src/cli.js +0 -0
  3. package/dist/src/cliCommands.d.ts +1 -1
  4. package/dist/src/cliCommands.d.ts.map +1 -1
  5. package/dist/src/config.d.ts +5 -0
  6. package/dist/src/config.d.ts.map +1 -1
  7. package/dist/src/config.js +8 -0
  8. package/dist/src/config.js.map +1 -1
  9. package/dist/src/curlInjection.d.ts +1 -1
  10. package/dist/src/curlInjection.d.ts.map +1 -1
  11. package/dist/src/curlInjection.js +16 -1
  12. package/dist/src/curlInjection.js.map +1 -1
  13. package/dist/src/gateway/extensions.d.ts +59 -0
  14. package/dist/src/gateway/extensions.d.ts.map +1 -0
  15. package/dist/src/gateway/extensions.js +170 -0
  16. package/dist/src/gateway/extensions.js.map +1 -0
  17. package/dist/src/gateway/gatewayEndpoint.d.ts +11 -1
  18. package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
  19. package/dist/src/gateway/gatewayEndpoint.js +40 -39
  20. package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
  21. package/dist/src/gateway/permissionsOverride.d.ts +9 -0
  22. package/dist/src/gateway/permissionsOverride.d.ts.map +1 -1
  23. package/dist/src/gateway/permissionsOverride.js +14 -0
  24. package/dist/src/gateway/permissionsOverride.js.map +1 -1
  25. package/dist/src/gateway/server.d.ts.map +1 -1
  26. package/dist/src/gateway/server.js +66 -14
  27. package/dist/src/gateway/server.js.map +1 -1
  28. package/dist/src/permissions.d.ts +3 -6
  29. package/dist/src/permissions.d.ts.map +1 -1
  30. package/dist/src/permissions.js +6 -13
  31. package/dist/src/permissions.js.map +1 -1
  32. package/dist/src/version.d.ts +1 -1
  33. package/dist/src/version.js +1 -1
  34. package/dist/tests/cli.test.js +3 -0
  35. package/dist/tests/cli.test.js.map +1 -1
  36. package/dist/tests/gateway.test.js +14 -0
  37. package/dist/tests/gateway.test.js.map +1 -1
  38. package/dist/tests/gatewayExtensions.test.d.ts +2 -0
  39. package/dist/tests/gatewayExtensions.test.d.ts.map +1 -0
  40. package/dist/tests/gatewayExtensions.test.js +398 -0
  41. package/dist/tests/gatewayExtensions.test.js.map +1 -0
  42. package/dist/tests/permissions.test.js +14 -10
  43. package/dist/tests/permissions.test.js.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/src/gateway/permissionPointer.d.ts +0 -56
  46. package/dist/src/gateway/permissionPointer.d.ts.map +0 -1
  47. package/dist/src/gateway/permissionPointer.js +0 -171
  48. package/dist/src/gateway/permissionPointer.js.map +0 -1
  49. package/dist/tests/encryptedStorageKeyGeneration.test.d.ts +0 -2
  50. package/dist/tests/encryptedStorageKeyGeneration.test.d.ts.map +0 -1
  51. package/dist/tests/encryptedStorageKeyGeneration.test.js +0 -23
  52. package/dist/tests/encryptedStorageKeyGeneration.test.js.map +0 -1
  53. package/dist/tests/permissionPointer.test.d.ts +0 -2
  54. package/dist/tests/permissionPointer.test.d.ts.map +0 -1
  55. package/dist/tests/permissionPointer.test.js +0 -152
  56. package/dist/tests/permissionPointer.test.js.map +0 -1
@@ -1,171 +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
- import { createHmac, timingSafeEqual } from 'node:crypto';
15
- import { existsSync, statSync } from 'node:fs';
16
- import { isAbsolute } from 'node:path';
17
- /**
18
- * HTTP header used to carry the permission-pointer JWT. Lowercased to match
19
- * how Node's `http.IncomingMessage.headers` exposes header names.
20
- */
21
- export const PERMISSION_POINTER_HEADER = 'x-latchkey-gateway-permission-pointer';
22
- /**
23
- * Domain-separation label mixed into the HMAC that derives the JWT signing
24
- * key from the Latchkey encryption key. Changing this value invalidates all
25
- * previously issued tokens.
26
- */
27
- const SIGNING_KEY_DERIVATION_LABEL = 'latchkey:gateway:permission-pointer:v1';
28
- const JWT_HEADER = { alg: 'HS256', typ: 'JWT' };
29
- const JWT_HEADER_ENCODED = base64UrlEncode(Buffer.from(JSON.stringify(JWT_HEADER), 'utf-8'));
30
- export class InvalidPermissionPointerError extends Error {
31
- constructor(message) {
32
- super(message);
33
- this.name = 'InvalidPermissionPointerError';
34
- }
35
- }
36
- export class PermissionPointerFileMissingError extends Error {
37
- constructor(filePath) {
38
- super(`Permission pointer references missing or invalid file: ${filePath}`);
39
- this.name = 'PermissionPointerFileMissingError';
40
- }
41
- }
42
- function base64UrlEncode(buffer) {
43
- return buffer
44
- .toString('base64')
45
- .replace(/\+/g, '-')
46
- .replace(/\//g, '_')
47
- .replace(/=+$/, '');
48
- }
49
- function base64UrlDecode(value) {
50
- const padded = value
51
- .replace(/-/g, '+')
52
- .replace(/_/g, '/')
53
- .padEnd(value.length + ((4 - (value.length % 4)) % 4), '=');
54
- return Buffer.from(padded, 'base64');
55
- }
56
- /**
57
- * Derive the HS256 signing key used for permission-pointer JWTs from the
58
- * Latchkey encryption key. The encryption key is base64-encoded; the
59
- * derived key is the raw HMAC-SHA256 output (32 bytes).
60
- */
61
- export function derivePermissionPointerSigningKey(encryptionKeyBase64) {
62
- const masterKey = Buffer.from(encryptionKeyBase64, 'base64');
63
- return createHmac('sha256', masterKey).update(SIGNING_KEY_DERIVATION_LABEL).digest();
64
- }
65
- /**
66
- * Build a permission-pointer JWT for the given absolute path. The path is
67
- * not validated here; callers that want to ensure the file exists must do
68
- * so before calling this function.
69
- */
70
- export function createPermissionPointerJwt(permissionsConfigPath, signingKey) {
71
- if (!isAbsolute(permissionsConfigPath)) {
72
- throw new InvalidPermissionPointerError(`permissionsConfig path must be absolute: ${permissionsConfigPath}`);
73
- }
74
- const payload = { permissionsConfig: permissionsConfigPath };
75
- const payloadEncoded = base64UrlEncode(Buffer.from(JSON.stringify(payload), 'utf-8'));
76
- const signingInput = `${JWT_HEADER_ENCODED}.${payloadEncoded}`;
77
- const signature = createHmac('sha256', signingKey).update(signingInput).digest();
78
- return `${signingInput}.${base64UrlEncode(signature)}`;
79
- }
80
- function parsePayload(payloadEncoded) {
81
- let payloadJson;
82
- try {
83
- payloadJson = base64UrlDecode(payloadEncoded).toString('utf-8');
84
- }
85
- catch {
86
- throw new InvalidPermissionPointerError('Permission pointer payload is not valid base64url.');
87
- }
88
- let payload;
89
- try {
90
- payload = JSON.parse(payloadJson);
91
- }
92
- catch {
93
- throw new InvalidPermissionPointerError('Permission pointer payload is not valid JSON.');
94
- }
95
- if (typeof payload !== 'object' ||
96
- payload === null ||
97
- !('permissionsConfig' in payload) ||
98
- typeof payload.permissionsConfig !== 'string') {
99
- throw new InvalidPermissionPointerError("Permission pointer payload must contain a string 'permissionsConfig' field.");
100
- }
101
- const permissionsConfig = payload.permissionsConfig;
102
- if (!isAbsolute(permissionsConfig)) {
103
- throw new InvalidPermissionPointerError(`Permission pointer 'permissionsConfig' must be an absolute path: ${permissionsConfig}`);
104
- }
105
- return { permissionsConfig };
106
- }
107
- /**
108
- * Verify a permission-pointer JWT and return its payload. Throws
109
- * `InvalidPermissionPointerError` on any structural, signature, or content
110
- * issue (i.e. anything that should be reported as "the JWT is invalid").
111
- *
112
- * This intentionally does not check that the referenced file exists; that
113
- * concern is handled by `resolvePermissionPointer` so that file-system
114
- * errors can be reported separately from JWT errors.
115
- */
116
- export function verifyPermissionPointerJwt(token, signingKey) {
117
- const segments = token.split('.');
118
- if (segments.length !== 3) {
119
- throw new InvalidPermissionPointerError('Permission pointer JWT must have three dot-separated segments.');
120
- }
121
- const headerEncoded = segments[0];
122
- const payloadEncoded = segments[1];
123
- const signatureEncoded = segments[2];
124
- let headerJson;
125
- try {
126
- headerJson = base64UrlDecode(headerEncoded).toString('utf-8');
127
- }
128
- catch {
129
- throw new InvalidPermissionPointerError('Permission pointer header is not valid base64url.');
130
- }
131
- let header;
132
- try {
133
- header = JSON.parse(headerJson);
134
- }
135
- catch {
136
- throw new InvalidPermissionPointerError('Permission pointer header is not valid JSON.');
137
- }
138
- if (typeof header !== 'object' ||
139
- header === null ||
140
- header.alg !== 'HS256' ||
141
- header.typ !== 'JWT') {
142
- throw new InvalidPermissionPointerError("Permission pointer header must declare alg='HS256' and typ='JWT'.");
143
- }
144
- let providedSignature;
145
- try {
146
- providedSignature = base64UrlDecode(signatureEncoded);
147
- }
148
- catch {
149
- throw new InvalidPermissionPointerError('Permission pointer signature is not valid base64url.');
150
- }
151
- const expectedSignature = createHmac('sha256', signingKey)
152
- .update(`${headerEncoded}.${payloadEncoded}`)
153
- .digest();
154
- if (providedSignature.length !== expectedSignature.length ||
155
- !timingSafeEqual(providedSignature, expectedSignature)) {
156
- throw new InvalidPermissionPointerError('Permission pointer signature is invalid.');
157
- }
158
- return parsePayload(payloadEncoded);
159
- }
160
- /**
161
- * Verify a permission-pointer JWT and additionally require the referenced
162
- * file to exist as a regular file. Returns the absolute path on success.
163
- */
164
- export function resolvePermissionPointer(token, signingKey) {
165
- const { permissionsConfig } = verifyPermissionPointerJwt(token, signingKey);
166
- if (!existsSync(permissionsConfig) || !statSync(permissionsConfig).isFile()) {
167
- throw new PermissionPointerFileMissingError(permissionsConfig);
168
- }
169
- return permissionsConfig;
170
- }
171
- //# sourceMappingURL=permissionPointer.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissionPointer.js","sourceRoot":"","sources":["../../../src/gateway/permissionPointer.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=encryptedStorageKeyGeneration.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"encryptedStorageKeyGeneration.test.d.ts","sourceRoot":"","sources":["../../tests/encryptedStorageKeyGeneration.test.ts"],"names":[],"mappings":""}
@@ -1,23 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { EncryptedStorage } from '../src/encryptedStorage.js';
3
- import { EncryptionKeyLostError } from '../src/encryption.js';
4
- vi.mock('../src/keychain.js', async (importOriginal) => {
5
- const original = await importOriginal();
6
- return {
7
- ...original,
8
- retrieveFromKeychain: () => Promise.resolve(null),
9
- storeInKeychain: () => Promise.resolve(undefined),
10
- };
11
- });
12
- describe('EncryptedStorage key generation guard', () => {
13
- it('should throw EncryptionKeyLostError when allowKeyGeneration is false and keychain has no key', async () => {
14
- await expect(EncryptedStorage.create({ allowKeyGeneration: false })).rejects.toThrow(EncryptionKeyLostError);
15
- });
16
- it('should generate a new key when allowKeyGeneration is true and keychain has no key', async () => {
17
- await expect(EncryptedStorage.create({ allowKeyGeneration: true })).resolves.toBeDefined();
18
- });
19
- it('should generate a new key when allowKeyGeneration is not set and keychain has no key', async () => {
20
- await expect(EncryptedStorage.create({})).resolves.toBeDefined();
21
- });
22
- });
23
- //# sourceMappingURL=encryptedStorageKeyGeneration.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"encryptedStorageKeyGeneration.test.js","sourceRoot":"","sources":["../../tests/encryptedStorageKeyGeneration.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,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,uCAAuC,EAAE,GAAG,EAAE;IACrD,EAAE,CAAC,8FAA8F,EAAE,KAAK,IAAI,EAAE;QAC5G,MAAM,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAClF,sBAAsB,CACvB,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mFAAmF,EAAE,KAAK,IAAI,EAAE;QACjG,MAAM,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IAC7F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sFAAsF,EAAE,KAAK,IAAI,EAAE;QACpG,MAAM,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACnE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=permissionPointer.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissionPointer.test.d.ts","sourceRoot":"","sources":["../../tests/permissionPointer.test.ts"],"names":[],"mappings":""}
@@ -1,152 +0,0 @@
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 { createPermissionPointerJwt, derivePermissionPointerSigningKey, InvalidPermissionPointerError, PERMISSION_POINTER_HEADER, PermissionPointerFileMissingError, resolvePermissionPointer, verifyPermissionPointerJwt, } from '../src/gateway/permissionPointer.js';
7
- const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
8
- const OTHER_ENCRYPTION_KEY = 'b3RoZXJrZXlvdGhlcmtleW90aGVya2V5b3RoZXJrZXk=';
9
- describe('PERMISSION_POINTER_HEADER', () => {
10
- it('is the lowercase header name', () => {
11
- expect(PERMISSION_POINTER_HEADER).toBe('x-latchkey-gateway-permission-pointer');
12
- });
13
- });
14
- describe('derivePermissionPointerSigningKey', () => {
15
- it('produces a deterministic 32-byte key', () => {
16
- const a = derivePermissionPointerSigningKey(TEST_ENCRYPTION_KEY);
17
- const b = derivePermissionPointerSigningKey(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 = derivePermissionPointerSigningKey(TEST_ENCRYPTION_KEY);
23
- const b = derivePermissionPointerSigningKey(OTHER_ENCRYPTION_KEY);
24
- expect(a.equals(b)).toBe(false);
25
- });
26
- it('does not equal the encryption key itself', () => {
27
- const derived = derivePermissionPointerSigningKey(TEST_ENCRYPTION_KEY);
28
- const master = Buffer.from(TEST_ENCRYPTION_KEY, 'base64');
29
- expect(derived.equals(master)).toBe(false);
30
- });
31
- });
32
- describe('createPermissionPointerJwt / verifyPermissionPointerJwt', () => {
33
- const signingKey = derivePermissionPointerSigningKey(TEST_ENCRYPTION_KEY);
34
- it('round-trips an absolute path', () => {
35
- const token = createPermissionPointerJwt('/etc/latchkey/permissions.json', signingKey);
36
- const payload = verifyPermissionPointerJwt(token, signingKey);
37
- expect(payload).toEqual({ permissionsConfig: '/etc/latchkey/permissions.json' });
38
- });
39
- it('produces a three-segment JWT', () => {
40
- const token = createPermissionPointerJwt('/x.json', signingKey);
41
- expect(token.split('.')).toHaveLength(3);
42
- });
43
- it('uses HS256/JWT in the header', () => {
44
- const token = createPermissionPointerJwt('/x.json', signingKey);
45
- const header = token.split('.')[0];
46
- const json = Buffer.from(header.replace(/-/g, '+').replace(/_/g, '/'), 'base64').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 = createPermissionPointerJwt('/x.json', signingKey);
51
- const payload = token.split('.')[1];
52
- const json = Buffer.from(payload.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf-8');
53
- expect(JSON.parse(json)).toEqual({ permissionsConfig: '/x.json' });
54
- });
55
- it('rejects creation for non-absolute paths', () => {
56
- expect(() => createPermissionPointerJwt('relative/path.json', signingKey)).toThrow(InvalidPermissionPointerError);
57
- });
58
- it('rejects tokens signed with a different key', () => {
59
- const otherKey = derivePermissionPointerSigningKey(OTHER_ENCRYPTION_KEY);
60
- const token = createPermissionPointerJwt('/x.json', otherKey);
61
- expect(() => verifyPermissionPointerJwt(token, signingKey)).toThrow(InvalidPermissionPointerError);
62
- });
63
- it('rejects tokens with a tampered payload', () => {
64
- const token = createPermissionPointerJwt('/x.json', signingKey);
65
- const [header, , signature] = token.split('.');
66
- const tamperedPayload = Buffer.from(JSON.stringify({ permissionsConfig: '/y.json' }), 'utf-8')
67
- .toString('base64')
68
- .replace(/\+/g, '-')
69
- .replace(/\//g, '_')
70
- .replace(/=+$/, '');
71
- const tampered = `${header}.${tamperedPayload}.${signature}`;
72
- expect(() => verifyPermissionPointerJwt(tampered, signingKey)).toThrow(InvalidPermissionPointerError);
73
- });
74
- it('rejects tokens that do not have three segments', () => {
75
- expect(() => verifyPermissionPointerJwt('a.b', signingKey)).toThrow(InvalidPermissionPointerError);
76
- expect(() => verifyPermissionPointerJwt('a.b.c.d', signingKey)).toThrow(InvalidPermissionPointerError);
77
- });
78
- function base64Url(value) {
79
- return Buffer.from(value, 'utf-8')
80
- .toString('base64')
81
- .replace(/\+/g, '-')
82
- .replace(/\//g, '_')
83
- .replace(/=+$/, '');
84
- }
85
- function buildSignedToken(headerJson, payloadJson) {
86
- const headerSegment = base64Url(headerJson);
87
- const payloadSegment = base64Url(payloadJson);
88
- const signature = createHmac('sha256', signingKey)
89
- .update(`${headerSegment}.${payloadSegment}`)
90
- .digest()
91
- .toString('base64')
92
- .replace(/\+/g, '-')
93
- .replace(/\//g, '_')
94
- .replace(/=+$/, '');
95
- return `${headerSegment}.${payloadSegment}.${signature}`;
96
- }
97
- it('rejects tokens whose payload is not valid JSON', () => {
98
- // Sign a payload that is base64url of "not-json" so we hit the JSON parse
99
- // error rather than the signature mismatch error first.
100
- const headerSegment = base64Url(JSON.stringify({ alg: 'HS256', typ: 'JWT' }));
101
- const payloadSegment = base64Url('not-json');
102
- const signature = createHmac('sha256', signingKey)
103
- .update(`${headerSegment}.${payloadSegment}`)
104
- .digest()
105
- .toString('base64')
106
- .replace(/\+/g, '-')
107
- .replace(/\//g, '_')
108
- .replace(/=+$/, '');
109
- const token = `${headerSegment}.${payloadSegment}.${signature}`;
110
- expect(() => verifyPermissionPointerJwt(token, signingKey)).toThrow(InvalidPermissionPointerError);
111
- });
112
- it('rejects payloads without permissionsConfig', () => {
113
- const token = buildSignedToken(JSON.stringify({ alg: 'HS256', typ: 'JWT' }), JSON.stringify({ other: '/x.json' }));
114
- expect(() => verifyPermissionPointerJwt(token, signingKey)).toThrow(InvalidPermissionPointerError);
115
- });
116
- it('rejects payloads whose permissionsConfig is not absolute', () => {
117
- const token = buildSignedToken(JSON.stringify({ alg: 'HS256', typ: 'JWT' }), JSON.stringify({ permissionsConfig: 'relative.json' }));
118
- expect(() => verifyPermissionPointerJwt(token, signingKey)).toThrow(InvalidPermissionPointerError);
119
- });
120
- it('rejects headers with the wrong algorithm', () => {
121
- const token = buildSignedToken(JSON.stringify({ alg: 'none', typ: 'JWT' }), JSON.stringify({ permissionsConfig: '/x.json' }));
122
- expect(() => verifyPermissionPointerJwt(token, signingKey)).toThrow(InvalidPermissionPointerError);
123
- });
124
- });
125
- describe('resolvePermissionPointer', () => {
126
- const signingKey = derivePermissionPointerSigningKey(TEST_ENCRYPTION_KEY);
127
- let tempDir;
128
- beforeEach(() => {
129
- tempDir = mkdtempSync(join(tmpdir(), 'latchkey-pp-test-'));
130
- });
131
- afterEach(() => {
132
- rmSync(tempDir, { recursive: true, force: true });
133
- });
134
- it('returns the path when the file exists', () => {
135
- const path = join(tempDir, 'permissions.json');
136
- writeFileSync(path, '{}');
137
- const token = createPermissionPointerJwt(path, signingKey);
138
- expect(resolvePermissionPointer(token, signingKey)).toBe(path);
139
- });
140
- it('throws PermissionPointerFileMissingError when the file is absent', () => {
141
- const path = join(tempDir, 'does-not-exist.json');
142
- const token = createPermissionPointerJwt(path, signingKey);
143
- expect(() => resolvePermissionPointer(token, signingKey)).toThrow(PermissionPointerFileMissingError);
144
- });
145
- it('throws PermissionPointerFileMissingError when the path is a directory', () => {
146
- const path = join(tempDir, 'subdir');
147
- mkdirSync(path);
148
- const token = createPermissionPointerJwt(path, signingKey);
149
- expect(() => resolvePermissionPointer(token, signingKey)).toThrow(PermissionPointerFileMissingError);
150
- });
151
- });
152
- //# sourceMappingURL=permissionPointer.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissionPointer.test.js","sourceRoot":"","sources":["../../tests/permissionPointer.test.ts"],"names":[],"mappings":"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"}