latchkey 3.8.0 → 3.10.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 +3 -3
- package/dist/scripts/recordBrowserSession.js +4 -3
- package/dist/scripts/recordBrowserSession.js.map +1 -1
- package/dist/src/apiCredentials/base.d.ts +7 -1
- package/dist/src/apiCredentials/base.d.ts.map +1 -1
- package/dist/src/apiCredentials/base.js.map +1 -1
- package/dist/src/apiCredentials/store.d.ts +5 -0
- package/dist/src/apiCredentials/store.d.ts.map +1 -1
- package/dist/src/apiCredentials/store.js +7 -0
- package/dist/src/apiCredentials/store.js.map +1 -1
- package/dist/src/atomicWrite.d.ts +4 -1
- package/dist/src/atomicWrite.d.ts.map +1 -1
- package/dist/src/atomicWrite.js +34 -3
- package/dist/src/atomicWrite.js.map +1 -1
- package/dist/src/cli.js +0 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliCommands.d.ts +8 -0
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/cliCommands.js +110 -17
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/curl.d.ts +36 -0
- package/dist/src/curl.d.ts.map +1 -1
- package/dist/src/curl.js +42 -0
- package/dist/src/curl.js.map +1 -1
- package/dist/src/curlInjection.d.ts +5 -4
- package/dist/src/curlInjection.d.ts.map +1 -1
- package/dist/src/curlInjection.js +8 -7
- package/dist/src/curlInjection.js.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.js +6 -3
- package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
- package/dist/src/gateway/server.d.ts.map +1 -1
- package/dist/src/gateway/server.js +14 -3
- package/dist/src/gateway/server.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/migrations.d.ts +6 -0
- package/dist/src/migrations.d.ts.map +1 -1
- package/dist/src/migrations.js +21 -8
- package/dist/src/migrations.js.map +1 -1
- package/dist/src/serviceRegistry.d.ts +14 -1
- package/dist/src/serviceRegistry.d.ts.map +1 -1
- package/dist/src/serviceRegistry.js +22 -2
- package/dist/src/serviceRegistry.js.map +1 -1
- package/dist/src/services/aws.d.ts +11 -2
- package/dist/src/services/aws.d.ts.map +1 -1
- package/dist/src/services/aws.js +72 -5
- package/dist/src/services/aws.js.map +1 -1
- package/dist/src/services/core/loginFlows/registry.d.ts.map +1 -1
- package/dist/src/services/core/loginFlows/registry.js +11 -6
- package/dist/src/services/core/loginFlows/registry.js.map +1 -1
- package/dist/src/services/core/loginFlows/tokenCapture.d.ts +74 -0
- package/dist/src/services/core/loginFlows/tokenCapture.d.ts.map +1 -0
- package/dist/src/services/core/loginFlows/tokenCapture.js +201 -0
- package/dist/src/services/core/loginFlows/tokenCapture.js.map +1 -0
- 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/version.d.ts +1 -1
- package/dist/src/version.d.ts.map +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/version.js.map +1 -1
- package/dist/tests/apiCredentialStore.test.js +8 -0
- package/dist/tests/apiCredentialStore.test.js.map +1 -1
- package/dist/tests/apiCredentials.test.js +84 -1
- package/dist/tests/apiCredentials.test.js.map +1 -1
- package/dist/tests/atomicWrite.test.js +42 -1
- package/dist/tests/atomicWrite.test.js.map +1 -1
- package/dist/tests/builtinServiceRegistry.d.ts +10 -0
- package/dist/tests/builtinServiceRegistry.d.ts.map +1 -0
- package/dist/tests/builtinServiceRegistry.js +10 -0
- package/dist/tests/builtinServiceRegistry.js.map +1 -0
- package/dist/tests/cli.test.js +273 -7
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/devShim.test.d.ts +2 -0
- package/dist/tests/devShim.test.d.ts.map +1 -0
- package/dist/tests/devShim.test.js +240 -0
- package/dist/tests/devShim.test.js.map +1 -0
- package/dist/tests/gateway.test.js +87 -2
- package/dist/tests/gateway.test.js.map +1 -1
- package/dist/tests/gatewayExtensions.test.js +2 -0
- package/dist/tests/gatewayExtensions.test.js.map +1 -1
- package/dist/tests/github.test.js +2 -2
- package/dist/tests/github.test.js.map +1 -1
- package/dist/tests/huggingface.test.js +2 -2
- package/dist/tests/huggingface.test.js.map +1 -1
- package/dist/tests/latchkeyEndpoint.test.js +9 -5
- package/dist/tests/latchkeyEndpoint.test.js.map +1 -1
- package/dist/tests/manualCredentialForms.test.js +2 -2
- package/dist/tests/manualCredentialForms.test.js.map +1 -1
- package/dist/tests/serviceRegistry.test.js +104 -20
- package/dist/tests/serviceRegistry.test.js.map +1 -1
- package/dist/tests/servicesAgainstRecordings.test.js +2 -2
- package/dist/tests/servicesAgainstRecordings.test.js.map +1 -1
- package/dist/tests/tailscale.test.js +2 -2
- package/dist/tests/tailscale.test.js.map +1 -1
- package/dist/tests/tokenCapture.test.d.ts +10 -0
- package/dist/tests/tokenCapture.test.d.ts.map +1 -0
- package/dist/tests/tokenCapture.test.js +136 -0
- package/dist/tests/tokenCapture.test.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A registry over the services latchkey ships with.
|
|
3
|
+
*
|
|
4
|
+
* The CLI and the gateway each build their own registry with
|
|
5
|
+
* `createServiceRegistry`, since theirs also carry whatever config.json holds.
|
|
6
|
+
* Tests that only care about the built-in services share this one.
|
|
7
|
+
*/
|
|
8
|
+
import { BUILTIN_SERVICES, ServiceRegistry } from '../src/serviceRegistry.js';
|
|
9
|
+
export const BUILTIN_SERVICE_REGISTRY = new ServiceRegistry(BUILTIN_SERVICES);
|
|
10
|
+
//# sourceMappingURL=builtinServiceRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"builtinServiceRegistry.js","sourceRoot":"","sources":["../../tests/builtinServiceRegistry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE9E,MAAM,CAAC,MAAM,wBAAwB,GAAG,IAAI,eAAe,CAAC,gBAAgB,CAAC,CAAC"}
|
package/dist/tests/cli.test.js
CHANGED
|
@@ -5,12 +5,13 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { execSync } from 'node:child_process';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { registerCommands } from '../src/cliCommands.js';
|
|
8
|
-
import { CurlParseError, extractUrlFromCurlArguments } from '../src/curl.js';
|
|
8
|
+
import { CurlParseError, extractUrlFromCurlArguments, resolveCurlRequestBodySource, } from '../src/curl.js';
|
|
9
9
|
import { hasGraphicalEnvironment } from '../src/playwrightUtils.js';
|
|
10
10
|
import { EncryptedStorage } from '../src/encryptedStorage.js';
|
|
11
11
|
import { Config } from '../src/config.js';
|
|
12
12
|
import { ServiceRegistry } from '../src/serviceRegistry.js';
|
|
13
13
|
import { ApiCredentialStatus } from '../src/apiCredentials/base.js';
|
|
14
|
+
import { LATEST_VERSION } from '../src/migrations.js';
|
|
14
15
|
import { Service } from '../src/services/core/base.js';
|
|
15
16
|
import { createMockService, MockService } from './mockService.js';
|
|
16
17
|
import { RegisteredService } from '../src/services/core/registered.js';
|
|
@@ -183,6 +184,26 @@ describe('extractUrlFromCurlArguments', () => {
|
|
|
183
184
|
expect(extractUrlFromCurlArguments(arguments_)).toBe('https://example.com/api?query=from%3Ame');
|
|
184
185
|
});
|
|
185
186
|
});
|
|
187
|
+
describe('resolveCurlRequestBodySource', () => {
|
|
188
|
+
it('should report an inline body when no file reference is present', () => {
|
|
189
|
+
expect(resolveCurlRequestBodySource(['-d', '{"a":1}', 'https://example.com'], '{"a":1}')).toEqual({ kind: 'inline', text: '{"a":1}' });
|
|
190
|
+
});
|
|
191
|
+
it('should report an inline body for --data-raw, which treats @ literally', () => {
|
|
192
|
+
expect(resolveCurlRequestBodySource(['--data-raw', '@literal', 'https://example.com'], '@literal')).toEqual({ kind: 'inline', text: '@literal' });
|
|
193
|
+
});
|
|
194
|
+
it('should report a file reference', () => {
|
|
195
|
+
expect(resolveCurlRequestBodySource(['--data-binary', '@/tmp/payload.json', 'https://example.com'], '@/tmp/payload.json')).toEqual({ kind: 'file', path: '/tmp/payload.json' });
|
|
196
|
+
});
|
|
197
|
+
it('should report stdin for @-', () => {
|
|
198
|
+
expect(resolveCurlRequestBodySource(['--data-binary', '@-', 'https://example.com'], '@-')).toEqual({ kind: 'stdin' });
|
|
199
|
+
});
|
|
200
|
+
it('should report an unreconstructable body for combined data arguments', () => {
|
|
201
|
+
expect(resolveCurlRequestBodySource(['-d', '@/tmp/payload.json', '-d', 'extra=1', 'https://example.com'], '@/tmp/payload.json&extra=1')).toEqual({ kind: 'unreconstructable' });
|
|
202
|
+
});
|
|
203
|
+
it('should report an unreconstructable body for url-encoded file data', () => {
|
|
204
|
+
expect(resolveCurlRequestBodySource(['--data-urlencode', '@/tmp/payload.json', 'https://example.com'], '@/tmp/payload.json')).toEqual({ kind: 'unreconstructable' });
|
|
205
|
+
});
|
|
206
|
+
});
|
|
186
207
|
describe('hasGraphicalEnvironment', () => {
|
|
187
208
|
const originalPlatform = process.platform;
|
|
188
209
|
afterEach(() => {
|
|
@@ -345,6 +366,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
345
366
|
const mockRegistry = new ServiceRegistry([mockSlackService]);
|
|
346
367
|
return {
|
|
347
368
|
registry: mockRegistry,
|
|
369
|
+
builtinServices: [mockSlackService],
|
|
348
370
|
config: createMockConfig(),
|
|
349
371
|
runCurl: (args) => {
|
|
350
372
|
capturedArgs.push(...args);
|
|
@@ -973,11 +995,35 @@ describe('CLI commands with dependency injection', () => {
|
|
|
973
995
|
function withStdinKey(key, overrides = {}) {
|
|
974
996
|
return createMockDependencies({ readStdin: () => Promise.resolve(key), ...overrides });
|
|
975
997
|
}
|
|
976
|
-
// The
|
|
998
|
+
// The filenames used by the mock config.
|
|
977
999
|
const STORE_FILENAME = 'credentials.json';
|
|
978
|
-
|
|
979
|
-
|
|
1000
|
+
const BROWSER_STATE_FILENAME = 'browser_state.json';
|
|
1001
|
+
function writeStore(contents, preparations = {}) {
|
|
1002
|
+
writeSecureFile(join(tempDir, STORE_FILENAME), JSON.stringify({ ...nestAccounts(contents), preparations }));
|
|
1003
|
+
}
|
|
1004
|
+
// Write an on-disk store with explicit account keys.
|
|
1005
|
+
function writeAccountStore(credentials, preparations = {}) {
|
|
1006
|
+
writeSecureFile(join(tempDir, STORE_FILENAME), JSON.stringify({ credentials, preparations }));
|
|
980
1007
|
}
|
|
1008
|
+
function readAccountsWithKey(path, key) {
|
|
1009
|
+
const raw = JSON.parse(new EncryptedStorage(key).readFile(path) ?? '{}');
|
|
1010
|
+
return raw.credentials ?? {};
|
|
1011
|
+
}
|
|
1012
|
+
function readPreparationsWithKey(path, key) {
|
|
1013
|
+
const raw = JSON.parse(new EncryptedStorage(key).readFile(path) ?? '{}');
|
|
1014
|
+
return raw.preparations ?? {};
|
|
1015
|
+
}
|
|
1016
|
+
// A destination that already holds a store must be in the current data
|
|
1017
|
+
// format, which real Latchkey directories record in this file.
|
|
1018
|
+
function stampDataFormatVersion(directory) {
|
|
1019
|
+
mkdirSync(directory, { recursive: true });
|
|
1020
|
+
writeFileSync(join(directory, 'data-format-version'), String(LATEST_VERSION));
|
|
1021
|
+
}
|
|
1022
|
+
const GITLAB_PREPARATION = {
|
|
1023
|
+
objectType: 'oauth',
|
|
1024
|
+
clientId: 'gitlab-client-id',
|
|
1025
|
+
clientSecret: 'gitlab-client-secret',
|
|
1026
|
+
};
|
|
981
1027
|
it('re-encrypts all stored credentials with the new key', async () => {
|
|
982
1028
|
writeStore({
|
|
983
1029
|
slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' },
|
|
@@ -1025,7 +1071,7 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1025
1071
|
const reEncrypted = readWithKey(join(destinationDirectory, STORE_FILENAME), NEW_ENCRYPTION_KEY);
|
|
1026
1072
|
expect(Object.keys(reEncrypted)).toEqual(['slack']);
|
|
1027
1073
|
});
|
|
1028
|
-
it('errors when a requested service has
|
|
1074
|
+
it('errors when a requested service has neither credentials nor a preparation', async () => {
|
|
1029
1075
|
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
|
|
1030
1076
|
const destinationDirectory = join(tempDir, 'export');
|
|
1031
1077
|
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
@@ -1033,6 +1079,170 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1033
1079
|
expect(exitCode).toBe(1);
|
|
1034
1080
|
expect(existsSync(join(destinationDirectory, STORE_FILENAME))).toBe(false);
|
|
1035
1081
|
});
|
|
1082
|
+
it('carries the preparations of the re-encrypted services', async () => {
|
|
1083
|
+
writeStore({ gitlab: { objectType: 'authorizationBearer', token: 'gitlab-token' } }, { gitlab: GITLAB_PREPARATION });
|
|
1084
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1085
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1086
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
|
|
1087
|
+
expect(exitCode).toBeNull();
|
|
1088
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1089
|
+
expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1090
|
+
gitlab: GITLAB_PREPARATION,
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
it('includes services that only have a preparation', async () => {
|
|
1094
|
+
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } }, { gitlab: GITLAB_PREPARATION });
|
|
1095
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1096
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1097
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
|
|
1098
|
+
expect(exitCode).toBeNull();
|
|
1099
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1100
|
+
expect(Object.keys(readWithKey(destination, NEW_ENCRYPTION_KEY))).toEqual(['slack']);
|
|
1101
|
+
expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1102
|
+
gitlab: GITLAB_PREPARATION,
|
|
1103
|
+
});
|
|
1104
|
+
expect(logs.join('\n')).toContain('2 service(s)');
|
|
1105
|
+
});
|
|
1106
|
+
it('selects a service that only has a preparation with --services', async () => {
|
|
1107
|
+
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } }, { gitlab: GITLAB_PREPARATION });
|
|
1108
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1109
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1110
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory, '--services', 'gitlab'], deps);
|
|
1111
|
+
expect(exitCode).toBeNull();
|
|
1112
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1113
|
+
expect(readWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({});
|
|
1114
|
+
expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1115
|
+
gitlab: GITLAB_PREPARATION,
|
|
1116
|
+
});
|
|
1117
|
+
});
|
|
1118
|
+
it('replaces an existing preparation in the destination', async () => {
|
|
1119
|
+
writeStore({}, { gitlab: GITLAB_PREPARATION });
|
|
1120
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1121
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1122
|
+
stampDataFormatVersion(destinationDirectory);
|
|
1123
|
+
new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(destination, JSON.stringify({
|
|
1124
|
+
credentials: {},
|
|
1125
|
+
preparations: {
|
|
1126
|
+
gitlab: { objectType: 'oauth', clientId: 'old-id', clientSecret: 'old-secret' },
|
|
1127
|
+
},
|
|
1128
|
+
}));
|
|
1129
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1130
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
|
|
1131
|
+
expect(exitCode).toBeNull();
|
|
1132
|
+
expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1133
|
+
gitlab: GITLAB_PREPARATION,
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
it('only includes the given account when using --account', async () => {
|
|
1137
|
+
writeAccountStore({
|
|
1138
|
+
slack: {
|
|
1139
|
+
'work@example.com': { objectType: 'slack', token: 'work-tok', dCookie: 'work-cookie' },
|
|
1140
|
+
'home@example.com': { objectType: 'slack', token: 'home-tok', dCookie: 'home-cookie' },
|
|
1141
|
+
},
|
|
1142
|
+
gitlab: {
|
|
1143
|
+
'work@example.com': { objectType: 'authorizationBearer', token: 'gitlab-token' },
|
|
1144
|
+
},
|
|
1145
|
+
}, { gitlab: GITLAB_PREPARATION });
|
|
1146
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1147
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1148
|
+
await runCommand(['--account', 'work@example.com', 'auth', 're-encrypt', destinationDirectory], deps);
|
|
1149
|
+
expect(exitCode).toBeNull();
|
|
1150
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1151
|
+
expect(readAccountsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1152
|
+
slack: {
|
|
1153
|
+
'work@example.com': { objectType: 'slack', token: 'work-tok', dCookie: 'work-cookie' },
|
|
1154
|
+
},
|
|
1155
|
+
gitlab: {
|
|
1156
|
+
'work@example.com': { objectType: 'authorizationBearer', token: 'gitlab-token' },
|
|
1157
|
+
},
|
|
1158
|
+
});
|
|
1159
|
+
// Preparations are not account-specific and are carried over as usual.
|
|
1160
|
+
expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1161
|
+
gitlab: GITLAB_PREPARATION,
|
|
1162
|
+
});
|
|
1163
|
+
});
|
|
1164
|
+
it('keeps the other accounts of the destination when using --account', async () => {
|
|
1165
|
+
writeAccountStore({
|
|
1166
|
+
slack: {
|
|
1167
|
+
'work@example.com': { objectType: 'slack', token: 'new-tok', dCookie: 'new-cookie' },
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1171
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1172
|
+
stampDataFormatVersion(destinationDirectory);
|
|
1173
|
+
new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(destination, JSON.stringify({
|
|
1174
|
+
credentials: {
|
|
1175
|
+
slack: {
|
|
1176
|
+
'work@example.com': { objectType: 'slack', token: 'old-tok', dCookie: 'old-cookie' },
|
|
1177
|
+
'home@example.com': {
|
|
1178
|
+
objectType: 'slack',
|
|
1179
|
+
token: 'home-tok',
|
|
1180
|
+
dCookie: 'home-cookie',
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
preparations: {},
|
|
1185
|
+
}));
|
|
1186
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1187
|
+
await runCommand(['--account', 'work@example.com', 'auth', 're-encrypt', destinationDirectory], deps);
|
|
1188
|
+
expect(exitCode).toBeNull();
|
|
1189
|
+
expect(readAccountsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1190
|
+
slack: {
|
|
1191
|
+
'work@example.com': { objectType: 'slack', token: 'new-tok', dCookie: 'new-cookie' },
|
|
1192
|
+
'home@example.com': { objectType: 'slack', token: 'home-tok', dCookie: 'home-cookie' },
|
|
1193
|
+
},
|
|
1194
|
+
});
|
|
1195
|
+
});
|
|
1196
|
+
it('leaves the destination account alone when the source does not have it', async () => {
|
|
1197
|
+
writeAccountStore({
|
|
1198
|
+
slack: {
|
|
1199
|
+
'work@example.com': { objectType: 'slack', token: 'work-tok', dCookie: 'work-cookie' },
|
|
1200
|
+
},
|
|
1201
|
+
}, { gitlab: GITLAB_PREPARATION });
|
|
1202
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1203
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1204
|
+
stampDataFormatVersion(destinationDirectory);
|
|
1205
|
+
new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(destination, JSON.stringify({
|
|
1206
|
+
credentials: {
|
|
1207
|
+
// gitlab only has a preparation in the source, so its credentials
|
|
1208
|
+
// in the destination must survive.
|
|
1209
|
+
gitlab: {
|
|
1210
|
+
'work@example.com': {
|
|
1211
|
+
objectType: 'authorizationBearer',
|
|
1212
|
+
token: 'destination-token',
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
},
|
|
1216
|
+
preparations: {},
|
|
1217
|
+
}));
|
|
1218
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1219
|
+
await runCommand(['--account', 'work@example.com', 'auth', 're-encrypt', destinationDirectory], deps);
|
|
1220
|
+
expect(exitCode).toBeNull();
|
|
1221
|
+
expect(readAccountsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1222
|
+
gitlab: {
|
|
1223
|
+
'work@example.com': { objectType: 'authorizationBearer', token: 'destination-token' },
|
|
1224
|
+
},
|
|
1225
|
+
slack: {
|
|
1226
|
+
'work@example.com': { objectType: 'slack', token: 'work-tok', dCookie: 'work-cookie' },
|
|
1227
|
+
},
|
|
1228
|
+
});
|
|
1229
|
+
expect(readPreparationsWithKey(destination, NEW_ENCRYPTION_KEY)).toEqual({
|
|
1230
|
+
gitlab: GITLAB_PREPARATION,
|
|
1231
|
+
});
|
|
1232
|
+
});
|
|
1233
|
+
it('errors when no stored credentials belong to the given account', async () => {
|
|
1234
|
+
writeAccountStore({
|
|
1235
|
+
slack: {
|
|
1236
|
+
'work@example.com': { objectType: 'slack', token: 'work-tok', dCookie: 'work-cookie' },
|
|
1237
|
+
},
|
|
1238
|
+
});
|
|
1239
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1240
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1241
|
+
await runCommand(['--account', 'missing@example.com', 'auth', 're-encrypt', destinationDirectory], deps);
|
|
1242
|
+
expect(exitCode).toBe(1);
|
|
1243
|
+
expect(errorLogs.join('\n')).toContain('missing@example.com');
|
|
1244
|
+
expect(existsSync(join(destinationDirectory, STORE_FILENAME))).toBe(false);
|
|
1245
|
+
});
|
|
1036
1246
|
it('errors for an invalid encryption key read from stdin', async () => {
|
|
1037
1247
|
writeStore({});
|
|
1038
1248
|
const destinationDirectory = join(tempDir, 'export');
|
|
@@ -1049,10 +1259,36 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1049
1259
|
expect(exitCode).toBeNull();
|
|
1050
1260
|
expect(Object.keys(readWithKey(join(destinationDirectory, STORE_FILENAME), NEW_ENCRYPTION_KEY))).toEqual(['slack']);
|
|
1051
1261
|
});
|
|
1052
|
-
it('
|
|
1262
|
+
it('adds the services into an existing credential store, overwriting by service', async () => {
|
|
1263
|
+
writeStore({
|
|
1264
|
+
slack: { objectType: 'slack', token: 'new-tok', dCookie: 'new-cookie' },
|
|
1265
|
+
});
|
|
1266
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1267
|
+
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1268
|
+
stampDataFormatVersion(destinationDirectory);
|
|
1269
|
+
new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(destination, JSON.stringify(nestAccounts({
|
|
1270
|
+
slack: { objectType: 'slack', token: 'old-tok', dCookie: 'old-cookie' },
|
|
1271
|
+
discord: { objectType: 'authorizationBearer', token: 'discord-token' },
|
|
1272
|
+
})));
|
|
1273
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1274
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory], deps);
|
|
1275
|
+
expect(exitCode).toBeNull();
|
|
1276
|
+
const reEncrypted = readWithKey(destination, NEW_ENCRYPTION_KEY);
|
|
1277
|
+
expect(Object.keys(reEncrypted).sort()).toEqual(['discord', 'slack']);
|
|
1278
|
+
expect(reEncrypted.slack).toEqual({
|
|
1279
|
+
objectType: 'slack',
|
|
1280
|
+
token: 'new-tok',
|
|
1281
|
+
dCookie: 'new-cookie',
|
|
1282
|
+
});
|
|
1283
|
+
expect(reEncrypted.discord).toEqual({
|
|
1284
|
+
objectType: 'authorizationBearer',
|
|
1285
|
+
token: 'discord-token',
|
|
1286
|
+
});
|
|
1287
|
+
});
|
|
1288
|
+
it('errors when the existing credential store cannot be read with the new key', async () => {
|
|
1053
1289
|
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
|
|
1054
1290
|
const destinationDirectory = join(tempDir, 'export');
|
|
1055
|
-
|
|
1291
|
+
stampDataFormatVersion(destinationDirectory);
|
|
1056
1292
|
const destination = join(destinationDirectory, STORE_FILENAME);
|
|
1057
1293
|
writeFileSync(destination, 'existing');
|
|
1058
1294
|
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
@@ -1060,6 +1296,36 @@ describe('CLI commands with dependency injection', () => {
|
|
|
1060
1296
|
expect(exitCode).toBe(1);
|
|
1061
1297
|
expect(readFileSync(destination, 'utf-8')).toBe('existing');
|
|
1062
1298
|
});
|
|
1299
|
+
it('re-encrypts the browser state with --browser-state', async () => {
|
|
1300
|
+
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
|
|
1301
|
+
new EncryptedStorage(TEST_ENCRYPTION_KEY).writeFile(join(tempDir, BROWSER_STATE_FILENAME), '{"cookies":[]}');
|
|
1302
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1303
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1304
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory, '--browser-state'], deps);
|
|
1305
|
+
expect(exitCode).toBeNull();
|
|
1306
|
+
const browserStateDestination = join(destinationDirectory, BROWSER_STATE_FILENAME);
|
|
1307
|
+
expect(new EncryptedStorage(NEW_ENCRYPTION_KEY).readFile(browserStateDestination)).toBe('{"cookies":[]}');
|
|
1308
|
+
expect(Object.keys(readWithKey(join(destinationDirectory, STORE_FILENAME), NEW_ENCRYPTION_KEY))).toEqual(['slack']);
|
|
1309
|
+
});
|
|
1310
|
+
it('errors with --browser-state when there is no browser state', async () => {
|
|
1311
|
+
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
|
|
1312
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1313
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1314
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory, '--browser-state'], deps);
|
|
1315
|
+
expect(exitCode).toBe(1);
|
|
1316
|
+
expect(existsSync(join(destinationDirectory, STORE_FILENAME))).toBe(false);
|
|
1317
|
+
});
|
|
1318
|
+
it('overwrites an existing browser state in the destination', async () => {
|
|
1319
|
+
writeStore({});
|
|
1320
|
+
new EncryptedStorage(TEST_ENCRYPTION_KEY).writeFile(join(tempDir, BROWSER_STATE_FILENAME), '{"cookies":["new"]}');
|
|
1321
|
+
const destinationDirectory = join(tempDir, 'export');
|
|
1322
|
+
const browserStateDestination = join(destinationDirectory, BROWSER_STATE_FILENAME);
|
|
1323
|
+
new EncryptedStorage(NEW_ENCRYPTION_KEY).writeFile(browserStateDestination, 'old');
|
|
1324
|
+
const deps = withStdinKey(NEW_ENCRYPTION_KEY);
|
|
1325
|
+
await runCommand(['auth', 're-encrypt', destinationDirectory, '--browser-state'], deps);
|
|
1326
|
+
expect(exitCode).toBeNull();
|
|
1327
|
+
expect(new EncryptedStorage(NEW_ENCRYPTION_KEY).readFile(browserStateDestination)).toBe('{"cookies":["new"]}');
|
|
1328
|
+
});
|
|
1063
1329
|
it('errors when the destination is an existing file, not a directory', async () => {
|
|
1064
1330
|
writeStore({ slack: { objectType: 'slack', token: 'tok', dCookie: 'cookie' } });
|
|
1065
1331
|
const destinationDirectory = join(tempDir, 'export');
|