latchkey 2.7.2 → 2.8.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 +49 -5
- package/dist/scripts/cryptFile.js +2 -2
- package/dist/scripts/cryptFile.js.map +1 -1
- package/dist/scripts/recordBrowserSession.js +3 -2
- package/dist/scripts/recordBrowserSession.js.map +1 -1
- package/dist/src/cli.js +5 -4
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/cliCommands.js +44 -6
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/config.d.ts +29 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +45 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/encryptedStorage.d.ts +9 -25
- package/dist/src/encryptedStorage.d.ts.map +1 -1
- package/dist/src/encryptedStorage.js +9 -52
- package/dist/src/encryptedStorage.js.map +1 -1
- package/dist/src/encryption.d.ts +45 -0
- package/dist/src/encryption.d.ts.map +1 -1
- package/dist/src/encryption.js +69 -0
- package/dist/src/encryption.js.map +1 -1
- package/dist/src/gateway/client.d.ts +12 -2
- package/dist/src/gateway/client.d.ts.map +1 -1
- package/dist/src/gateway/client.js +31 -4
- package/dist/src/gateway/client.js.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.d.ts +11 -0
- package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.js +40 -4
- package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
- package/dist/src/gateway/password.d.ts +16 -0
- package/dist/src/gateway/password.d.ts.map +1 -0
- package/dist/src/gateway/password.js +24 -0
- package/dist/src/gateway/password.js.map +1 -0
- package/dist/src/gateway/permissionPointer.d.ts +56 -0
- package/dist/src/gateway/permissionPointer.d.ts.map +1 -0
- package/dist/src/gateway/permissionPointer.js +171 -0
- package/dist/src/gateway/permissionPointer.js.map +1 -0
- package/dist/src/gateway/permissionsOverride.d.ts +56 -0
- package/dist/src/gateway/permissionsOverride.d.ts.map +1 -0
- package/dist/src/gateway/permissionsOverride.js +157 -0
- package/dist/src/gateway/permissionsOverride.js.map +1 -0
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +34 -1
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/oauthUtils.d.ts +11 -2
- package/dist/src/oauthUtils.d.ts.map +1 -1
- package/dist/src/oauthUtils.js +25 -4
- package/dist/src/oauthUtils.js.map +1 -1
- package/dist/src/serviceRegistry.d.ts.map +1 -1
- package/dist/src/serviceRegistry.js +2 -1
- package/dist/src/serviceRegistry.js.map +1 -1
- package/dist/src/services/index.d.ts +1 -0
- package/dist/src/services/index.d.ts.map +1 -1
- package/dist/src/services/index.js +1 -0
- package/dist/src/services/index.js.map +1 -1
- package/dist/src/services/notion-mcp.d.ts +29 -0
- package/dist/src/services/notion-mcp.d.ts.map +1 -0
- package/dist/src/services/notion-mcp.js +156 -0
- package/dist/src/services/notion-mcp.js.map +1 -0
- package/dist/src/services/notion.d.ts +1 -1
- package/dist/src/services/notion.d.ts.map +1 -1
- package/dist/src/services/notion.js +5 -4
- package/dist/src/services/notion.js.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/tests/apiCredentialStore.test.js +2 -2
- package/dist/tests/apiCredentialStore.test.js.map +1 -1
- package/dist/tests/cli.test.js +95 -53
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/config.test.js +37 -0
- package/dist/tests/config.test.js.map +1 -1
- package/dist/tests/encryptedStorage.test.js +19 -39
- package/dist/tests/encryptedStorage.test.js.map +1 -1
- package/dist/tests/encryptedStorageKeyGeneration.test.js +2 -1
- package/dist/tests/encryptedStorageKeyGeneration.test.js.map +1 -1
- package/dist/tests/gateway.test.js +170 -7
- package/dist/tests/gateway.test.js.map +1 -1
- package/dist/tests/gatewayClient.test.js +74 -0
- package/dist/tests/gatewayClient.test.js.map +1 -1
- package/dist/tests/latchkeyEndpoint.test.js +7 -6
- package/dist/tests/latchkeyEndpoint.test.js.map +1 -1
- package/dist/tests/migrations.test.js +2 -2
- package/dist/tests/migrations.test.js.map +1 -1
- package/dist/tests/oauthUtils.test.d.ts +2 -0
- package/dist/tests/oauthUtils.test.d.ts.map +1 -0
- package/dist/tests/oauthUtils.test.js +63 -0
- package/dist/tests/oauthUtils.test.js.map +1 -0
- package/dist/tests/permissionPointer.test.d.ts +2 -0
- package/dist/tests/permissionPointer.test.d.ts.map +1 -0
- package/dist/tests/permissionPointer.test.js +152 -0
- package/dist/tests/permissionPointer.test.js.map +1 -0
- package/dist/tests/permissionsOverride.test.d.ts +2 -0
- package/dist/tests/permissionsOverride.test.d.ts.map +1 -0
- package/dist/tests/permissionsOverride.test.js +136 -0
- package/dist/tests/permissionsOverride.test.js.map +1 -0
- package/dist/tests/resolveEncryptionKey.test.d.ts +2 -0
- package/dist/tests/resolveEncryptionKey.test.d.ts.map +1 -0
- package/dist/tests/resolveEncryptionKey.test.js +26 -0
- package/dist/tests/resolveEncryptionKey.test.js.map +1 -0
- package/dist/tests/sharedOperations.test.js +34 -50
- package/dist/tests/sharedOperations.test.js.map +1 -1
- package/package.json +2 -2
package/dist/tests/cli.test.js
CHANGED
|
@@ -16,17 +16,18 @@ import { NoCurlCredentialsNotSupportedError, Service } from '../src/services/cor
|
|
|
16
16
|
import { RegisteredService } from '../src/services/core/registered.js';
|
|
17
17
|
import { GITLAB } from '../src/services/gitlab.js';
|
|
18
18
|
import { GITHUB } from '../src/services/github.js';
|
|
19
|
+
import { derivePermissionsOverrideSigningKey, verifyPermissionsOverrideJwt, } from '../src/gateway/permissionsOverride.js';
|
|
19
20
|
import { TELEGRAM } from '../src/services/telegram.js';
|
|
20
21
|
import { deleteRegisteredService, loadRegisteredServices, saveRegisteredService, } from '../src/configDataStore.js';
|
|
21
22
|
import { loadRegisteredServicesIntoServiceRegistry } from '../src/serviceRegistry.js';
|
|
22
23
|
// Use a fixed test key for deterministic test behavior (32 bytes = 256 bits, base64 encoded)
|
|
23
24
|
const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
|
|
24
|
-
|
|
25
|
-
const storage =
|
|
25
|
+
function writeSecureFile(path, content) {
|
|
26
|
+
const storage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
|
|
26
27
|
storage.writeFile(path, content);
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
const storage =
|
|
29
|
+
function readSecureFile(path) {
|
|
30
|
+
const storage = new EncryptedStorage(TEST_ENCRYPTION_KEY);
|
|
30
31
|
return storage.readFile(path);
|
|
31
32
|
}
|
|
32
33
|
function getCliPath() {
|
|
@@ -301,6 +302,9 @@ describe('CLI commands with dependency injection', () => {
|
|
|
301
302
|
gatewayUrl: overrides.gatewayUrl ?? null,
|
|
302
303
|
gatewayListenHost: overrides.gatewayListenHost ?? 'localhost',
|
|
303
304
|
gatewayListenPort: overrides.gatewayListenPort ?? 1989,
|
|
305
|
+
gatewayPassword: overrides.gatewayPassword ?? null,
|
|
306
|
+
gatewayListenPassword: overrides.gatewayListenPassword ?? null,
|
|
307
|
+
gatewayPermissionsOverride: overrides.gatewayPermissionsOverride ?? null,
|
|
304
308
|
checkSensitiveFilePermissions: () => undefined,
|
|
305
309
|
checkSystemPrerequisites: () => undefined,
|
|
306
310
|
};
|
|
@@ -403,7 +407,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
403
407
|
});
|
|
404
408
|
it('should include services with stored credentials when using --viable', async () => {
|
|
405
409
|
const storePath = join(tempDir, 'credentials.json');
|
|
406
|
-
|
|
410
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
407
411
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
408
412
|
}));
|
|
409
413
|
const deps = createMockDependencies();
|
|
@@ -414,7 +418,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
414
418
|
});
|
|
415
419
|
it('should include services with browser auth when using --viable', async () => {
|
|
416
420
|
const storePath = join(tempDir, 'credentials.json');
|
|
417
|
-
|
|
421
|
+
writeSecureFile(storePath, '{}');
|
|
418
422
|
// The default mock slack service has getSession defined, so it supports browser auth
|
|
419
423
|
// Ensure a graphical environment is available so browser auth is considered viable
|
|
420
424
|
const originalDisplay = process.env.DISPLAY;
|
|
@@ -437,7 +441,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
437
441
|
});
|
|
438
442
|
it('should exclude services without credentials or browser auth when using --viable', async () => {
|
|
439
443
|
const storePath = join(tempDir, 'credentials.json');
|
|
440
|
-
|
|
444
|
+
writeSecureFile(storePath, '{}');
|
|
441
445
|
const noLoginService = {
|
|
442
446
|
name: 'nologin',
|
|
443
447
|
displayName: 'No Login Service',
|
|
@@ -463,7 +467,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
463
467
|
});
|
|
464
468
|
it('should exclude browser-capable services when browser is disabled and no credentials with --viable', async () => {
|
|
465
469
|
const storePath = join(tempDir, 'credentials.json');
|
|
466
|
-
|
|
470
|
+
writeSecureFile(storePath, '{}');
|
|
467
471
|
const deps = createMockDependencies({
|
|
468
472
|
config: createMockConfig({ browserDisabled: true }),
|
|
469
473
|
});
|
|
@@ -474,7 +478,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
474
478
|
});
|
|
475
479
|
it('should exclude browser-capable services when no graphical environment and no credentials with --viable', async () => {
|
|
476
480
|
const storePath = join(tempDir, 'credentials.json');
|
|
477
|
-
|
|
481
|
+
writeSecureFile(storePath, '{}');
|
|
478
482
|
const originalPlatform = process.platform;
|
|
479
483
|
const originalDisplay = process.env.DISPLAY;
|
|
480
484
|
const originalWayland = process.env.WAYLAND_DISPLAY;
|
|
@@ -506,7 +510,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
506
510
|
});
|
|
507
511
|
it('should include services with credentials even when no graphical environment with --viable', async () => {
|
|
508
512
|
const storePath = join(tempDir, 'credentials.json');
|
|
509
|
-
|
|
513
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
510
514
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
511
515
|
}));
|
|
512
516
|
const originalPlatform = process.platform;
|
|
@@ -540,7 +544,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
540
544
|
});
|
|
541
545
|
it('should include services with credentials even when browser is disabled with --viable', async () => {
|
|
542
546
|
const storePath = join(tempDir, 'credentials.json');
|
|
543
|
-
|
|
547
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
544
548
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
545
549
|
}));
|
|
546
550
|
const deps = createMockDependencies({
|
|
@@ -553,7 +557,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
553
557
|
});
|
|
554
558
|
it('should combine --builtin and --viable filters', async () => {
|
|
555
559
|
const storePath = join(tempDir, 'credentials.json');
|
|
556
|
-
|
|
560
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
557
561
|
'my-gitlab': {
|
|
558
562
|
objectType: 'rawCurl',
|
|
559
563
|
curlArguments: ['-H', 'PRIVATE-TOKEN: token'],
|
|
@@ -587,7 +591,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
587
591
|
describe('services info command', () => {
|
|
588
592
|
it('should show login options, credentials status, and developer notes', async () => {
|
|
589
593
|
const storePath = join(tempDir, 'credentials.json');
|
|
590
|
-
|
|
594
|
+
writeSecureFile(storePath, '{}');
|
|
591
595
|
const deps = createMockDependencies();
|
|
592
596
|
await runCommand(['services', 'info', 'slack'], deps);
|
|
593
597
|
expect(logs).toHaveLength(1);
|
|
@@ -601,7 +605,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
601
605
|
});
|
|
602
606
|
it('should show auth set only for services without browser login', async () => {
|
|
603
607
|
const storePath = join(tempDir, 'credentials.json');
|
|
604
|
-
|
|
608
|
+
writeSecureFile(storePath, '{}');
|
|
605
609
|
const noLoginService = {
|
|
606
610
|
name: 'nologin',
|
|
607
611
|
displayName: 'No Login Service',
|
|
@@ -626,7 +630,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
626
630
|
});
|
|
627
631
|
it('should not list browser in authOptions when LATCHKEY_DISABLE_BROWSER is in effect', async () => {
|
|
628
632
|
const storePath = join(tempDir, 'credentials.json');
|
|
629
|
-
|
|
633
|
+
writeSecureFile(storePath, '{}');
|
|
630
634
|
const deps = createMockDependencies({
|
|
631
635
|
config: createMockConfig({ browserDisabled: true }),
|
|
632
636
|
});
|
|
@@ -636,7 +640,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
636
640
|
});
|
|
637
641
|
it('should show valid credentials status when credentials are valid', async () => {
|
|
638
642
|
const storePath = join(tempDir, 'credentials.json');
|
|
639
|
-
|
|
643
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
640
644
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
641
645
|
}));
|
|
642
646
|
const deps = createMockDependencies();
|
|
@@ -652,7 +656,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
652
656
|
});
|
|
653
657
|
it('should show type as registered for registered services', async () => {
|
|
654
658
|
const storePath = join(tempDir, 'credentials.json');
|
|
655
|
-
|
|
659
|
+
writeSecureFile(storePath, '{}');
|
|
656
660
|
const registeredService = new RegisteredService('my-gitlab', 'https://gitlab.example.com');
|
|
657
661
|
const deps = createMockDependencies();
|
|
658
662
|
deps.registry.addService(registeredService);
|
|
@@ -664,12 +668,12 @@ describe('CLI commands with dependency injection', () => {
|
|
|
664
668
|
describe('clear command', () => {
|
|
665
669
|
it('should delete credentials for a service', async () => {
|
|
666
670
|
const storePath = join(tempDir, 'credentials.json');
|
|
667
|
-
|
|
671
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
668
672
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
669
673
|
}));
|
|
670
674
|
const deps = createMockDependencies();
|
|
671
675
|
await runCommand(['auth', 'clear', 'slack'], deps);
|
|
672
|
-
const storedData = JSON.parse(
|
|
676
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
673
677
|
expect(storedData.slack).toBeUndefined();
|
|
674
678
|
});
|
|
675
679
|
it('should return error for unknown service', async () => {
|
|
@@ -680,13 +684,13 @@ describe('CLI commands with dependency injection', () => {
|
|
|
680
684
|
});
|
|
681
685
|
it('should preserve other services when clearing one', async () => {
|
|
682
686
|
const storePath = join(tempDir, 'credentials.json');
|
|
683
|
-
|
|
687
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
684
688
|
slack: { objectType: 'slack', token: 'slack-token', dCookie: 'slack-cookie' },
|
|
685
689
|
discord: { objectType: 'authorizationBare', token: 'discord-token' },
|
|
686
690
|
}));
|
|
687
691
|
const deps = createMockDependencies();
|
|
688
692
|
await runCommand(['auth', 'clear', 'slack'], deps);
|
|
689
|
-
const storedData = JSON.parse(
|
|
693
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
690
694
|
expect(storedData.slack).toBeUndefined();
|
|
691
695
|
expect(storedData.discord).toBeDefined();
|
|
692
696
|
expect(storedData.discord?.token).toBe('discord-token');
|
|
@@ -694,8 +698,8 @@ describe('CLI commands with dependency injection', () => {
|
|
|
694
698
|
it('should delete both store and browser state with -y flag', async () => {
|
|
695
699
|
const storePath = join(tempDir, 'credentials.json');
|
|
696
700
|
const browserStatePath = join(tempDir, 'browser_state.json');
|
|
697
|
-
|
|
698
|
-
|
|
701
|
+
writeSecureFile(storePath, JSON.stringify({ slack: { objectType: 'slack', token: 'test', dCookie: 'test' } }));
|
|
702
|
+
writeSecureFile(browserStatePath, '{}');
|
|
699
703
|
const deps = createMockDependencies();
|
|
700
704
|
await runCommand(['auth', 'clear', '-y'], deps);
|
|
701
705
|
expect(existsSync(storePath)).toBe(false);
|
|
@@ -705,7 +709,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
705
709
|
describe('auth list command', () => {
|
|
706
710
|
it('should list stored credentials with their status', async () => {
|
|
707
711
|
const storePath = join(tempDir, 'credentials.json');
|
|
708
|
-
|
|
712
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
709
713
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
710
714
|
}));
|
|
711
715
|
const deps = createMockDependencies();
|
|
@@ -719,7 +723,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
719
723
|
});
|
|
720
724
|
it('should output empty object when no credentials are stored', async () => {
|
|
721
725
|
const storePath = join(tempDir, 'credentials.json');
|
|
722
|
-
|
|
726
|
+
writeSecureFile(storePath, '{}');
|
|
723
727
|
const deps = createMockDependencies();
|
|
724
728
|
await runCommand(['auth', 'list'], deps);
|
|
725
729
|
expect(logs).toHaveLength(1);
|
|
@@ -728,7 +732,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
728
732
|
});
|
|
729
733
|
it('should treat unknown services as valid', async () => {
|
|
730
734
|
const storePath = join(tempDir, 'credentials.json');
|
|
731
|
-
|
|
735
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
732
736
|
unknown: { objectType: 'rawCurl', curlArguments: ['-H', 'X-Token: secret'] },
|
|
733
737
|
}));
|
|
734
738
|
const deps = createMockDependencies();
|
|
@@ -744,11 +748,11 @@ describe('CLI commands with dependency injection', () => {
|
|
|
744
748
|
describe('auth set command', () => {
|
|
745
749
|
it('should store raw curl credentials', async () => {
|
|
746
750
|
const storePath = join(tempDir, 'credentials.json');
|
|
747
|
-
|
|
751
|
+
writeSecureFile(storePath, '{}');
|
|
748
752
|
const deps = createMockDependencies();
|
|
749
753
|
await runCommand(['auth', 'set', 'slack', '-H', 'X-Token: secret', '-H', 'X-Other: value'], deps);
|
|
750
754
|
expect(logs).toContain('Credentials stored.');
|
|
751
|
-
const storedData = JSON.parse(
|
|
755
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
752
756
|
expect(storedData.slack).toEqual({
|
|
753
757
|
objectType: 'rawCurl',
|
|
754
758
|
curlArguments: ['-H', 'X-Token: secret', '-H', 'X-Other: value'],
|
|
@@ -771,13 +775,13 @@ describe('CLI commands with dependency injection', () => {
|
|
|
771
775
|
});
|
|
772
776
|
it('should overwrite existing credentials', async () => {
|
|
773
777
|
const storePath = join(tempDir, 'credentials.json');
|
|
774
|
-
|
|
778
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
775
779
|
slack: { objectType: 'slack', token: 'old-token', dCookie: 'old-cookie' },
|
|
776
780
|
}));
|
|
777
781
|
const deps = createMockDependencies();
|
|
778
782
|
await runCommand(['auth', 'set', 'slack', '-H', 'X-Token: new-secret'], deps);
|
|
779
783
|
expect(logs).toContain('Credentials stored.');
|
|
780
|
-
const storedData = JSON.parse(
|
|
784
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
781
785
|
expect(storedData.slack).toEqual({
|
|
782
786
|
objectType: 'rawCurl',
|
|
783
787
|
curlArguments: ['-H', 'X-Token: new-secret'],
|
|
@@ -787,13 +791,13 @@ describe('CLI commands with dependency injection', () => {
|
|
|
787
791
|
describe('auth set-nocurl command', () => {
|
|
788
792
|
it('should store telegram bot credentials', async () => {
|
|
789
793
|
const storePath = join(tempDir, 'credentials.json');
|
|
790
|
-
|
|
794
|
+
writeSecureFile(storePath, '{}');
|
|
791
795
|
const deps = createMockDependencies({
|
|
792
796
|
registry: new ServiceRegistry([TELEGRAM]),
|
|
793
797
|
});
|
|
794
798
|
await runCommand(['auth', 'set-nocurl', 'telegram', '123456:ABC-DEF'], deps);
|
|
795
799
|
expect(logs).toContain('Credentials stored.');
|
|
796
|
-
const storedData = JSON.parse(
|
|
800
|
+
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
797
801
|
expect(storedData.telegram).toEqual({
|
|
798
802
|
objectType: 'telegramBot',
|
|
799
803
|
token: '123456:ABC-DEF',
|
|
@@ -827,7 +831,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
827
831
|
describe('curl command', () => {
|
|
828
832
|
it('should pass arguments to subprocess', async () => {
|
|
829
833
|
const storePath = join(tempDir, 'credentials.json');
|
|
830
|
-
|
|
834
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
831
835
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
832
836
|
}));
|
|
833
837
|
const deps = createMockDependencies();
|
|
@@ -843,7 +847,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
843
847
|
});
|
|
844
848
|
it('should pass raw curl credentials to subprocess', async () => {
|
|
845
849
|
const storePath = join(tempDir, 'credentials.json');
|
|
846
|
-
|
|
850
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
847
851
|
slack: { objectType: 'rawCurl', curlArguments: ['-H', 'X-Custom: header'] },
|
|
848
852
|
}));
|
|
849
853
|
const deps = createMockDependencies();
|
|
@@ -853,7 +857,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
853
857
|
});
|
|
854
858
|
it('should pass multiple arguments correctly', async () => {
|
|
855
859
|
const storePath = join(tempDir, 'credentials.json');
|
|
856
|
-
|
|
860
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
857
861
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
858
862
|
}));
|
|
859
863
|
const deps = createMockDependencies();
|
|
@@ -875,7 +879,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
875
879
|
});
|
|
876
880
|
it('should return subprocess exit code', async () => {
|
|
877
881
|
const storePath = join(tempDir, 'credentials.json');
|
|
878
|
-
|
|
882
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
879
883
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
880
884
|
}));
|
|
881
885
|
const deps = createMockDependencies({
|
|
@@ -905,7 +909,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
905
909
|
});
|
|
906
910
|
it('should pass through missing credentials when passthroughUnknown is enabled', async () => {
|
|
907
911
|
const storePath = join(tempDir, 'credentials.json');
|
|
908
|
-
|
|
912
|
+
writeSecureFile(storePath, '{}');
|
|
909
913
|
const deps = createMockDependencies({
|
|
910
914
|
config: createMockConfig({ passthroughUnknown: true }),
|
|
911
915
|
});
|
|
@@ -916,7 +920,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
916
920
|
});
|
|
917
921
|
it('should still inject credentials for known services when passthroughUnknown is enabled', async () => {
|
|
918
922
|
const storePath = join(tempDir, 'credentials.json');
|
|
919
|
-
|
|
923
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
920
924
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
921
925
|
}));
|
|
922
926
|
const deps = createMockDependencies({
|
|
@@ -928,7 +932,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
928
932
|
});
|
|
929
933
|
it('should read credentials from store and not call login', async () => {
|
|
930
934
|
const storePath = join(tempDir, 'credentials.json');
|
|
931
|
-
|
|
935
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
932
936
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
933
937
|
}));
|
|
934
938
|
const mockLogin = vi.fn();
|
|
@@ -957,14 +961,14 @@ describe('CLI commands with dependency injection', () => {
|
|
|
957
961
|
});
|
|
958
962
|
it('should return error when no credentials in store', async () => {
|
|
959
963
|
const storePath = join(tempDir, 'credentials.json');
|
|
960
|
-
|
|
964
|
+
writeSecureFile(storePath, '{}');
|
|
961
965
|
const deps = createMockDependencies();
|
|
962
966
|
await runCommand(['curl', 'https://slack.com/api/test'], deps);
|
|
963
967
|
expect(exitCode).toBe(1);
|
|
964
968
|
});
|
|
965
969
|
it('should inject telegram bot token into URL path', async () => {
|
|
966
970
|
const storePath = join(tempDir, 'credentials.json');
|
|
967
|
-
|
|
971
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
968
972
|
telegram: { objectType: 'telegramBot', token: '123456:ABC-DEF' },
|
|
969
973
|
}));
|
|
970
974
|
const deps = createMockDependencies({
|
|
@@ -976,7 +980,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
976
980
|
});
|
|
977
981
|
it('should work when service does not have getSession but credentials exist', async () => {
|
|
978
982
|
const storePath = join(tempDir, 'credentials.json');
|
|
979
|
-
|
|
983
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
980
984
|
nologin: { objectType: 'rawCurl', curlArguments: ['-H', 'X-API-Key: secret'] },
|
|
981
985
|
}));
|
|
982
986
|
const noLoginService = {
|
|
@@ -1005,7 +1009,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1005
1009
|
});
|
|
1006
1010
|
it('should reject request when permission check denies it', async () => {
|
|
1007
1011
|
const storePath = join(tempDir, 'credentials.json');
|
|
1008
|
-
|
|
1012
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1009
1013
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
1010
1014
|
}));
|
|
1011
1015
|
const deps = createMockDependencies({
|
|
@@ -1018,7 +1022,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1018
1022
|
});
|
|
1019
1023
|
it('should allow request when permission check approves it', async () => {
|
|
1020
1024
|
const storePath = join(tempDir, 'credentials.json');
|
|
1021
|
-
|
|
1025
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1022
1026
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
1023
1027
|
}));
|
|
1024
1028
|
const deps = createMockDependencies({
|
|
@@ -1030,7 +1034,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1030
1034
|
});
|
|
1031
1035
|
it('should exit with error when permission check fails', async () => {
|
|
1032
1036
|
const storePath = join(tempDir, 'credentials.json');
|
|
1033
|
-
|
|
1037
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1034
1038
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
1035
1039
|
}));
|
|
1036
1040
|
const { PermissionCheckError } = await import('../src/permissions.js');
|
|
@@ -1070,7 +1074,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1070
1074
|
});
|
|
1071
1075
|
it('should return error when no graphical environment is available', async () => {
|
|
1072
1076
|
const storePath = join(tempDir, 'credentials.json');
|
|
1073
|
-
|
|
1077
|
+
writeSecureFile(storePath, '{}');
|
|
1074
1078
|
const originalPlatform = process.platform;
|
|
1075
1079
|
const originalDisplay = process.env.DISPLAY;
|
|
1076
1080
|
const originalWayland = process.env.WAYLAND_DISPLAY;
|
|
@@ -1235,7 +1239,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1235
1239
|
});
|
|
1236
1240
|
it('should not expose browser auth without --login-url', async () => {
|
|
1237
1241
|
const storePath = join(tempDir, 'credentials.json');
|
|
1238
|
-
|
|
1242
|
+
writeSecureFile(storePath, '{}');
|
|
1239
1243
|
const deps = createMockDependencies({
|
|
1240
1244
|
registry: new ServiceRegistry([GITLAB]),
|
|
1241
1245
|
});
|
|
@@ -1338,7 +1342,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1338
1342
|
], deps);
|
|
1339
1343
|
// Now store credentials for it
|
|
1340
1344
|
const storePath = join(tempDir, 'credentials.json');
|
|
1341
|
-
|
|
1345
|
+
writeSecureFile(storePath, '{}');
|
|
1342
1346
|
logs = [];
|
|
1343
1347
|
exitCode = null;
|
|
1344
1348
|
await runCommand(['auth', 'set', 'my-gitlab', '-H', 'PRIVATE-TOKEN: my-secret-token'], deps);
|
|
@@ -1370,7 +1374,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1370
1374
|
});
|
|
1371
1375
|
it('should not expose browser auth for service without family', async () => {
|
|
1372
1376
|
const storePath = join(tempDir, 'credentials.json');
|
|
1373
|
-
|
|
1377
|
+
writeSecureFile(storePath, '{}');
|
|
1374
1378
|
const deps = createMockDependencies({
|
|
1375
1379
|
registry: new ServiceRegistry([GITLAB]),
|
|
1376
1380
|
});
|
|
@@ -1389,7 +1393,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1389
1393
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1390
1394
|
// Store credentials
|
|
1391
1395
|
const storePath = join(tempDir, 'credentials.json');
|
|
1392
|
-
|
|
1396
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1393
1397
|
'my-api': {
|
|
1394
1398
|
objectType: 'rawCurl',
|
|
1395
1399
|
curlArguments: ['-H', 'Authorization: Bearer my-token'],
|
|
@@ -1443,7 +1447,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1443
1447
|
], deps);
|
|
1444
1448
|
// Store credentials
|
|
1445
1449
|
const storePath = join(tempDir, 'credentials.json');
|
|
1446
|
-
|
|
1450
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1447
1451
|
'my-gitlab': {
|
|
1448
1452
|
objectType: 'rawCurl',
|
|
1449
1453
|
curlArguments: ['-H', 'PRIVATE-TOKEN: my-secret-token'],
|
|
@@ -1526,7 +1530,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1526
1530
|
], deps);
|
|
1527
1531
|
// Store credentials for it
|
|
1528
1532
|
const storePath = join(tempDir, 'credentials.json');
|
|
1529
|
-
|
|
1533
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1530
1534
|
'my-gitlab': {
|
|
1531
1535
|
objectType: 'rawCurl',
|
|
1532
1536
|
curlArguments: ['-H', 'PRIVATE-TOKEN: my-secret-token'],
|
|
@@ -1559,7 +1563,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1559
1563
|
], deps);
|
|
1560
1564
|
// Store and then clear credentials
|
|
1561
1565
|
const storePath = join(tempDir, 'credentials.json');
|
|
1562
|
-
|
|
1566
|
+
writeSecureFile(storePath, JSON.stringify({
|
|
1563
1567
|
'my-gitlab': {
|
|
1564
1568
|
objectType: 'rawCurl',
|
|
1565
1569
|
curlArguments: ['-H', 'PRIVATE-TOKEN: my-secret-token'],
|
|
@@ -1578,6 +1582,44 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1578
1582
|
expect(logs).toContain("Service 'my-gitlab' deregistered.");
|
|
1579
1583
|
});
|
|
1580
1584
|
});
|
|
1585
|
+
describe('gateway create-jwt command', () => {
|
|
1586
|
+
it('prints a JWT for an existing absolute path', async () => {
|
|
1587
|
+
const permissionsPath = join(tempDir, 'permissions.json');
|
|
1588
|
+
writeFileSync(permissionsPath, '{}');
|
|
1589
|
+
const deps = createMockDependencies();
|
|
1590
|
+
await runCommand(['gateway', 'create-jwt', permissionsPath], deps);
|
|
1591
|
+
expect(exitCode).toBeNull();
|
|
1592
|
+
expect(logs).toHaveLength(1);
|
|
1593
|
+
const jwt = logs[0];
|
|
1594
|
+
expect(jwt.split('.')).toHaveLength(3);
|
|
1595
|
+
const signingKey = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
|
|
1596
|
+
const payload = verifyPermissionsOverrideJwt(jwt, signingKey);
|
|
1597
|
+
expect(payload.permissionsConfig).toBe(permissionsPath);
|
|
1598
|
+
});
|
|
1599
|
+
it('refuses to issue a JWT when the path does not exist', async () => {
|
|
1600
|
+
const missingPath = join(tempDir, 'missing.json');
|
|
1601
|
+
const deps = createMockDependencies();
|
|
1602
|
+
await runCommand(['gateway', 'create-jwt', missingPath], deps);
|
|
1603
|
+
expect(exitCode).toBe(1);
|
|
1604
|
+
expect(errorLogs.some((message) => message.includes('does not exist'))).toBe(true);
|
|
1605
|
+
});
|
|
1606
|
+
it('issues a JWT without checking existence when --no-validate is given', async () => {
|
|
1607
|
+
const missingPath = join(tempDir, 'missing.json');
|
|
1608
|
+
const deps = createMockDependencies();
|
|
1609
|
+
await runCommand(['gateway', 'create-jwt', missingPath, '--no-validate'], deps);
|
|
1610
|
+
expect(exitCode).toBeNull();
|
|
1611
|
+
expect(logs).toHaveLength(1);
|
|
1612
|
+
const signingKey = derivePermissionsOverrideSigningKey(TEST_ENCRYPTION_KEY);
|
|
1613
|
+
const payload = verifyPermissionsOverrideJwt(logs[0], signingKey);
|
|
1614
|
+
expect(payload.permissionsConfig).toBe(missingPath);
|
|
1615
|
+
});
|
|
1616
|
+
it('rejects a non-absolute path', async () => {
|
|
1617
|
+
const deps = createMockDependencies();
|
|
1618
|
+
await runCommand(['gateway', 'create-jwt', 'relative.json', '--no-validate'], deps);
|
|
1619
|
+
expect(exitCode).toBe(1);
|
|
1620
|
+
expect(errorLogs.some((message) => message.includes('absolute'))).toBe(true);
|
|
1621
|
+
});
|
|
1622
|
+
});
|
|
1581
1623
|
describe('gateway mode (LATCHKEY_GATEWAY)', () => {
|
|
1582
1624
|
const GATEWAY_URL = 'http://localhost:9000';
|
|
1583
1625
|
const originalFetch = globalThis.fetch;
|