latchkey 2.5.2 → 2.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/dist/scripts/cryptFile.js +10 -10
- package/dist/scripts/cryptFile.js.map +1 -1
- package/dist/scripts/recordBrowserSession.js +1 -1
- package/dist/scripts/recordBrowserSession.js.map +1 -1
- package/dist/src/cli.js +6 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliCommands.js +18 -18
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/encryptedStorage.d.ts +2 -1
- package/dist/src/encryptedStorage.d.ts.map +1 -1
- package/dist/src/encryptedStorage.js +9 -5
- package/dist/src/encryptedStorage.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/keychain.d.ts +11 -3
- package/dist/src/keychain.d.ts.map +1 -1
- package/dist/src/keychain.js +44 -9
- package/dist/src/keychain.js.map +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 +48 -48
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/encryptedStorage.test.js +19 -19
- package/dist/tests/encryptedStorage.test.js.map +1 -1
- package/dist/tests/encryptedStorageKeyGeneration.test.js +8 -8
- package/dist/tests/encryptedStorageKeyGeneration.test.js.map +1 -1
- package/dist/tests/migrations.test.js +2 -2
- package/dist/tests/migrations.test.js.map +1 -1
- package/package.json +1 -1
package/dist/src/keychain.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
2
2
|
* System keychain integration for secure password storage.
|
|
3
3
|
* Uses @napi-rs/keyring for cross-platform support (macOS Keychain, Windows Credential Manager,
|
|
4
4
|
* Linux Secret Service via keyutils/kernel keyring).
|
|
5
|
+
*
|
|
6
|
+
* All operations have a timeout to prevent hanging when the keyring is locked.
|
|
5
7
|
*/
|
|
6
|
-
import {
|
|
8
|
+
import { AsyncEntry } from '@napi-rs/keyring';
|
|
9
|
+
import { platform } from 'node:os';
|
|
10
|
+
const KEYRING_TIMEOUT_MILLISECONDS = 30_000;
|
|
7
11
|
export class KeychainError extends Error {
|
|
8
12
|
constructor(message) {
|
|
9
13
|
super(message);
|
|
@@ -16,37 +20,64 @@ export class KeychainNotAvailableError extends KeychainError {
|
|
|
16
20
|
this.name = 'KeychainNotAvailableError';
|
|
17
21
|
}
|
|
18
22
|
}
|
|
23
|
+
export class KeychainTimeoutError extends KeychainError {
|
|
24
|
+
constructor() {
|
|
25
|
+
const linuxHint = platform() === 'linux'
|
|
26
|
+
? '\n\nOn Linux, you can try:\n' +
|
|
27
|
+
' - Unlocking your keyring: run a GUI app that accesses it, or use `gnome-keyring-daemon --unlock`\n' +
|
|
28
|
+
' - Bypassing the keyring entirely by setting LATCHKEY_ENCRYPTION_KEY:\n' +
|
|
29
|
+
' export LATCHKEY_ENCRYPTION_KEY="$(openssl rand -base64 32)"\n' +
|
|
30
|
+
' Add this to your shell profile to persist it across sessions.\n' +
|
|
31
|
+
' You may need to first delete any existing .enc files in your LATCHKEY_DIRECTORY\n' +
|
|
32
|
+
' (~/.latchkey by default) to reset any previously stored credentials.'
|
|
33
|
+
: '';
|
|
34
|
+
const timeoutSeconds = String(KEYRING_TIMEOUT_MILLISECONDS / 1000);
|
|
35
|
+
super(`Could not access the system keyring within ${timeoutSeconds} seconds — it may be locked or unavailable.${linuxHint}`);
|
|
36
|
+
this.name = 'KeychainTimeoutError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
19
39
|
/**
|
|
20
|
-
* Get
|
|
40
|
+
* Get an async keyring entry.
|
|
21
41
|
*/
|
|
22
42
|
function getEntry(serviceName, accountName) {
|
|
23
|
-
return new
|
|
43
|
+
return new AsyncEntry(serviceName, accountName);
|
|
44
|
+
}
|
|
45
|
+
function isTimeoutError(error) {
|
|
46
|
+
return error instanceof Error && (error.name === 'TimeoutError' || error.name === 'AbortError');
|
|
24
47
|
}
|
|
25
48
|
/**
|
|
26
49
|
* Store a password in the system keychain.
|
|
50
|
+
* Throws KeychainTimeoutError if the keychain does not respond in time.
|
|
27
51
|
* Throws KeychainNotAvailableError if the keychain is not accessible.
|
|
28
52
|
*/
|
|
29
|
-
export function storeInKeychain(serviceName, accountName, password) {
|
|
53
|
+
export async function storeInKeychain(serviceName, accountName, password) {
|
|
30
54
|
try {
|
|
31
55
|
const entry = getEntry(serviceName, accountName);
|
|
32
|
-
entry.setPassword(password);
|
|
56
|
+
await entry.setPassword(password, AbortSignal.timeout(KEYRING_TIMEOUT_MILLISECONDS));
|
|
33
57
|
}
|
|
34
58
|
catch (error) {
|
|
59
|
+
if (isTimeoutError(error)) {
|
|
60
|
+
throw new KeychainTimeoutError();
|
|
61
|
+
}
|
|
35
62
|
throw new KeychainNotAvailableError(`Failed to store password in keychain: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
63
|
}
|
|
37
64
|
}
|
|
38
65
|
/**
|
|
39
66
|
* Retrieve a password from the system keychain.
|
|
40
67
|
* Returns null if the password is not found.
|
|
68
|
+
* Throws KeychainTimeoutError if the keychain does not respond in time.
|
|
41
69
|
* Throws KeychainNotAvailableError if the keychain is not accessible.
|
|
42
70
|
*/
|
|
43
|
-
export function retrieveFromKeychain(serviceName, accountName) {
|
|
71
|
+
export async function retrieveFromKeychain(serviceName, accountName) {
|
|
44
72
|
try {
|
|
45
73
|
const entry = getEntry(serviceName, accountName);
|
|
46
|
-
const password = entry.getPassword();
|
|
74
|
+
const password = await entry.getPassword(AbortSignal.timeout(KEYRING_TIMEOUT_MILLISECONDS));
|
|
47
75
|
return password ?? null;
|
|
48
76
|
}
|
|
49
77
|
catch (error) {
|
|
78
|
+
if (isTimeoutError(error)) {
|
|
79
|
+
throw new KeychainTimeoutError();
|
|
80
|
+
}
|
|
50
81
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
51
82
|
// Check if it's a "not found" error
|
|
52
83
|
if (errorMessage.includes('not found') ||
|
|
@@ -60,15 +91,19 @@ export function retrieveFromKeychain(serviceName, accountName) {
|
|
|
60
91
|
/**
|
|
61
92
|
* Delete a password from the system keychain.
|
|
62
93
|
* Returns true if deleted, false if not found.
|
|
94
|
+
* Throws KeychainTimeoutError if the keychain does not respond in time.
|
|
63
95
|
* Throws KeychainNotAvailableError if the keychain is not accessible.
|
|
64
96
|
*/
|
|
65
|
-
export function deleteFromKeychain(serviceName, accountName) {
|
|
97
|
+
export async function deleteFromKeychain(serviceName, accountName) {
|
|
66
98
|
try {
|
|
67
99
|
const entry = getEntry(serviceName, accountName);
|
|
68
|
-
entry.deletePassword();
|
|
100
|
+
await entry.deletePassword(AbortSignal.timeout(KEYRING_TIMEOUT_MILLISECONDS));
|
|
69
101
|
return true;
|
|
70
102
|
}
|
|
71
103
|
catch (error) {
|
|
104
|
+
if (isTimeoutError(error)) {
|
|
105
|
+
throw new KeychainTimeoutError();
|
|
106
|
+
}
|
|
72
107
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
73
108
|
if (errorMessage.includes('not found') ||
|
|
74
109
|
errorMessage.includes('No password') ||
|
package/dist/src/keychain.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/keychain.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/keychain.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,10 +11,10 @@ describe('ApiCredentialStore', () => {
|
|
|
11
11
|
let tempDir;
|
|
12
12
|
let storePath;
|
|
13
13
|
let encryptedStorage;
|
|
14
|
-
beforeEach(() => {
|
|
14
|
+
beforeEach(async () => {
|
|
15
15
|
tempDir = mkdtempSync(join(tmpdir(), 'latchkey-test-'));
|
|
16
16
|
storePath = join(tempDir, 'credentials.json');
|
|
17
|
-
encryptedStorage =
|
|
17
|
+
encryptedStorage = await EncryptedStorage.create({ encryptionKeyOverride: generateKey() });
|
|
18
18
|
});
|
|
19
19
|
afterEach(() => {
|
|
20
20
|
rmSync(tempDir, { recursive: true, force: true });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apiCredentialStore.test.js","sourceRoot":"","sources":["../../tests/apiCredentialStore.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACrE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACxE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,IAAI,OAAe,CAAC;IACpB,IAAI,SAAiB,CAAC;IACtB,IAAI,gBAAkC,CAAC;IAEvC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"apiCredentialStore.test.js","sourceRoot":"","sources":["../../tests/apiCredentialStore.test.ts"],"names":[],"mappings":"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"}
|
package/dist/tests/cli.test.js
CHANGED
|
@@ -21,12 +21,12 @@ import { deleteRegisteredService, loadRegisteredServices, saveRegisteredService,
|
|
|
21
21
|
import { loadRegisteredServicesIntoRegistry } from '../src/registry.js';
|
|
22
22
|
// Use a fixed test key for deterministic test behavior (32 bytes = 256 bits, base64 encoded)
|
|
23
23
|
const TEST_ENCRYPTION_KEY = 'dGVzdGtleXRlc3RrZXl0ZXN0a2V5dGVzdGtleXRlc3Q=';
|
|
24
|
-
function writeSecureFile(path, content) {
|
|
25
|
-
const storage =
|
|
24
|
+
async function writeSecureFile(path, content) {
|
|
25
|
+
const storage = await EncryptedStorage.create({ encryptionKeyOverride: TEST_ENCRYPTION_KEY });
|
|
26
26
|
storage.writeFile(path, content);
|
|
27
27
|
}
|
|
28
|
-
function readSecureFile(path) {
|
|
29
|
-
const storage =
|
|
28
|
+
async function readSecureFile(path) {
|
|
29
|
+
const storage = await EncryptedStorage.create({ encryptionKeyOverride: TEST_ENCRYPTION_KEY });
|
|
30
30
|
return storage.readFile(path);
|
|
31
31
|
}
|
|
32
32
|
function getCliPath() {
|
|
@@ -352,7 +352,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
352
352
|
});
|
|
353
353
|
it('should include services with stored credentials when using --viable', async () => {
|
|
354
354
|
const storePath = join(tempDir, 'credentials.json');
|
|
355
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
355
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
356
356
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
357
357
|
}));
|
|
358
358
|
const deps = createMockDependencies();
|
|
@@ -363,7 +363,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
363
363
|
});
|
|
364
364
|
it('should include services with browser auth when using --viable', async () => {
|
|
365
365
|
const storePath = join(tempDir, 'credentials.json');
|
|
366
|
-
writeSecureFile(storePath, '{}');
|
|
366
|
+
await writeSecureFile(storePath, '{}');
|
|
367
367
|
// The default mock slack service has getSession defined, so it supports browser auth
|
|
368
368
|
// Ensure a graphical environment is available so browser auth is considered viable
|
|
369
369
|
const originalDisplay = process.env.DISPLAY;
|
|
@@ -386,7 +386,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
386
386
|
});
|
|
387
387
|
it('should exclude services without credentials or browser auth when using --viable', async () => {
|
|
388
388
|
const storePath = join(tempDir, 'credentials.json');
|
|
389
|
-
writeSecureFile(storePath, '{}');
|
|
389
|
+
await writeSecureFile(storePath, '{}');
|
|
390
390
|
const noLoginService = {
|
|
391
391
|
name: 'nologin',
|
|
392
392
|
displayName: 'No Login Service',
|
|
@@ -412,7 +412,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
412
412
|
});
|
|
413
413
|
it('should exclude browser-capable services when browser is disabled and no credentials with --viable', async () => {
|
|
414
414
|
const storePath = join(tempDir, 'credentials.json');
|
|
415
|
-
writeSecureFile(storePath, '{}');
|
|
415
|
+
await writeSecureFile(storePath, '{}');
|
|
416
416
|
const deps = createMockDependencies({
|
|
417
417
|
config: createMockConfig({ browserDisabled: true }),
|
|
418
418
|
});
|
|
@@ -423,7 +423,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
423
423
|
});
|
|
424
424
|
it('should exclude browser-capable services when no graphical environment and no credentials with --viable', async () => {
|
|
425
425
|
const storePath = join(tempDir, 'credentials.json');
|
|
426
|
-
writeSecureFile(storePath, '{}');
|
|
426
|
+
await writeSecureFile(storePath, '{}');
|
|
427
427
|
const originalPlatform = process.platform;
|
|
428
428
|
const originalDisplay = process.env.DISPLAY;
|
|
429
429
|
const originalWayland = process.env.WAYLAND_DISPLAY;
|
|
@@ -455,7 +455,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
455
455
|
});
|
|
456
456
|
it('should include services with credentials even when no graphical environment with --viable', async () => {
|
|
457
457
|
const storePath = join(tempDir, 'credentials.json');
|
|
458
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
458
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
459
459
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
460
460
|
}));
|
|
461
461
|
const originalPlatform = process.platform;
|
|
@@ -489,7 +489,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
489
489
|
});
|
|
490
490
|
it('should include services with credentials even when browser is disabled with --viable', async () => {
|
|
491
491
|
const storePath = join(tempDir, 'credentials.json');
|
|
492
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
492
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
493
493
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
494
494
|
}));
|
|
495
495
|
const deps = createMockDependencies({
|
|
@@ -502,7 +502,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
502
502
|
});
|
|
503
503
|
it('should combine --builtin and --viable filters', async () => {
|
|
504
504
|
const storePath = join(tempDir, 'credentials.json');
|
|
505
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
505
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
506
506
|
'my-gitlab': {
|
|
507
507
|
objectType: 'rawCurl',
|
|
508
508
|
curlArguments: ['-H', 'PRIVATE-TOKEN: token'],
|
|
@@ -536,7 +536,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
536
536
|
describe('services info command', () => {
|
|
537
537
|
it('should show login options, credentials status, and developer notes', async () => {
|
|
538
538
|
const storePath = join(tempDir, 'credentials.json');
|
|
539
|
-
writeSecureFile(storePath, '{}');
|
|
539
|
+
await writeSecureFile(storePath, '{}');
|
|
540
540
|
const deps = createMockDependencies();
|
|
541
541
|
await runCommand(['services', 'info', 'slack'], deps);
|
|
542
542
|
expect(logs).toHaveLength(1);
|
|
@@ -550,7 +550,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
550
550
|
});
|
|
551
551
|
it('should show auth set only for services without browser login', async () => {
|
|
552
552
|
const storePath = join(tempDir, 'credentials.json');
|
|
553
|
-
writeSecureFile(storePath, '{}');
|
|
553
|
+
await writeSecureFile(storePath, '{}');
|
|
554
554
|
const noLoginService = {
|
|
555
555
|
name: 'nologin',
|
|
556
556
|
displayName: 'No Login Service',
|
|
@@ -575,7 +575,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
575
575
|
});
|
|
576
576
|
it('should not list browser in authOptions when LATCHKEY_DISABLE_BROWSER is in effect', async () => {
|
|
577
577
|
const storePath = join(tempDir, 'credentials.json');
|
|
578
|
-
writeSecureFile(storePath, '{}');
|
|
578
|
+
await writeSecureFile(storePath, '{}');
|
|
579
579
|
const deps = createMockDependencies({
|
|
580
580
|
config: createMockConfig({ browserDisabled: true }),
|
|
581
581
|
});
|
|
@@ -585,7 +585,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
585
585
|
});
|
|
586
586
|
it('should show valid credentials status when credentials are valid', async () => {
|
|
587
587
|
const storePath = join(tempDir, 'credentials.json');
|
|
588
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
588
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
589
589
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
590
590
|
}));
|
|
591
591
|
const deps = createMockDependencies();
|
|
@@ -601,7 +601,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
601
601
|
});
|
|
602
602
|
it('should show type as registered for registered services', async () => {
|
|
603
603
|
const storePath = join(tempDir, 'credentials.json');
|
|
604
|
-
writeSecureFile(storePath, '{}');
|
|
604
|
+
await writeSecureFile(storePath, '{}');
|
|
605
605
|
const registeredService = new RegisteredService('my-gitlab', 'https://gitlab.example.com');
|
|
606
606
|
const deps = createMockDependencies();
|
|
607
607
|
deps.registry.addService(registeredService);
|
|
@@ -613,12 +613,12 @@ describe('CLI commands with dependency injection', () => {
|
|
|
613
613
|
describe('clear command', () => {
|
|
614
614
|
it('should delete credentials for a service', async () => {
|
|
615
615
|
const storePath = join(tempDir, 'credentials.json');
|
|
616
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
616
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
617
617
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
618
618
|
}));
|
|
619
619
|
const deps = createMockDependencies();
|
|
620
620
|
await runCommand(['auth', 'clear', 'slack'], deps);
|
|
621
|
-
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
621
|
+
const storedData = JSON.parse((await readSecureFile(storePath)) ?? '{}');
|
|
622
622
|
expect(storedData.slack).toBeUndefined();
|
|
623
623
|
});
|
|
624
624
|
it('should return error for unknown service', async () => {
|
|
@@ -629,13 +629,13 @@ describe('CLI commands with dependency injection', () => {
|
|
|
629
629
|
});
|
|
630
630
|
it('should preserve other services when clearing one', async () => {
|
|
631
631
|
const storePath = join(tempDir, 'credentials.json');
|
|
632
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
632
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
633
633
|
slack: { objectType: 'slack', token: 'slack-token', dCookie: 'slack-cookie' },
|
|
634
634
|
discord: { objectType: 'authorizationBare', token: 'discord-token' },
|
|
635
635
|
}));
|
|
636
636
|
const deps = createMockDependencies();
|
|
637
637
|
await runCommand(['auth', 'clear', 'slack'], deps);
|
|
638
|
-
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
638
|
+
const storedData = JSON.parse((await readSecureFile(storePath)) ?? '{}');
|
|
639
639
|
expect(storedData.slack).toBeUndefined();
|
|
640
640
|
expect(storedData.discord).toBeDefined();
|
|
641
641
|
expect(storedData.discord?.token).toBe('discord-token');
|
|
@@ -643,8 +643,8 @@ describe('CLI commands with dependency injection', () => {
|
|
|
643
643
|
it('should delete both store and browser state with -y flag', async () => {
|
|
644
644
|
const storePath = join(tempDir, 'credentials.json');
|
|
645
645
|
const browserStatePath = join(tempDir, 'browser_state.json');
|
|
646
|
-
writeSecureFile(storePath, JSON.stringify({ slack: { objectType: 'slack', token: 'test', dCookie: 'test' } }));
|
|
647
|
-
writeSecureFile(browserStatePath, '{}');
|
|
646
|
+
await writeSecureFile(storePath, JSON.stringify({ slack: { objectType: 'slack', token: 'test', dCookie: 'test' } }));
|
|
647
|
+
await writeSecureFile(browserStatePath, '{}');
|
|
648
648
|
const deps = createMockDependencies();
|
|
649
649
|
await runCommand(['auth', 'clear', '-y'], deps);
|
|
650
650
|
expect(existsSync(storePath)).toBe(false);
|
|
@@ -654,7 +654,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
654
654
|
describe('auth list command', () => {
|
|
655
655
|
it('should list stored credentials with their status', async () => {
|
|
656
656
|
const storePath = join(tempDir, 'credentials.json');
|
|
657
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
657
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
658
658
|
slack: { objectType: 'slack', token: 'test-token', dCookie: 'test-cookie' },
|
|
659
659
|
}));
|
|
660
660
|
const deps = createMockDependencies();
|
|
@@ -668,7 +668,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
668
668
|
});
|
|
669
669
|
it('should output empty object when no credentials are stored', async () => {
|
|
670
670
|
const storePath = join(tempDir, 'credentials.json');
|
|
671
|
-
writeSecureFile(storePath, '{}');
|
|
671
|
+
await writeSecureFile(storePath, '{}');
|
|
672
672
|
const deps = createMockDependencies();
|
|
673
673
|
await runCommand(['auth', 'list'], deps);
|
|
674
674
|
expect(logs).toHaveLength(1);
|
|
@@ -677,7 +677,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
677
677
|
});
|
|
678
678
|
it('should treat unknown services as valid', async () => {
|
|
679
679
|
const storePath = join(tempDir, 'credentials.json');
|
|
680
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
680
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
681
681
|
unknown: { objectType: 'rawCurl', curlArguments: ['-H', 'X-Token: secret'] },
|
|
682
682
|
}));
|
|
683
683
|
const deps = createMockDependencies();
|
|
@@ -693,11 +693,11 @@ describe('CLI commands with dependency injection', () => {
|
|
|
693
693
|
describe('auth set command', () => {
|
|
694
694
|
it('should store raw curl credentials', async () => {
|
|
695
695
|
const storePath = join(tempDir, 'credentials.json');
|
|
696
|
-
writeSecureFile(storePath, '{}');
|
|
696
|
+
await writeSecureFile(storePath, '{}');
|
|
697
697
|
const deps = createMockDependencies();
|
|
698
698
|
await runCommand(['auth', 'set', 'slack', '-H', 'X-Token: secret', '-H', 'X-Other: value'], deps);
|
|
699
699
|
expect(logs).toContain('Credentials stored.');
|
|
700
|
-
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
700
|
+
const storedData = JSON.parse((await readSecureFile(storePath)) ?? '{}');
|
|
701
701
|
expect(storedData.slack).toEqual({
|
|
702
702
|
objectType: 'rawCurl',
|
|
703
703
|
curlArguments: ['-H', 'X-Token: secret', '-H', 'X-Other: value'],
|
|
@@ -720,13 +720,13 @@ describe('CLI commands with dependency injection', () => {
|
|
|
720
720
|
});
|
|
721
721
|
it('should overwrite existing credentials', async () => {
|
|
722
722
|
const storePath = join(tempDir, 'credentials.json');
|
|
723
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
723
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
724
724
|
slack: { objectType: 'slack', token: 'old-token', dCookie: 'old-cookie' },
|
|
725
725
|
}));
|
|
726
726
|
const deps = createMockDependencies();
|
|
727
727
|
await runCommand(['auth', 'set', 'slack', '-H', 'X-Token: new-secret'], deps);
|
|
728
728
|
expect(logs).toContain('Credentials stored.');
|
|
729
|
-
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
729
|
+
const storedData = JSON.parse((await readSecureFile(storePath)) ?? '{}');
|
|
730
730
|
expect(storedData.slack).toEqual({
|
|
731
731
|
objectType: 'rawCurl',
|
|
732
732
|
curlArguments: ['-H', 'X-Token: new-secret'],
|
|
@@ -736,13 +736,13 @@ describe('CLI commands with dependency injection', () => {
|
|
|
736
736
|
describe('auth set-nocurl command', () => {
|
|
737
737
|
it('should store telegram bot credentials', async () => {
|
|
738
738
|
const storePath = join(tempDir, 'credentials.json');
|
|
739
|
-
writeSecureFile(storePath, '{}');
|
|
739
|
+
await writeSecureFile(storePath, '{}');
|
|
740
740
|
const deps = createMockDependencies({
|
|
741
741
|
registry: new Registry([TELEGRAM]),
|
|
742
742
|
});
|
|
743
743
|
await runCommand(['auth', 'set-nocurl', 'telegram', '123456:ABC-DEF'], deps);
|
|
744
744
|
expect(logs).toContain('Credentials stored.');
|
|
745
|
-
const storedData = JSON.parse(readSecureFile(storePath) ?? '{}');
|
|
745
|
+
const storedData = JSON.parse((await readSecureFile(storePath)) ?? '{}');
|
|
746
746
|
expect(storedData.telegram).toEqual({
|
|
747
747
|
objectType: 'telegramBot',
|
|
748
748
|
token: '123456:ABC-DEF',
|
|
@@ -776,7 +776,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
776
776
|
describe('curl command', () => {
|
|
777
777
|
it('should pass arguments to subprocess', async () => {
|
|
778
778
|
const storePath = join(tempDir, 'credentials.json');
|
|
779
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
779
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
780
780
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
781
781
|
}));
|
|
782
782
|
const deps = createMockDependencies();
|
|
@@ -792,7 +792,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
792
792
|
});
|
|
793
793
|
it('should pass raw curl credentials to subprocess', async () => {
|
|
794
794
|
const storePath = join(tempDir, 'credentials.json');
|
|
795
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
795
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
796
796
|
slack: { objectType: 'rawCurl', curlArguments: ['-H', 'X-Custom: header'] },
|
|
797
797
|
}));
|
|
798
798
|
const deps = createMockDependencies();
|
|
@@ -802,7 +802,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
802
802
|
});
|
|
803
803
|
it('should pass multiple arguments correctly', async () => {
|
|
804
804
|
const storePath = join(tempDir, 'credentials.json');
|
|
805
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
805
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
806
806
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
807
807
|
}));
|
|
808
808
|
const deps = createMockDependencies();
|
|
@@ -824,7 +824,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
824
824
|
});
|
|
825
825
|
it('should return subprocess exit code', async () => {
|
|
826
826
|
const storePath = join(tempDir, 'credentials.json');
|
|
827
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
827
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
828
828
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
829
829
|
}));
|
|
830
830
|
const deps = createMockDependencies({
|
|
@@ -845,7 +845,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
845
845
|
});
|
|
846
846
|
it('should read credentials from store and not call login', async () => {
|
|
847
847
|
const storePath = join(tempDir, 'credentials.json');
|
|
848
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
848
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
849
849
|
slack: { objectType: 'slack', token: 'stored-token', dCookie: 'stored-cookie' },
|
|
850
850
|
}));
|
|
851
851
|
const mockLogin = vi.fn();
|
|
@@ -874,14 +874,14 @@ describe('CLI commands with dependency injection', () => {
|
|
|
874
874
|
});
|
|
875
875
|
it('should return error when no credentials in store', async () => {
|
|
876
876
|
const storePath = join(tempDir, 'credentials.json');
|
|
877
|
-
writeSecureFile(storePath, '{}');
|
|
877
|
+
await writeSecureFile(storePath, '{}');
|
|
878
878
|
const deps = createMockDependencies();
|
|
879
879
|
await runCommand(['curl', 'https://slack.com/api/test'], deps);
|
|
880
880
|
expect(exitCode).toBe(1);
|
|
881
881
|
});
|
|
882
882
|
it('should inject telegram bot token into URL path', async () => {
|
|
883
883
|
const storePath = join(tempDir, 'credentials.json');
|
|
884
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
884
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
885
885
|
telegram: { objectType: 'telegramBot', token: '123456:ABC-DEF' },
|
|
886
886
|
}));
|
|
887
887
|
const deps = createMockDependencies({
|
|
@@ -893,7 +893,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
893
893
|
});
|
|
894
894
|
it('should work when service does not have getSession but credentials exist', async () => {
|
|
895
895
|
const storePath = join(tempDir, 'credentials.json');
|
|
896
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
896
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
897
897
|
nologin: { objectType: 'rawCurl', curlArguments: ['-H', 'X-API-Key: secret'] },
|
|
898
898
|
}));
|
|
899
899
|
const noLoginService = {
|
|
@@ -946,7 +946,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
946
946
|
});
|
|
947
947
|
it('should return error when no graphical environment is available', async () => {
|
|
948
948
|
const storePath = join(tempDir, 'credentials.json');
|
|
949
|
-
writeSecureFile(storePath, '{}');
|
|
949
|
+
await writeSecureFile(storePath, '{}');
|
|
950
950
|
const originalPlatform = process.platform;
|
|
951
951
|
const originalDisplay = process.env.DISPLAY;
|
|
952
952
|
const originalWayland = process.env.WAYLAND_DISPLAY;
|
|
@@ -1111,7 +1111,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1111
1111
|
});
|
|
1112
1112
|
it('should not expose browser auth without --login-url', async () => {
|
|
1113
1113
|
const storePath = join(tempDir, 'credentials.json');
|
|
1114
|
-
writeSecureFile(storePath, '{}');
|
|
1114
|
+
await writeSecureFile(storePath, '{}');
|
|
1115
1115
|
const deps = createMockDependencies({
|
|
1116
1116
|
registry: new Registry([GITLAB]),
|
|
1117
1117
|
});
|
|
@@ -1214,7 +1214,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1214
1214
|
], deps);
|
|
1215
1215
|
// Now store credentials for it
|
|
1216
1216
|
const storePath = join(tempDir, 'credentials.json');
|
|
1217
|
-
writeSecureFile(storePath, '{}');
|
|
1217
|
+
await writeSecureFile(storePath, '{}');
|
|
1218
1218
|
logs = [];
|
|
1219
1219
|
exitCode = null;
|
|
1220
1220
|
await runCommand(['auth', 'set', 'my-gitlab', '-H', 'PRIVATE-TOKEN: my-secret-token'], deps);
|
|
@@ -1246,7 +1246,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1246
1246
|
});
|
|
1247
1247
|
it('should not expose browser auth for service without family', async () => {
|
|
1248
1248
|
const storePath = join(tempDir, 'credentials.json');
|
|
1249
|
-
writeSecureFile(storePath, '{}');
|
|
1249
|
+
await writeSecureFile(storePath, '{}');
|
|
1250
1250
|
const deps = createMockDependencies({
|
|
1251
1251
|
registry: new Registry([GITLAB]),
|
|
1252
1252
|
});
|
|
@@ -1265,7 +1265,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1265
1265
|
await runCommand(['services', 'register', 'my-api', '--base-api-url', 'https://api.example.com/'], deps);
|
|
1266
1266
|
// Store credentials
|
|
1267
1267
|
const storePath = join(tempDir, 'credentials.json');
|
|
1268
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
1268
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
1269
1269
|
'my-api': {
|
|
1270
1270
|
objectType: 'rawCurl',
|
|
1271
1271
|
curlArguments: ['-H', 'Authorization: Bearer my-token'],
|
|
@@ -1319,7 +1319,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1319
1319
|
], deps);
|
|
1320
1320
|
// Store credentials
|
|
1321
1321
|
const storePath = join(tempDir, 'credentials.json');
|
|
1322
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
1322
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
1323
1323
|
'my-gitlab': {
|
|
1324
1324
|
objectType: 'rawCurl',
|
|
1325
1325
|
curlArguments: ['-H', 'PRIVATE-TOKEN: my-secret-token'],
|
|
@@ -1402,7 +1402,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1402
1402
|
], deps);
|
|
1403
1403
|
// Store credentials for it
|
|
1404
1404
|
const storePath = join(tempDir, 'credentials.json');
|
|
1405
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
1405
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
1406
1406
|
'my-gitlab': {
|
|
1407
1407
|
objectType: 'rawCurl',
|
|
1408
1408
|
curlArguments: ['-H', 'PRIVATE-TOKEN: my-secret-token'],
|
|
@@ -1435,7 +1435,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1435
1435
|
], deps);
|
|
1436
1436
|
// Store and then clear credentials
|
|
1437
1437
|
const storePath = join(tempDir, 'credentials.json');
|
|
1438
|
-
writeSecureFile(storePath, JSON.stringify({
|
|
1438
|
+
await writeSecureFile(storePath, JSON.stringify({
|
|
1439
1439
|
'my-gitlab': {
|
|
1440
1440
|
objectType: 'rawCurl',
|
|
1441
1441
|
curlArguments: ['-H', 'PRIVATE-TOKEN: my-secret-token'],
|