latchkey 2.21.0 → 3.0.1
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 +36 -4
- package/dist/skills/generic/latchkey/SKILL.md +21 -0
- package/dist/skills/openclaw/latchkey/SKILL.md +21 -0
- package/dist/src/apiCredentials/account.d.ts +28 -0
- package/dist/src/apiCredentials/account.d.ts.map +1 -0
- package/dist/src/apiCredentials/account.js +49 -0
- package/dist/src/apiCredentials/account.js.map +1 -0
- package/dist/src/apiCredentials/store.d.ts +70 -4
- package/dist/src/apiCredentials/store.d.ts.map +1 -1
- package/dist/src/apiCredentials/store.js +161 -19
- package/dist/src/apiCredentials/store.js.map +1 -1
- package/dist/src/apiCredentials/utils.d.ts +2 -2
- package/dist/src/apiCredentials/utils.d.ts.map +1 -1
- package/dist/src/apiCredentials/utils.js +5 -5
- package/dist/src/apiCredentials/utils.js.map +1 -1
- package/dist/src/cli.js +1 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/cliCommands.js +118 -22
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/config.d.ts +6 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +8 -0
- package/dist/src/config.js.map +1 -1
- package/dist/src/configDataStore.d.ts +3 -0
- package/dist/src/configDataStore.d.ts.map +1 -1
- package/dist/src/configDataStore.js +1 -0
- package/dist/src/configDataStore.js.map +1 -1
- package/dist/src/curl.d.ts +21 -16
- package/dist/src/curl.d.ts.map +1 -1
- package/dist/src/curl.js +17 -24
- package/dist/src/curl.js.map +1 -1
- package/dist/src/curlInjection.d.ts +8 -2
- package/dist/src/curlInjection.d.ts.map +1 -1
- package/dist/src/curlInjection.js +13 -8
- package/dist/src/curlInjection.js.map +1 -1
- package/dist/src/errorMessages.d.ts +2 -2
- package/dist/src/errorMessages.d.ts.map +1 -1
- package/dist/src/errorMessages.js +22 -6
- package/dist/src/errorMessages.js.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.d.ts +1 -1
- package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -1
- package/dist/src/gateway/gatewayEndpoint.js +3 -1
- package/dist/src/gateway/gatewayEndpoint.js.map +1 -1
- package/dist/src/gateway/latchkeyEndpoint.d.ts +6 -0
- package/dist/src/gateway/latchkeyEndpoint.d.ts.map +1 -1
- package/dist/src/gateway/latchkeyEndpoint.js +6 -4
- package/dist/src/gateway/latchkeyEndpoint.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 +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/migrations.d.ts +27 -1
- package/dist/src/migrations.d.ts.map +1 -1
- package/dist/src/migrations.js +141 -4
- package/dist/src/migrations.js.map +1 -1
- package/dist/src/oauthUtils.d.ts +2 -2
- package/dist/src/oauthUtils.d.ts.map +1 -1
- package/dist/src/oauthUtils.js +5 -5
- package/dist/src/oauthUtils.js.map +1 -1
- package/dist/src/services/aws.d.ts +1 -0
- package/dist/src/services/aws.d.ts.map +1 -1
- package/dist/src/services/aws.js +13 -0
- package/dist/src/services/aws.js.map +1 -1
- package/dist/src/services/calendly.d.ts +2 -0
- package/dist/src/services/calendly.d.ts.map +1 -1
- package/dist/src/services/calendly.js +7 -0
- package/dist/src/services/calendly.js.map +1 -1
- package/dist/src/services/coolify.d.ts +3 -1
- package/dist/src/services/coolify.d.ts.map +1 -1
- package/dist/src/services/coolify.js +10 -1
- package/dist/src/services/coolify.js.map +1 -1
- package/dist/src/services/core/base.d.ts +43 -1
- package/dist/src/services/core/base.d.ts.map +1 -1
- package/dist/src/services/core/base.js +29 -9
- package/dist/src/services/core/base.js.map +1 -1
- package/dist/src/services/core/registered.d.ts +1 -0
- package/dist/src/services/core/registered.d.ts.map +1 -1
- package/dist/src/services/core/registered.js +5 -0
- package/dist/src/services/core/registered.js.map +1 -1
- package/dist/src/services/discord.d.ts +1 -0
- package/dist/src/services/discord.d.ts.map +1 -1
- package/dist/src/services/discord.js +9 -0
- package/dist/src/services/discord.js.map +1 -1
- package/dist/src/services/dropbox.d.ts +2 -1
- package/dist/src/services/dropbox.d.ts.map +1 -1
- package/dist/src/services/dropbox.js +17 -11
- package/dist/src/services/dropbox.js.map +1 -1
- package/dist/src/services/figma.d.ts +2 -0
- package/dist/src/services/figma.d.ts.map +1 -1
- package/dist/src/services/figma.js +7 -0
- package/dist/src/services/figma.js.map +1 -1
- package/dist/src/services/github.d.ts +1 -0
- package/dist/src/services/github.d.ts.map +1 -1
- package/dist/src/services/github.js +9 -0
- package/dist/src/services/github.js.map +1 -1
- package/dist/src/services/gitlab.d.ts +2 -0
- package/dist/src/services/gitlab.d.ts.map +1 -1
- package/dist/src/services/gitlab.js +9 -0
- package/dist/src/services/gitlab.js.map +1 -1
- package/dist/src/services/google/analytics.d.ts +0 -1
- package/dist/src/services/google/analytics.d.ts.map +1 -1
- package/dist/src/services/google/analytics.js +0 -3
- package/dist/src/services/google/analytics.js.map +1 -1
- package/dist/src/services/google/base.d.ts +11 -0
- package/dist/src/services/google/base.d.ts.map +1 -1
- package/dist/src/services/google/base.js +26 -7
- package/dist/src/services/google/base.js.map +1 -1
- package/dist/src/services/google/calendar.d.ts +0 -1
- package/dist/src/services/google/calendar.d.ts.map +1 -1
- package/dist/src/services/google/calendar.js +0 -3
- package/dist/src/services/google/calendar.js.map +1 -1
- package/dist/src/services/google/directions.d.ts +1 -0
- package/dist/src/services/google/directions.d.ts.map +1 -1
- package/dist/src/services/google/directions.js +5 -0
- package/dist/src/services/google/directions.js.map +1 -1
- package/dist/src/services/google/docs.d.ts +0 -1
- package/dist/src/services/google/docs.d.ts.map +1 -1
- package/dist/src/services/google/docs.js +0 -3
- package/dist/src/services/google/docs.js.map +1 -1
- package/dist/src/services/google/drive.d.ts +0 -1
- package/dist/src/services/google/drive.d.ts.map +1 -1
- package/dist/src/services/google/drive.js +0 -3
- package/dist/src/services/google/drive.js.map +1 -1
- package/dist/src/services/google/gmail.d.ts +0 -1
- package/dist/src/services/google/gmail.d.ts.map +1 -1
- package/dist/src/services/google/gmail.js +0 -3
- package/dist/src/services/google/gmail.js.map +1 -1
- package/dist/src/services/google/people.d.ts +0 -1
- package/dist/src/services/google/people.d.ts.map +1 -1
- package/dist/src/services/google/people.js +0 -3
- package/dist/src/services/google/people.js.map +1 -1
- package/dist/src/services/google/sheets.d.ts +0 -1
- package/dist/src/services/google/sheets.d.ts.map +1 -1
- package/dist/src/services/google/sheets.js +0 -3
- package/dist/src/services/google/sheets.js.map +1 -1
- package/dist/src/services/google/slides.d.ts +0 -1
- package/dist/src/services/google/slides.d.ts.map +1 -1
- package/dist/src/services/google/slides.js +0 -3
- package/dist/src/services/google/slides.js.map +1 -1
- package/dist/src/services/index.d.ts +1 -1
- package/dist/src/services/index.d.ts.map +1 -1
- package/dist/src/services/index.js.map +1 -1
- package/dist/src/services/linear.d.ts +2 -1
- package/dist/src/services/linear.d.ts.map +1 -1
- package/dist/src/services/linear.js +8 -1
- package/dist/src/services/linear.js.map +1 -1
- package/dist/src/services/mailchimp.d.ts +2 -0
- package/dist/src/services/mailchimp.d.ts.map +1 -1
- package/dist/src/services/mailchimp.js +7 -0
- package/dist/src/services/mailchimp.js.map +1 -1
- package/dist/src/services/notion-mcp.d.ts +3 -2
- package/dist/src/services/notion-mcp.d.ts.map +1 -1
- package/dist/src/services/notion-mcp.js +111 -14
- package/dist/src/services/notion-mcp.js.map +1 -1
- package/dist/src/services/notion.d.ts +2 -0
- package/dist/src/services/notion.d.ts.map +1 -1
- package/dist/src/services/notion.js +17 -0
- package/dist/src/services/notion.js.map +1 -1
- package/dist/src/services/ramp.d.ts +11 -2
- package/dist/src/services/ramp.d.ts.map +1 -1
- package/dist/src/services/ramp.js +35 -9
- package/dist/src/services/ramp.js.map +1 -1
- package/dist/src/services/sentry.d.ts +3 -2
- package/dist/src/services/sentry.d.ts.map +1 -1
- package/dist/src/services/sentry.js +12 -27
- package/dist/src/services/sentry.js.map +1 -1
- package/dist/src/services/slack.d.ts +3 -2
- package/dist/src/services/slack.d.ts.map +1 -1
- package/dist/src/services/slack.js +19 -13
- package/dist/src/services/slack.js.map +1 -1
- package/dist/src/services/stripe.d.ts +7 -0
- package/dist/src/services/stripe.d.ts.map +1 -1
- package/dist/src/services/stripe.js +15 -0
- package/dist/src/services/stripe.js.map +1 -1
- package/dist/src/services/telegram.d.ts +1 -0
- package/dist/src/services/telegram.d.ts.map +1 -1
- package/dist/src/services/telegram.js +7 -0
- package/dist/src/services/telegram.js.map +1 -1
- package/dist/src/services/todoist.d.ts +2 -1
- package/dist/src/services/todoist.d.ts.map +1 -1
- package/dist/src/services/todoist.js +9 -1
- package/dist/src/services/todoist.js.map +1 -1
- package/dist/src/services/umami.d.ts +3 -1
- package/dist/src/services/umami.d.ts.map +1 -1
- package/dist/src/services/umami.js +11 -1
- package/dist/src/services/umami.js.map +1 -1
- package/dist/src/services/yelp.d.ts +1 -0
- package/dist/src/services/yelp.d.ts.map +1 -1
- package/dist/src/services/yelp.js +5 -0
- package/dist/src/services/yelp.js.map +1 -1
- package/dist/src/services/zoom.d.ts +8 -0
- package/dist/src/services/zoom.d.ts.map +1 -1
- package/dist/src/services/zoom.js +16 -0
- package/dist/src/services/zoom.js.map +1 -1
- package/dist/src/sharedOperations.d.ts +30 -10
- package/dist/src/sharedOperations.d.ts.map +1 -1
- package/dist/src/sharedOperations.js +67 -29
- package/dist/src/sharedOperations.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 +155 -2
- package/dist/tests/apiCredentialStore.test.js.map +1 -1
- package/dist/tests/apiCredentialsUtils.test.js +1 -1
- package/dist/tests/apiCredentialsUtils.test.js.map +1 -1
- package/dist/tests/cli.test.js +334 -147
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/config.test.js +5 -0
- package/dist/tests/config.test.js.map +1 -1
- package/dist/tests/gateway.test.js +8 -18
- package/dist/tests/gateway.test.js.map +1 -1
- package/dist/tests/latchkeyEndpoint.test.js +18 -28
- package/dist/tests/latchkeyEndpoint.test.js.map +1 -1
- package/dist/tests/migrations.test.js +299 -39
- package/dist/tests/migrations.test.js.map +1 -1
- package/dist/tests/mockService.d.ts +25 -0
- package/dist/tests/mockService.d.ts.map +1 -0
- package/dist/tests/mockService.js +40 -0
- package/dist/tests/mockService.js.map +1 -0
- package/dist/tests/oauthUtils.test.js +12 -12
- package/dist/tests/oauthUtils.test.js.map +1 -1
- package/dist/tests/serviceAccounts.test.d.ts +12 -0
- package/dist/tests/serviceAccounts.test.d.ts.map +1 -0
- package/dist/tests/serviceAccounts.test.js +278 -0
- package/dist/tests/serviceAccounts.test.js.map +1 -0
- package/dist/tests/sharedOperations.test.js +327 -58
- package/dist/tests/sharedOperations.test.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,6 +162,33 @@ code, stdout and stderr are passed back from curl to the caller
|
|
|
162
162
|
of `latchkey`.
|
|
163
163
|
|
|
164
164
|
|
|
165
|
+
### Multiple accounts
|
|
166
|
+
|
|
167
|
+
Credentials for a service can be stored under several accounts.
|
|
168
|
+
An account is a string that uniquely identifies the account
|
|
169
|
+
behind the credentials - typically an e-mail, but for some
|
|
170
|
+
services it may be an id. Use the global `--account` option to
|
|
171
|
+
select one:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Store credentials for a specific account.
|
|
175
|
+
latchkey --account bob@example.com auth set slack -H "Authorization: Bearer xoxb-..."
|
|
176
|
+
|
|
177
|
+
# Use them in a request.
|
|
178
|
+
latchkey --account bob@example.com curl https://slack.com/api/auth.test
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
When `--account` is omitted, the single stored account is used
|
|
182
|
+
automatically. If a service has more than one stored account,
|
|
183
|
+
`--account` becomes required. Credentials stored without an
|
|
184
|
+
account live under a "default" account (empty string, `""`).
|
|
185
|
+
|
|
186
|
+
Browser logins (`latchkey auth browser`) determine the account
|
|
187
|
+
automatically. The `--account` option here merely allows users
|
|
188
|
+
to specify which existing OAuth client configuration to use for
|
|
189
|
+
the login (selecting one associated with an existing account) if
|
|
190
|
+
needed.
|
|
191
|
+
|
|
165
192
|
### Self-hosted services
|
|
166
193
|
|
|
167
194
|
For services that can be self-hosted, like GitLab, first make Latchkey aware of your service instance:
|
|
@@ -226,15 +253,18 @@ calls.
|
|
|
226
253
|
|
|
227
254
|
### Inspecting the status of stored credentials
|
|
228
255
|
|
|
229
|
-
Calling `latchkey services info <service_name>` will show
|
|
230
|
-
about the service, including
|
|
231
|
-
|
|
256
|
+
Calling `latchkey services info <service_name>` will show
|
|
257
|
+
information about the service, including a `credentials` object
|
|
258
|
+
keyed by account (the default account uses the empty string).
|
|
259
|
+
Each entry reports a credentials status of:
|
|
232
260
|
|
|
233
|
-
- `missing`
|
|
234
261
|
- `invalid`
|
|
235
262
|
- `valid`
|
|
236
263
|
- `unknown` (for user-registered services)
|
|
237
264
|
|
|
265
|
+
A service with no stored credentials shows an empty `credentials`
|
|
266
|
+
object.
|
|
267
|
+
|
|
238
268
|
### Clearing credentials
|
|
239
269
|
|
|
240
270
|
Remembered API credentials can expire. The caller of `latchkey
|
|
@@ -384,6 +414,7 @@ defaults:
|
|
|
384
414
|
- `LATCHKEY_KEYRING_SERVICE_NAME`, `LATCHKEY_KEYRING_ACCOUNT_NAME`: identifiers that are used to store the encryption password in your keyring
|
|
385
415
|
- `LATCHKEY_ENCRYPTION_KEY`: override the encryption key, e.g. when a keyring is not available. Example: `export LATCHKEY_ENCRYPTION_KEY="$(openssl rand -base64 32)"`
|
|
386
416
|
- `LATCHKEY_DISABLE_BROWSER`: when set to a non-empty value, disables the browser login flow; commands that would trigger a browser login (`auth browser`, `auth browser-prepare`) will fail with an error instead
|
|
417
|
+
- `LATCHKEY_EPHEMERAL_BROWSER`: when set to a non-empty value, browser flows operate similarly to the browser incognito mode. Useful when logging into multiple accounts.
|
|
387
418
|
- `LATCHKEY_DISABLE_COUNTING`: when set to a non-empty value, disables daily usage counting.
|
|
388
419
|
- `LATCHKEY_DISABLE_CREDENTIALS_REFRESH`: when set to a non-empty value, expired credentials are never refreshed. (Useful when the credentials are shared and refreshing in one place would risk exhausting the refresh token for the other one.)
|
|
389
420
|
- `LATCHKEY_PERMISSIONS_CONFIG`: override the `permissions.json` location.
|
|
@@ -411,6 +442,7 @@ override `config.json` values.
|
|
|
411
442
|
"keyringServiceName": "latchkey",
|
|
412
443
|
"keyringAccountName": "encryption-key",
|
|
413
444
|
"browserDisabled": false,
|
|
445
|
+
"browserEphemeral": false,
|
|
414
446
|
"countingDisabled": true,
|
|
415
447
|
"permissionsConfig": "/etc/latchkey/permissions.json",
|
|
416
448
|
"permissionsDoNotUseBuiltinSchemas": false,
|
|
@@ -95,6 +95,27 @@ credentials yet. `latchkey service info <service_name>` can be
|
|
|
95
95
|
used to see how to provide credentials for a specific service.
|
|
96
96
|
|
|
97
97
|
|
|
98
|
+
## Using multiple accounts
|
|
99
|
+
|
|
100
|
+
It is possible to associate credentials with a specific account:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
latchkey --account alice@example.com auth set ...
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
And then reference it in curl calls:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
latchkey --account alice@example.com curl ...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The `--account` option must go right after `latchkey`.
|
|
113
|
+
|
|
114
|
+
You can see the existing accounts as keys in the credential
|
|
115
|
+
dictionary produced by `latchkey services info`. An empty string
|
|
116
|
+
in the key means "unknown account".
|
|
117
|
+
|
|
118
|
+
|
|
98
119
|
## Notes
|
|
99
120
|
|
|
100
121
|
- All curl arguments are passed through unchanged
|
|
@@ -87,6 +87,27 @@ credentials yet. `latchkey service info <service_name>` can be
|
|
|
87
87
|
used to see how to provide credentials for a specific service.
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
## Using multiple accounts
|
|
91
|
+
|
|
92
|
+
It is possible to associate credentials with a specific account:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
latchkey --account alice@example.com auth set ...
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
And then reference it in curl calls:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
latchkey --account alice@example.com curl ...
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The `--account` option must go right after `latchkey`.
|
|
105
|
+
|
|
106
|
+
You can see the existing accounts as keys in the credential
|
|
107
|
+
dictionary produced by `latchkey services info`. An empty string
|
|
108
|
+
in the key means "unknown account".
|
|
109
|
+
|
|
110
|
+
|
|
90
111
|
## Notes
|
|
91
112
|
|
|
92
113
|
- All curl arguments are passed through unchanged
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account identifiers for stored credentials, and helpers for determining the
|
|
3
|
+
* account behind a set of credentials.
|
|
4
|
+
*
|
|
5
|
+
* An account is a string that uniquely identifies the account behind a set of
|
|
6
|
+
* credentials (typically an e-mail, sometimes an opaque id). The empty string
|
|
7
|
+
* denotes the "default" account.
|
|
8
|
+
*
|
|
9
|
+
* This module must not import the credential store or any service module, so
|
|
10
|
+
* that service implementations can use it without creating an import cycle
|
|
11
|
+
* (store -> serialization -> service -> core/base -> store).
|
|
12
|
+
*/
|
|
13
|
+
import { type ApiCredentials } from './base.js';
|
|
14
|
+
export declare const DEFAULT_ACCOUNT = "";
|
|
15
|
+
/**
|
|
16
|
+
* Parse a JSON response body, returning null instead of throwing on malformed
|
|
17
|
+
* input. Response bodies come from arbitrary servers, so account parsing must
|
|
18
|
+
* never crash on unexpected content.
|
|
19
|
+
*/
|
|
20
|
+
export declare function tryParseJson(body: string): unknown;
|
|
21
|
+
/**
|
|
22
|
+
* Fetch the account behind the given credentials from an identity-revealing
|
|
23
|
+
* endpoint, best-effort: returns null when the credentials cannot be injected
|
|
24
|
+
* or when the parser finds no identity in the response body. The shared
|
|
25
|
+
* implementation behind the services' `getAccount()`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function fetchAccountFromEndpoint(apiCredentials: ApiCredentials, accountCurlArguments: readonly string[], parseAccount: (responseBody: string) => string | null): Promise<string | null>;
|
|
28
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../../../src/apiCredentials/account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,cAAc,EAA4B,MAAM,WAAW,CAAC;AAG1E,eAAO,MAAM,eAAe,KAAK,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMlD;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,cAAc,EAAE,cAAc,EAC9B,oBAAoB,EAAE,SAAS,MAAM,EAAE,EACvC,YAAY,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,GACpD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAYxB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account identifiers for stored credentials, and helpers for determining the
|
|
3
|
+
* account behind a set of credentials.
|
|
4
|
+
*
|
|
5
|
+
* An account is a string that uniquely identifies the account behind a set of
|
|
6
|
+
* credentials (typically an e-mail, sometimes an opaque id). The empty string
|
|
7
|
+
* denotes the "default" account.
|
|
8
|
+
*
|
|
9
|
+
* This module must not import the credential store or any service module, so
|
|
10
|
+
* that service implementations can use it without creating an import cycle
|
|
11
|
+
* (store -> serialization -> service -> core/base -> store).
|
|
12
|
+
*/
|
|
13
|
+
import { ApiCredentialsUsageError } from './base.js';
|
|
14
|
+
import { runCapturedAsync } from '../curl.js';
|
|
15
|
+
export const DEFAULT_ACCOUNT = '';
|
|
16
|
+
/**
|
|
17
|
+
* Parse a JSON response body, returning null instead of throwing on malformed
|
|
18
|
+
* input. Response bodies come from arbitrary servers, so account parsing must
|
|
19
|
+
* never crash on unexpected content.
|
|
20
|
+
*/
|
|
21
|
+
export function tryParseJson(body) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(body);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Fetch the account behind the given credentials from an identity-revealing
|
|
31
|
+
* endpoint, best-effort: returns null when the credentials cannot be injected
|
|
32
|
+
* or when the parser finds no identity in the response body. The shared
|
|
33
|
+
* implementation behind the services' `getAccount()`.
|
|
34
|
+
*/
|
|
35
|
+
export async function fetchAccountFromEndpoint(apiCredentials, accountCurlArguments, parseAccount) {
|
|
36
|
+
let curlArguments;
|
|
37
|
+
try {
|
|
38
|
+
curlArguments = await apiCredentials.injectIntoCurlCall(['-s', ...accountCurlArguments]);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof ApiCredentialsUsageError) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const result = await runCapturedAsync(curlArguments, 10);
|
|
47
|
+
return parseAccount(result.stdout);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/apiCredentials/account.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAuB,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,cAA8B,EAC9B,oBAAuC,EACvC,YAAqD;IAErD,IAAI,aAAgC,CAAC;IACrC,IAAI,CAAC;QACH,aAAa,GAAG,MAAM,cAAc,CAAC,kBAAkB,CAAC,CAAC,IAAI,EAAE,GAAG,oBAAoB,CAAC,CAAC,CAAC;IAC3F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,wBAAwB,EAAE,CAAC;YAC9C,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -1,20 +1,86 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API credential store for persisting and loading API credentials.
|
|
3
|
+
*
|
|
4
|
+
* The store has two top-level sections:
|
|
5
|
+
*
|
|
6
|
+
* - "credentials": complete credentials, stored per service and, within each
|
|
7
|
+
* service, per account. An account is a string that uniquely identifies the
|
|
8
|
+
* account behind the credentials (typically an e-mail, sometimes an opaque
|
|
9
|
+
* id). The empty string denotes the "default" account (when we don't know
|
|
10
|
+
* anything more).
|
|
11
|
+
* - "preparations": prepared / incomplete credentials (e.g. an OAuth client
|
|
12
|
+
* id and secret created by `auth prepare` or `auth browser-prepare`),
|
|
13
|
+
* stored per service only. A service has at most one preparation; storing a
|
|
14
|
+
* new one overwrites the previous one.
|
|
15
|
+
*
|
|
16
|
+
* All credential read/write methods accept an optional account. When it is
|
|
17
|
+
* omitted the account is resolved from the data itself: the single stored
|
|
18
|
+
* account is used, or an {@link AmbiguousAccountError} is raised when several
|
|
19
|
+
* accounts exist and the caller must disambiguate.
|
|
3
20
|
*/
|
|
4
21
|
import type { ApiCredentials } from './base.js';
|
|
5
22
|
import { EncryptedStorage } from '../encryptedStorage.js';
|
|
6
23
|
export declare class ApiCredentialStoreError extends Error {
|
|
7
24
|
constructor(message: string);
|
|
8
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Thrown when a service has more than one account stored and no account was
|
|
28
|
+
* specified to disambiguate between them.
|
|
29
|
+
*/
|
|
30
|
+
export declare class AmbiguousAccountError extends Error {
|
|
31
|
+
readonly serviceName: string;
|
|
32
|
+
readonly accounts: readonly string[];
|
|
33
|
+
constructor(serviceName: string, accounts: readonly string[]);
|
|
34
|
+
}
|
|
9
35
|
export declare class ApiCredentialStore {
|
|
10
36
|
readonly path: string;
|
|
11
37
|
private readonly encryptedStorage;
|
|
12
38
|
constructor(path: string, encryptedStorage: EncryptedStorage);
|
|
13
39
|
private loadStoreData;
|
|
14
40
|
private saveStoreData;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
41
|
+
private parseCredentials;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve which stored account to operate on when the caller did not name
|
|
44
|
+
* one. Returns null when the service has no stored accounts. Throws
|
|
45
|
+
* {@link AmbiguousAccountError} when several accounts exist.
|
|
46
|
+
*/
|
|
47
|
+
private resolveImplicitAccount;
|
|
48
|
+
get(serviceName: string, account?: string): ApiCredentials | null;
|
|
49
|
+
save(serviceName: string, apiCredentials: ApiCredentials, account?: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* Return the prepared (incomplete) credentials stored for a service, or null
|
|
52
|
+
* when the service has no preparation.
|
|
53
|
+
*/
|
|
54
|
+
getPreparation(serviceName: string): ApiCredentials | null;
|
|
55
|
+
/**
|
|
56
|
+
* Store prepared (incomplete) credentials for a service, overwriting any
|
|
57
|
+
* previous preparation for that service.
|
|
58
|
+
*/
|
|
59
|
+
savePreparation(serviceName: string, apiCredentials: ApiCredentials): void;
|
|
60
|
+
/**
|
|
61
|
+
* List the accounts that have credentials stored for a service, in the order
|
|
62
|
+
* they appear in the store. Returns an empty array when the service has no
|
|
63
|
+
* stored credentials.
|
|
64
|
+
*/
|
|
65
|
+
listAccounts(serviceName: string): readonly string[];
|
|
66
|
+
/**
|
|
67
|
+
* Return all stored credentials as a map of service name to a map of account
|
|
68
|
+
* to credentials.
|
|
69
|
+
*/
|
|
70
|
+
getAll(): ReadonlyMap<string, ReadonlyMap<string, ApiCredentials>>;
|
|
71
|
+
/**
|
|
72
|
+
* Delete stored credentials for the given account (resolving an unspecified
|
|
73
|
+
* account the same way reads do). If that leaves the service with no
|
|
74
|
+
* accounts, the service entry is removed entirely. Preparations are never
|
|
75
|
+
* touched; use {@link deleteAll} for that. Returns whether anything was
|
|
76
|
+
* deleted.
|
|
77
|
+
*/
|
|
78
|
+
delete(serviceName: string, account?: string): boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Delete everything stored for a service: the credentials of all its
|
|
81
|
+
* accounts as well as its preparation (if any). Returns whether anything
|
|
82
|
+
* was deleted.
|
|
83
|
+
*/
|
|
84
|
+
deleteAll(serviceName: string): boolean;
|
|
19
85
|
}
|
|
20
86
|
//# sourceMappingURL=store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/apiCredentials/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/apiCredentials/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAMhD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;gBAEzB,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE;CAU7D;AASD,qBAAa,kBAAkB;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmB;gBAExC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,gBAAgB;IAK5D,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,gBAAgB;IAUxB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAU9B,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAejE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAgBjF;;;OAGG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAS1D;;;OAGG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,cAAc,GAAG,IAAI;IAM1E;;;;OAIG;IACH,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE;IAMpD;;;OAGG;IACH,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAalE;;;;;;OAMG;IACH,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO;IAyBtD;;;;OAIG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;CAUxC"}
|
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* API credential store for persisting and loading API credentials.
|
|
3
|
+
*
|
|
4
|
+
* The store has two top-level sections:
|
|
5
|
+
*
|
|
6
|
+
* - "credentials": complete credentials, stored per service and, within each
|
|
7
|
+
* service, per account. An account is a string that uniquely identifies the
|
|
8
|
+
* account behind the credentials (typically an e-mail, sometimes an opaque
|
|
9
|
+
* id). The empty string denotes the "default" account (when we don't know
|
|
10
|
+
* anything more).
|
|
11
|
+
* - "preparations": prepared / incomplete credentials (e.g. an OAuth client
|
|
12
|
+
* id and secret created by `auth prepare` or `auth browser-prepare`),
|
|
13
|
+
* stored per service only. A service has at most one preparation; storing a
|
|
14
|
+
* new one overwrites the previous one.
|
|
15
|
+
*
|
|
16
|
+
* All credential read/write methods accept an optional account. When it is
|
|
17
|
+
* omitted the account is resolved from the data itself: the single stored
|
|
18
|
+
* account is used, or an {@link AmbiguousAccountError} is raised when several
|
|
19
|
+
* accounts exist and the caller must disambiguate.
|
|
3
20
|
*/
|
|
21
|
+
import { DEFAULT_ACCOUNT } from './account.js';
|
|
4
22
|
import { ApiCredentialsSchema, deserializeCredentials, serializeCredentials, } from './serialization.js';
|
|
5
23
|
export class ApiCredentialStoreError extends Error {
|
|
6
24
|
constructor(message) {
|
|
@@ -8,6 +26,22 @@ export class ApiCredentialStoreError extends Error {
|
|
|
8
26
|
this.name = 'ApiCredentialStoreError';
|
|
9
27
|
}
|
|
10
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Thrown when a service has more than one account stored and no account was
|
|
31
|
+
* specified to disambiguate between them.
|
|
32
|
+
*/
|
|
33
|
+
export class AmbiguousAccountError extends Error {
|
|
34
|
+
serviceName;
|
|
35
|
+
accounts;
|
|
36
|
+
constructor(serviceName, accounts) {
|
|
37
|
+
super(`Multiple accounts are stored for service '${serviceName}': ` +
|
|
38
|
+
`${accounts.map((account) => `'${account}'`).join(', ')}. ` +
|
|
39
|
+
'Specify which one to use with --account.');
|
|
40
|
+
this.name = 'AmbiguousAccountError';
|
|
41
|
+
this.serviceName = serviceName;
|
|
42
|
+
this.accounts = accounts;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
11
45
|
export class ApiCredentialStore {
|
|
12
46
|
path;
|
|
13
47
|
encryptedStorage;
|
|
@@ -19,9 +53,13 @@ export class ApiCredentialStore {
|
|
|
19
53
|
try {
|
|
20
54
|
const content = this.encryptedStorage.readFile(this.path);
|
|
21
55
|
if (content === null) {
|
|
22
|
-
return {};
|
|
56
|
+
return { credentials: {}, preparations: {} };
|
|
23
57
|
}
|
|
24
|
-
|
|
58
|
+
const parsed = JSON.parse(content);
|
|
59
|
+
return {
|
|
60
|
+
credentials: parsed.credentials ?? {},
|
|
61
|
+
preparations: parsed.preparations ?? {},
|
|
62
|
+
};
|
|
25
63
|
}
|
|
26
64
|
catch (error) {
|
|
27
65
|
throw new ApiCredentialStoreError(`Failed to read credential store: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -35,42 +73,146 @@ export class ApiCredentialStore {
|
|
|
35
73
|
throw new ApiCredentialStoreError(`Failed to write credential store: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
74
|
}
|
|
37
75
|
}
|
|
38
|
-
|
|
39
|
-
const data = this.loadStoreData();
|
|
40
|
-
const credentialData = data[serviceName];
|
|
41
|
-
if (credentialData === undefined) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
76
|
+
parseCredentials(serviceName, credentialData) {
|
|
44
77
|
const parseResult = ApiCredentialsSchema.safeParse(credentialData);
|
|
45
78
|
if (!parseResult.success) {
|
|
46
79
|
throw new ApiCredentialStoreError(`Invalid credential data for service ${serviceName}: ${parseResult.error.message}`);
|
|
47
80
|
}
|
|
48
81
|
return deserializeCredentials(parseResult.data);
|
|
49
82
|
}
|
|
50
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Resolve which stored account to operate on when the caller did not name
|
|
85
|
+
* one. Returns null when the service has no stored accounts. Throws
|
|
86
|
+
* {@link AmbiguousAccountError} when several accounts exist.
|
|
87
|
+
*/
|
|
88
|
+
resolveImplicitAccount(serviceName, accounts) {
|
|
89
|
+
if (accounts.length === 0) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (accounts.length === 1) {
|
|
93
|
+
return accounts[0];
|
|
94
|
+
}
|
|
95
|
+
throw new AmbiguousAccountError(serviceName, accounts);
|
|
96
|
+
}
|
|
97
|
+
get(serviceName, account) {
|
|
98
|
+
const data = this.loadStoreData();
|
|
99
|
+
const serviceData = data.credentials[serviceName];
|
|
100
|
+
const accounts = serviceData === undefined ? [] : Object.keys(serviceData);
|
|
101
|
+
const resolvedAccount = account ?? this.resolveImplicitAccount(serviceName, accounts);
|
|
102
|
+
if (resolvedAccount === null) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const credentialData = serviceData?.[resolvedAccount];
|
|
106
|
+
if (credentialData === undefined) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return this.parseCredentials(serviceName, credentialData);
|
|
110
|
+
}
|
|
111
|
+
save(serviceName, apiCredentials, account) {
|
|
51
112
|
const data = this.loadStoreData();
|
|
52
|
-
data[serviceName]
|
|
113
|
+
const serviceData = data.credentials[serviceName] ?? {};
|
|
114
|
+
const accounts = Object.keys(serviceData);
|
|
115
|
+
// A brand-new service with no account specified defaults to the default
|
|
116
|
+
// account; otherwise an unspecified account updates the single existing one
|
|
117
|
+
// (and raises for ambiguity), keeping writes consistent with reads.
|
|
118
|
+
const resolvedAccount = account ?? this.resolveImplicitAccount(serviceName, accounts) ?? DEFAULT_ACCOUNT;
|
|
119
|
+
data.credentials[serviceName] = {
|
|
120
|
+
...serviceData,
|
|
121
|
+
[resolvedAccount]: serializeCredentials(apiCredentials),
|
|
122
|
+
};
|
|
53
123
|
this.saveStoreData(data);
|
|
54
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Return the prepared (incomplete) credentials stored for a service, or null
|
|
127
|
+
* when the service has no preparation.
|
|
128
|
+
*/
|
|
129
|
+
getPreparation(serviceName) {
|
|
130
|
+
const data = this.loadStoreData();
|
|
131
|
+
const preparationData = data.preparations[serviceName];
|
|
132
|
+
if (preparationData === undefined) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return this.parseCredentials(serviceName, preparationData);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Store prepared (incomplete) credentials for a service, overwriting any
|
|
139
|
+
* previous preparation for that service.
|
|
140
|
+
*/
|
|
141
|
+
savePreparation(serviceName, apiCredentials) {
|
|
142
|
+
const data = this.loadStoreData();
|
|
143
|
+
data.preparations[serviceName] = serializeCredentials(apiCredentials);
|
|
144
|
+
this.saveStoreData(data);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* List the accounts that have credentials stored for a service, in the order
|
|
148
|
+
* they appear in the store. Returns an empty array when the service has no
|
|
149
|
+
* stored credentials.
|
|
150
|
+
*/
|
|
151
|
+
listAccounts(serviceName) {
|
|
152
|
+
const data = this.loadStoreData();
|
|
153
|
+
const serviceData = data.credentials[serviceName];
|
|
154
|
+
return serviceData === undefined ? [] : Object.keys(serviceData);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Return all stored credentials as a map of service name to a map of account
|
|
158
|
+
* to credentials.
|
|
159
|
+
*/
|
|
55
160
|
getAll() {
|
|
56
161
|
const data = this.loadStoreData();
|
|
57
162
|
const result = new Map();
|
|
58
|
-
for (const [serviceName,
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
163
|
+
for (const [serviceName, serviceData] of Object.entries(data.credentials)) {
|
|
164
|
+
const accountMap = new Map();
|
|
165
|
+
for (const [account, credentialData] of Object.entries(serviceData)) {
|
|
166
|
+
accountMap.set(account, this.parseCredentials(serviceName, credentialData));
|
|
62
167
|
}
|
|
63
|
-
result.set(serviceName,
|
|
168
|
+
result.set(serviceName, accountMap);
|
|
64
169
|
}
|
|
65
170
|
return result;
|
|
66
171
|
}
|
|
67
|
-
|
|
172
|
+
/**
|
|
173
|
+
* Delete stored credentials for the given account (resolving an unspecified
|
|
174
|
+
* account the same way reads do). If that leaves the service with no
|
|
175
|
+
* accounts, the service entry is removed entirely. Preparations are never
|
|
176
|
+
* touched; use {@link deleteAll} for that. Returns whether anything was
|
|
177
|
+
* deleted.
|
|
178
|
+
*/
|
|
179
|
+
delete(serviceName, account) {
|
|
180
|
+
const data = this.loadStoreData();
|
|
181
|
+
const serviceData = data.credentials[serviceName];
|
|
182
|
+
if (serviceData === undefined) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
const accounts = Object.keys(serviceData);
|
|
186
|
+
const resolvedAccount = account ?? this.resolveImplicitAccount(serviceName, accounts);
|
|
187
|
+
if (resolvedAccount === null || !(resolvedAccount in serviceData)) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const { [resolvedAccount]: _, ...remainingAccounts } = serviceData;
|
|
191
|
+
if (Object.keys(remainingAccounts).length === 0) {
|
|
192
|
+
const { [serviceName]: __, ...remainingServices } = data.credentials;
|
|
193
|
+
this.saveStoreData({ ...data, credentials: remainingServices });
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
this.saveStoreData({
|
|
197
|
+
...data,
|
|
198
|
+
credentials: { ...data.credentials, [serviceName]: remainingAccounts },
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Delete everything stored for a service: the credentials of all its
|
|
205
|
+
* accounts as well as its preparation (if any). Returns whether anything
|
|
206
|
+
* was deleted.
|
|
207
|
+
*/
|
|
208
|
+
deleteAll(serviceName) {
|
|
68
209
|
const data = this.loadStoreData();
|
|
69
|
-
if (!(serviceName in data)) {
|
|
210
|
+
if (!(serviceName in data.credentials) && !(serviceName in data.preparations)) {
|
|
70
211
|
return false;
|
|
71
212
|
}
|
|
72
|
-
const { [serviceName]: _, ...
|
|
73
|
-
|
|
213
|
+
const { [serviceName]: _, ...remainingServices } = data.credentials;
|
|
214
|
+
const { [serviceName]: __, ...remainingPreparations } = data.preparations;
|
|
215
|
+
this.saveStoreData({ credentials: remainingServices, preparations: remainingPreparations });
|
|
74
216
|
return true;
|
|
75
217
|
}
|
|
76
218
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/apiCredentials/store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/apiCredentials/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -12,6 +12,6 @@ import type { Service } from '../services/core/base.js';
|
|
|
12
12
|
* used when the credentials are shared with another machine and refreshing here
|
|
13
13
|
* would otherwise risk exhausting the refresh token.
|
|
14
14
|
*/
|
|
15
|
-
export declare function maybeRefreshCredentials(service: Service, apiCredentials: ApiCredentials, apiCredentialStore: ApiCredentialStore, disableRefresh?: boolean): Promise<ApiCredentials>;
|
|
16
|
-
export declare function getCredentialStatus(service: Service, credentials: ApiCredentials | null, apiCredentialStore: ApiCredentialStore, disableRefresh?: boolean, offline?: boolean): Promise<ApiCredentialStatus>;
|
|
15
|
+
export declare function maybeRefreshCredentials(service: Service, apiCredentials: ApiCredentials, apiCredentialStore: ApiCredentialStore, disableRefresh?: boolean, account?: string): Promise<ApiCredentials>;
|
|
16
|
+
export declare function getCredentialStatus(service: Service, credentials: ApiCredentials | null, apiCredentialStore: ApiCredentialStore, disableRefresh?: boolean, offline?: boolean, account?: string): Promise<ApiCredentialStatus>;
|
|
17
17
|
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/apiCredentials/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,UAAQ,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/apiCredentials/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,cAAc,EAC9B,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,UAAQ,EACtB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,CAAC,CAUzB;AAED,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,cAAc,GAAG,IAAI,EAClC,kBAAkB,EAAE,kBAAkB,EACtC,cAAc,UAAQ,EACtB,OAAO,UAAQ,EACf,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,mBAAmB,CAAC,CAiB9B"}
|
|
@@ -10,18 +10,18 @@ import { ApiCredentialStatus } from './base.js';
|
|
|
10
10
|
* used when the credentials are shared with another machine and refreshing here
|
|
11
11
|
* would otherwise risk exhausting the refresh token.
|
|
12
12
|
*/
|
|
13
|
-
export async function maybeRefreshCredentials(service, apiCredentials, apiCredentialStore, disableRefresh = false) {
|
|
13
|
+
export async function maybeRefreshCredentials(service, apiCredentials, apiCredentialStore, disableRefresh = false, account) {
|
|
14
14
|
if (disableRefresh || apiCredentials.isExpired() !== true || !service.refreshCredentials) {
|
|
15
15
|
return apiCredentials;
|
|
16
16
|
}
|
|
17
17
|
const refreshedCredentials = await service.refreshCredentials(apiCredentials);
|
|
18
18
|
if (refreshedCredentials !== null) {
|
|
19
|
-
apiCredentialStore.save(service.name, refreshedCredentials);
|
|
19
|
+
apiCredentialStore.save(service.name, refreshedCredentials, account);
|
|
20
20
|
return refreshedCredentials;
|
|
21
21
|
}
|
|
22
22
|
return apiCredentials;
|
|
23
23
|
}
|
|
24
|
-
export async function getCredentialStatus(service, credentials, apiCredentialStore, disableRefresh = false, offline = false) {
|
|
24
|
+
export async function getCredentialStatus(service, credentials, apiCredentialStore, disableRefresh = false, offline = false, account) {
|
|
25
25
|
if (credentials === null) {
|
|
26
26
|
return ApiCredentialStatus.Missing;
|
|
27
27
|
}
|
|
@@ -30,7 +30,7 @@ export async function getCredentialStatus(service, credentials, apiCredentialSto
|
|
|
30
30
|
if (offline) {
|
|
31
31
|
return ApiCredentialStatus.Unknown;
|
|
32
32
|
}
|
|
33
|
-
const refreshed = await maybeRefreshCredentials(service, credentials, apiCredentialStore, disableRefresh);
|
|
34
|
-
return
|
|
33
|
+
const refreshed = await maybeRefreshCredentials(service, credentials, apiCredentialStore, disableRefresh, account);
|
|
34
|
+
return service.checkApiCredentials(refreshed);
|
|
35
35
|
}
|
|
36
36
|
//# sourceMappingURL=utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/apiCredentials/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAuB,MAAM,WAAW,CAAC;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAgB,EAChB,cAA8B,EAC9B,kBAAsC,EACtC,cAAc,GAAG,KAAK;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/apiCredentials/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,mBAAmB,EAAuB,MAAM,WAAW,CAAC;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAgB,EAChB,cAA8B,EAC9B,kBAAsC,EACtC,cAAc,GAAG,KAAK,EACtB,OAAgB;IAEhB,IAAI,cAAc,IAAI,cAAc,CAAC,SAAS,EAAE,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;QACzF,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,MAAM,oBAAoB,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAAC,cAAc,CAAC,CAAC;IAC9E,IAAI,oBAAoB,KAAK,IAAI,EAAE,CAAC;QAClC,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,CAAC,CAAC;QACrE,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAgB,EAChB,WAAkC,EAClC,kBAAsC,EACtC,cAAc,GAAG,KAAK,EACtB,OAAO,GAAG,KAAK,EACf,OAAgB;IAEhB,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,OAAO,mBAAmB,CAAC,OAAO,CAAC;IACrC,CAAC;IACD,4EAA4E;IAC5E,0EAA0E;IAC1E,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,mBAAmB,CAAC,OAAO,CAAC;IACrC,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAC7C,OAAO,EACP,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,OAAO,CACR,CAAC;IACF,OAAO,OAAO,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC;AAChD,CAAC"}
|
package/dist/src/cli.js
CHANGED
|
@@ -43,7 +43,7 @@ if (!gatewayMode) {
|
|
|
43
43
|
accountName: deps.config.accountName,
|
|
44
44
|
allowKeyGeneration: !hasEncryptedData,
|
|
45
45
|
});
|
|
46
|
-
runMigrations(deps.config, new EncryptedStorage(encryptionKey));
|
|
46
|
+
await runMigrations(deps.config, new EncryptedStorage(encryptionKey));
|
|
47
47
|
}
|
|
48
48
|
catch (error) {
|
|
49
49
|
if (error instanceof KeychainTimeoutError) {
|