@doist/cli-core 0.16.1 → 0.17.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/CHANGELOG.md +6 -0
- package/README.md +22 -14
- package/dist/auth/index.d.ts +3 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keyring/record-write.d.ts +57 -15
- package/dist/auth/keyring/record-write.d.ts.map +1 -1
- package/dist/auth/keyring/record-write.js +121 -30
- package/dist/auth/keyring/record-write.js.map +1 -1
- package/dist/auth/keyring/slot-naming.d.ts +6 -0
- package/dist/auth/keyring/slot-naming.d.ts.map +1 -0
- package/dist/auth/keyring/slot-naming.js +8 -0
- package/dist/auth/keyring/slot-naming.js.map +1 -0
- package/dist/auth/keyring/token-store.d.ts +10 -2
- package/dist/auth/keyring/token-store.d.ts.map +1 -1
- package/dist/auth/keyring/token-store.js +89 -50
- package/dist/auth/keyring/token-store.js.map +1 -1
- package/dist/auth/keyring/types.d.ts +20 -0
- package/dist/auth/keyring/types.d.ts.map +1 -1
- package/dist/auth/persist.d.ts +23 -0
- package/dist/auth/persist.d.ts.map +1 -0
- package/dist/auth/persist.js +38 -0
- package/dist/auth/persist.js.map +1 -0
- package/dist/auth/types.d.ts +27 -1
- package/dist/auth/types.d.ts.map +1 -1
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.17.0](https://github.com/Doist/cli-core/compare/v0.16.1...v0.17.0) (2026-05-19)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **auth:** add TokenBundle storage contract to TokenStore ([#37](https://github.com/Doist/cli-core/issues/37)) ([6513aaa](https://github.com/Doist/cli-core/commit/6513aaa8b6222eb421239f6e41ec63b12cf946e9))
|
|
6
|
+
|
|
1
7
|
## [0.16.1](https://github.com/Doist/cli-core/compare/v0.16.0...v0.16.1) (2026-05-16)
|
|
2
8
|
|
|
3
9
|
### Bug Fixes
|
package/README.md
CHANGED
|
@@ -12,20 +12,20 @@ npm install @doist/cli-core
|
|
|
12
12
|
|
|
13
13
|
## What's in it
|
|
14
14
|
|
|
15
|
-
| Module | Key exports
|
|
16
|
-
| -------------------- |
|
|
17
|
-
| `auth` (subpath) | `attachLoginCommand`, `attachLogoutCommand`, `attachStatusCommand`, `attachTokenViewCommand`, `runOAuthFlow`, `createPkceProvider`, `createSecureStore`, `createKeyringTokenStore`, `migrateLegacyAuth`, PKCE helpers, `AuthProvider` / `TokenStore` / `AccountRef` / `SecureStore` / `UserRecordStore` types, `AttachLogoutRevokeContext` | OAuth runtime plus the Commander attachers for `<cli> [auth] login` / `logout` / `status` / `token`. `attachLogoutCommand` accepts an optional `revokeToken` hook for best-effort server-side token revocation. Ships the standard public-client PKCE flow (`createPkceProvider`), a thin cross-platform OS-keyring wrapper (`createSecureStore`), and a multi-account keyring-backed `TokenStore` (`createKeyringTokenStore`) that stores secrets in the OS credential manager and degrades to plaintext in the consumer's config when the keyring is unavailable (WSL/headless Linux/containers). `AuthProvider` and `TokenStore` remain the escape hatches for DCR or fully bespoke backends. `logout` / `status` / `token` always attach `--user <ref>` and thread the parsed ref to `store.active(ref)` (and `store.clear(ref)` on `logout`). `commander` (when using the attachers), `open` (browser launch), and `@napi-rs/keyring` (when using `createSecureStore` or the keyring `TokenStore`) are optional peer/optional deps. |
|
|
18
|
-
| `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers)
|
|
19
|
-
| `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel`
|
|
20
|
-
| `empty` | `printEmpty`
|
|
21
|
-
| `errors` | `CliError`
|
|
22
|
-
| `global-args` | `parseGlobalArgs`, `stripUserFlag`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled`
|
|
23
|
-
| `json` | `formatJson`, `formatNdjson`
|
|
24
|
-
| `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions`
|
|
25
|
-
| `options` | `ViewOptions`
|
|
26
|
-
| `spinner` | `createSpinner`
|
|
27
|
-
| `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY`
|
|
28
|
-
| `testing` (subpath) | `describeEmptyMachineOutput`
|
|
15
|
+
| Module | Key exports | Purpose |
|
|
16
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `auth` (subpath) | `attachLoginCommand`, `attachLogoutCommand`, `attachStatusCommand`, `attachTokenViewCommand`, `runOAuthFlow`, `createPkceProvider`, `createSecureStore`, `createKeyringTokenStore`, `migrateLegacyAuth`, `persistBundle`, PKCE helpers, `AuthProvider` / `TokenStore` / `TokenBundle` / `RefreshInput` / `AccountRef` / `SecureStore` / `UserRecordStore` types, `AttachLogoutRevokeContext` | OAuth runtime plus the Commander attachers for `<cli> [auth] login` / `logout` / `status` / `token`. `attachLogoutCommand` accepts an optional `revokeToken` hook for best-effort server-side token revocation. Ships the standard public-client PKCE flow (`createPkceProvider`), a thin cross-platform OS-keyring wrapper (`createSecureStore`), and a multi-account keyring-backed `TokenStore` (`createKeyringTokenStore`) that stores secrets in the OS credential manager and degrades to plaintext in the consumer's config when the keyring is unavailable (WSL/headless Linux/containers). The store contract supports an optional `setBundle(account, bundle)` write method (required on `KeyringTokenStore`) so consumers that need refresh-token persistence can opt in via `TokenBundle`; `active()` stays narrow (access token + account only) so callers that don't need refresh state don't pay extra keyring IPC. `AuthProvider` and `TokenStore` remain the escape hatches for DCR or fully bespoke backends. `logout` / `status` / `token` always attach `--user <ref>` and thread the parsed ref to `store.active(ref)` (and `store.clear(ref)` on `logout`). `commander` (when using the attachers), `open` (browser launch), and `@napi-rs/keyring` (when using `createSecureStore` or the keyring `TokenStore`) are optional peer/optional deps. |
|
|
18
|
+
| `commands` (subpath) | `registerChangelogCommand`, `registerUpdateCommand` (+ semver helpers) | Commander wiring for cli-core's standard commands (e.g. `<cli> changelog`, `<cli> update`, `<cli> update switch`). **Requires** `commander` as an optional peer-dep. |
|
|
19
|
+
| `config` | `getConfigPath`, `readConfig`, `readConfigStrict`, `writeConfig`, `updateConfig`, `CoreConfig`, `UpdateChannel` | Read / write a per-CLI JSON config file with typed error codes; `CoreConfig` is the shape of fields cli-core itself owns (extend it for per-CLI fields). |
|
|
20
|
+
| `empty` | `printEmpty` | Print an empty-state message gated on `--json` / `--ndjson` so machine consumers never see human strings on stdout. |
|
|
21
|
+
| `errors` | `CliError` | Typed CLI error class with `code` and exit-code mapping. |
|
|
22
|
+
| `global-args` | `parseGlobalArgs`, `stripUserFlag`, `createGlobalArgsStore`, `createAccessibleGate`, `createSpinnerGate`, `getProgressJsonlPath`, `isProgressJsonlEnabled` | Parse well-known global flags (`--json`, `--ndjson`, `--quiet`, `--verbose`, `--accessible`, `--no-spinner`, `--progress-jsonl`, `--user <ref>`) and derive predicates from them. `stripUserFlag` removes `--user` tokens from argv so the cleaned array can be forwarded to Commander when the flag has no root-program attachment. |
|
|
23
|
+
| `json` | `formatJson`, `formatNdjson` | Stable JSON / newline-delimited JSON formatting for stdout. |
|
|
24
|
+
| `markdown` (subpath) | `preloadMarkdown`, `renderMarkdown`, `TerminalRendererOptions` | Lazy-init terminal markdown renderer. **Requires** `marked` and `marked-terminal-renderer` as peer-deps — install only if your CLI uses this subpath. |
|
|
25
|
+
| `options` | `ViewOptions` | Type contract for `{ json?, ndjson? }` per-command options that machine-output gates derive from. |
|
|
26
|
+
| `spinner` | `createSpinner` | Loading spinner factory wrapping `yocto-spinner` with disable gates. |
|
|
27
|
+
| `terminal` | `isCI`, `isStderrTTY`, `isStdinTTY`, `isStdoutTTY` | TTY / CI detection helpers. |
|
|
28
|
+
| `testing` (subpath) | `describeEmptyMachineOutput` | Vitest helpers reusable by consuming CLIs (e.g. parametrised empty-state suite covering `--json` / `--ndjson` / human modes). |
|
|
29
29
|
|
|
30
30
|
## Usage
|
|
31
31
|
|
|
@@ -288,6 +288,14 @@ export const tokenStore: TokenStore<Account> = {
|
|
|
288
288
|
|
|
289
289
|
For multi-account storage (OS keychain, per-user config slots, …), implement the same five methods against your backend and honour `ref` on `active` / `clear` / `setDefault`. `AccountRef` is an opaque `string` — cli-core does not constrain matching semantics (id-exact, email-case-insensitive, label, …). The store impl owns that.
|
|
290
290
|
|
|
291
|
+
##### Refresh-token storage (`TokenBundle`)
|
|
292
|
+
|
|
293
|
+
Stores that target servers issuing refresh tokens may implement the optional `setBundle(account, bundle, options?)` method. `TokenBundle` carries `{ accessToken, refreshToken?, accessTokenExpiresAt?, refreshTokenExpiresAt? }`. Stores that omit `setBundle` continue to work — cli-core helpers (`persistBundle`) fall back to `set(account, bundle.accessToken)` and silently drop refresh state. `KeyringTokenStore` implements `setBundle` as a required override and routes the refresh token to a sibling keyring slot.
|
|
294
|
+
|
|
295
|
+
`active()` still returns the narrow `{ token, account }` snapshot — refresh-state material is stored but not surfaced on the hot read path so commands that only need the access token don't pay an extra keyring IPC. A bundle-aware read path lands alongside the silent-refresh helper in a follow-up PR.
|
|
296
|
+
|
|
297
|
+
The `persistBundle({ store, account, bundle, promoteDefault? })` helper is the recommended write path for bundle-capable consumers — it prefers `setBundle` when available and falls back to `set` otherwise (the `set` fallback can't honour `promoteDefault`, so multi-account stores wanting silent-refresh-safe selection must implement `setBundle`). cli-core's own `runOAuthFlow` still persists via `store.set()` today; it switches to `persistBundle` when the refresh helper lands.
|
|
298
|
+
|
|
291
299
|
#### Keyring primitive (`createSecureStore`)
|
|
292
300
|
|
|
293
301
|
When the OS credential manager is the right place for your token, `createSecureStore` is a thin cross-platform wrapper around `@napi-rs/keyring`. It exposes a three-method handle (`getSecret` / `setSecret` / `deleteSecret`) for one slot identified by `serviceName` + `account`:
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -11,9 +11,11 @@ export { attachTokenViewCommand } from './token-view.js';
|
|
|
11
11
|
export type { AttachTokenViewCommandOptions } from './token-view.js';
|
|
12
12
|
export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
|
|
13
13
|
export type { GenerateVerifierOptions } from './pkce.js';
|
|
14
|
+
export { persistBundle } from './persist.js';
|
|
15
|
+
export type { PersistBundleOptions } from './persist.js';
|
|
14
16
|
export { createPkceProvider } from './providers/pkce.js';
|
|
15
17
|
export type { PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
|
|
16
|
-
export type { AccountRef, AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, TokenStore, ValidateInput, } from './types.js';
|
|
18
|
+
export type { AccountRef, AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, RefreshInput, TokenBundle, TokenStore, ValidateInput, } from './types.js';
|
|
17
19
|
export { SecureStoreUnavailableError, createKeyringTokenStore, createSecureStore, migrateLegacyAuth, } from './keyring/index.js';
|
|
18
20
|
export type { CreateKeyringTokenStoreOptions, CreateSecureStoreOptions, KeyringTokenStore, MigrateAuthResult, MigrateLegacyAuthOptions, MigrateSkipReason, SecureStore, TokenStorageLocation, TokenStorageResult, UserRecord, UserRecordStore, } from './keyring/index.js';
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/auth/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,YAAY,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EACR,0BAA0B,EAC1B,mBAAmB,EACnB,yBAAyB,GAC5B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,YAAY,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EACR,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,UAAU,EACV,aAAa,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACR,8BAA8B,EAC9B,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,eAAe,GAClB,MAAM,oBAAoB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AACxE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/C,YAAY,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAC/E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EACR,0BAA0B,EAC1B,mBAAmB,EACnB,yBAAyB,GAC5B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AACjD,YAAY,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAClF,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AACxD,YAAY,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAClB,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAC5C,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9E,YAAY,EACR,UAAU,EACV,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,WAAW,EACX,UAAU,EACV,aAAa,GAChB,MAAM,YAAY,CAAA;AACnB,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACR,8BAA8B,EAC9B,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,wBAAwB,EACxB,iBAAiB,EACjB,WAAW,EACX,oBAAoB,EACpB,kBAAkB,EAClB,UAAU,EACV,eAAe,GAClB,MAAM,oBAAoB,CAAA"}
|
package/dist/auth/index.js
CHANGED
|
@@ -4,6 +4,7 @@ export { attachLogoutCommand } from './logout.js';
|
|
|
4
4
|
export { attachStatusCommand } from './status.js';
|
|
5
5
|
export { attachTokenViewCommand } from './token-view.js';
|
|
6
6
|
export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVerifier, } from './pkce.js';
|
|
7
|
+
export { persistBundle } from './persist.js';
|
|
7
8
|
export { createPkceProvider } from './providers/pkce.js';
|
|
8
9
|
export { SecureStoreUnavailableError, createKeyringTokenStore, createSecureStore, migrateLegacyAuth, } from './keyring/index.js';
|
|
9
10
|
//# sourceMappingURL=index.js.map
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAMjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAExD,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAMjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAExD,OAAO,EACH,yBAAyB,EACzB,eAAe,EACf,aAAa,EACb,gBAAgB,GACnB,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAE5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAiBxD,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA"}
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import type { AuthAccount } from '../types.js';
|
|
1
|
+
import type { AuthAccount, TokenBundle } from '../types.js';
|
|
2
2
|
import { type SecureStore } from './secure-store.js';
|
|
3
3
|
import type { UserRecordStore } from './types.js';
|
|
4
4
|
type WriteRecordOptions<TAccount extends AuthAccount> = {
|
|
5
5
|
/** Per-account keyring slot, already configured by the caller (e.g. via `createSecureStore`). */
|
|
6
6
|
secureStore: SecureStore;
|
|
7
|
+
/**
|
|
8
|
+
* Optional refresh-token keyring slot. When supplied, any orphan refresh
|
|
9
|
+
* material from a prior `setBundle` is wiped best-effort AFTER the user
|
|
10
|
+
* record is upserted (see the deferred-cleanup contract on
|
|
11
|
+
* `writeBundleWithKeyringFallback`).
|
|
12
|
+
*/
|
|
13
|
+
refreshStore?: SecureStore;
|
|
7
14
|
userRecords: UserRecordStore<TAccount>;
|
|
8
15
|
account: TAccount;
|
|
9
16
|
token: string;
|
|
@@ -12,23 +19,58 @@ type WriteRecordResult = {
|
|
|
12
19
|
/** `true` when the secret landed in the OS keyring; `false` when the keyring was unavailable and the token was written to `fallbackToken` on the user record. */
|
|
13
20
|
storedSecurely: boolean;
|
|
14
21
|
};
|
|
22
|
+
type WriteBundleOptions<TAccount extends AuthAccount> = {
|
|
23
|
+
/** Per-account access-token keyring slot. */
|
|
24
|
+
accessStore: SecureStore;
|
|
25
|
+
/** Per-account refresh-token keyring slot. */
|
|
26
|
+
refreshStore: SecureStore;
|
|
27
|
+
userRecords: UserRecordStore<TAccount>;
|
|
28
|
+
account: TAccount;
|
|
29
|
+
bundle: TokenBundle;
|
|
30
|
+
};
|
|
31
|
+
type WriteBundleResult = {
|
|
32
|
+
/** `true` when the access token landed in the OS keyring; `false` when it fell back to `fallbackToken`. */
|
|
33
|
+
accessStoredSecurely: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* `true` when a refresh token landed in the OS keyring. `false` when it
|
|
36
|
+
* fell back to `fallbackRefreshToken`. `undefined` when the bundle
|
|
37
|
+
* carried no refresh token (nothing to store).
|
|
38
|
+
*/
|
|
39
|
+
refreshStoredSecurely: boolean | undefined;
|
|
40
|
+
};
|
|
15
41
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* 1. Keyring `setSecret` first. On `SecureStoreUnavailableError`, swallow
|
|
21
|
-
* the failure and record a `fallbackToken` on the user record instead.
|
|
22
|
-
* Any other error rethrows.
|
|
23
|
-
* 2. `userRecords.upsert(record)`. On failure, best-effort rollback the
|
|
24
|
-
* keyring write so we don't leave an orphan credential for an account
|
|
25
|
-
* cli-core never managed to register. Original error rethrows.
|
|
42
|
+
* Single-token write. Thin wrapper over `writeBundleWithKeyringFallback`
|
|
43
|
+
* passing a refresh-less bundle, so trim/validate, access-slot fallback,
|
|
44
|
+
* upsert rollback, and the deferred refresh-slot wipe all share one
|
|
45
|
+
* implementation.
|
|
26
46
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* dirty up a successful credential write.
|
|
47
|
+
* `refreshStore` is optional purely for legacy callers (`migrateLegacyAuth`)
|
|
48
|
+
* that don't have one wired; the migrate path never had refresh state so
|
|
49
|
+
* skipping the wipe is correct there.
|
|
31
50
|
*/
|
|
32
51
|
export declare function writeRecordWithKeyringFallback<TAccount extends AuthAccount>(options: WriteRecordOptions<TAccount>): Promise<WriteRecordResult>;
|
|
52
|
+
/**
|
|
53
|
+
* Two-slot write. Order: access slot → refresh slot → upsert → deferred
|
|
54
|
+
* refresh wipe.
|
|
55
|
+
*
|
|
56
|
+
* 1. Validate `bundle.accessToken` (non-empty after trim).
|
|
57
|
+
* 2. `accessStore.setSecret`. `SecureStoreUnavailableError` degrades to
|
|
58
|
+
* `fallbackToken` on the record; any other error rethrows.
|
|
59
|
+
* 3. `refreshStore.setSecret` when `bundle.refreshToken` is present.
|
|
60
|
+
* `SecureStoreUnavailableError` degrades to `fallbackRefreshToken`. A
|
|
61
|
+
* non-keyring failure rolls back the access slot before rethrowing
|
|
62
|
+
* (no partial credentials left behind for an unregistered user).
|
|
63
|
+
* 4. `userRecords.upsert(record)`. On failure, best-effort
|
|
64
|
+
* `Promise.allSettled` rollback of any slot writes that succeeded.
|
|
65
|
+
* 5. Only after a successful upsert: if the bundle has no refresh token,
|
|
66
|
+
* wipe any orphan slot from a prior `setBundle` (best-effort). Doing
|
|
67
|
+
* this BEFORE the upsert would lose refresh state if the upsert then
|
|
68
|
+
* rejected — the new record's `hasRefreshToken` would still claim
|
|
69
|
+
* false but the old slot would be gone with no rollback path.
|
|
70
|
+
*
|
|
71
|
+
* Default promotion is external — preference, not correctness, and an
|
|
72
|
+
* error there must not dirty up a successful credential write.
|
|
73
|
+
*/
|
|
74
|
+
export declare function writeBundleWithKeyringFallback<TAccount extends AuthAccount>(options: WriteBundleOptions<TAccount>): Promise<WriteBundleResult>;
|
|
33
75
|
export {};
|
|
34
76
|
//# sourceMappingURL=record-write.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-write.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/record-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"record-write.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/record-write.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC3D,OAAO,EAAE,KAAK,WAAW,EAA+B,MAAM,mBAAmB,CAAA;AACjF,OAAO,KAAK,EAAc,eAAe,EAAE,MAAM,YAAY,CAAA;AAE7D,KAAK,kBAAkB,CAAC,QAAQ,SAAS,WAAW,IAAI;IACpD,iGAAiG;IACjG,WAAW,EAAE,WAAW,CAAA;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,WAAW,CAAA;IAC1B,WAAW,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACtC,OAAO,EAAE,QAAQ,CAAA;IACjB,KAAK,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,iBAAiB,GAAG;IACrB,iKAAiK;IACjK,cAAc,EAAE,OAAO,CAAA;CAC1B,CAAA;AAED,KAAK,kBAAkB,CAAC,QAAQ,SAAS,WAAW,IAAI;IACpD,6CAA6C;IAC7C,WAAW,EAAE,WAAW,CAAA;IACxB,8CAA8C;IAC9C,YAAY,EAAE,WAAW,CAAA;IACzB,WAAW,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACtC,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;CACtB,CAAA;AAED,KAAK,iBAAiB,GAAG;IACrB,2GAA2G;IAC3G,oBAAoB,EAAE,OAAO,CAAA;IAC7B;;;;OAIG;IACH,qBAAqB,EAAE,OAAO,GAAG,SAAS,CAAA;CAC7C,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAsB,8BAA8B,CAAC,QAAQ,SAAS,WAAW,EAC7E,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAe5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,8BAA8B,CAAC,QAAQ,SAAS,WAAW,EAC7E,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GACtC,OAAO,CAAC,iBAAiB,CAAC,CAgF5B"}
|
|
@@ -1,50 +1,141 @@
|
|
|
1
|
+
import { CliError } from '../../errors.js';
|
|
1
2
|
import { SecureStoreUnavailableError } from './secure-store.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Single-token write. Thin wrapper over `writeBundleWithKeyringFallback`
|
|
5
|
+
* passing a refresh-less bundle, so trim/validate, access-slot fallback,
|
|
6
|
+
* upsert rollback, and the deferred refresh-slot wipe all share one
|
|
7
|
+
* implementation.
|
|
6
8
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* 2. `userRecords.upsert(record)`. On failure, best-effort rollback the
|
|
11
|
-
* keyring write so we don't leave an orphan credential for an account
|
|
12
|
-
* cli-core never managed to register. Original error rethrows.
|
|
13
|
-
*
|
|
14
|
-
* Default promotion (`setDefaultId`) is intentionally **not** in here — both
|
|
15
|
-
* call sites do it best-effort outside the critical section because it is a
|
|
16
|
-
* preference, not a correctness requirement, and an error there must not
|
|
17
|
-
* dirty up a successful credential write.
|
|
9
|
+
* `refreshStore` is optional purely for legacy callers (`migrateLegacyAuth`)
|
|
10
|
+
* that don't have one wired; the migrate path never had refresh state so
|
|
11
|
+
* skipping the wipe is correct there.
|
|
18
12
|
*/
|
|
19
13
|
export async function writeRecordWithKeyringFallback(options) {
|
|
20
|
-
const { secureStore, userRecords, account, token } = options;
|
|
21
|
-
const
|
|
22
|
-
|
|
14
|
+
const { secureStore, refreshStore, userRecords, account, token } = options;
|
|
15
|
+
const { accessStoredSecurely } = await writeBundleWithKeyringFallback({
|
|
16
|
+
accessStore: secureStore,
|
|
17
|
+
// No-op store when the caller didn't wire one — the deferred wipe
|
|
18
|
+
// becomes inert and we don't accidentally create a refresh slot
|
|
19
|
+
// for legacy/migrate paths.
|
|
20
|
+
refreshStore: refreshStore ?? NOOP_SECURE_STORE,
|
|
21
|
+
userRecords,
|
|
22
|
+
account,
|
|
23
|
+
bundle: { accessToken: token },
|
|
24
|
+
});
|
|
25
|
+
return { storedSecurely: accessStoredSecurely };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Two-slot write. Order: access slot → refresh slot → upsert → deferred
|
|
29
|
+
* refresh wipe.
|
|
30
|
+
*
|
|
31
|
+
* 1. Validate `bundle.accessToken` (non-empty after trim).
|
|
32
|
+
* 2. `accessStore.setSecret`. `SecureStoreUnavailableError` degrades to
|
|
33
|
+
* `fallbackToken` on the record; any other error rethrows.
|
|
34
|
+
* 3. `refreshStore.setSecret` when `bundle.refreshToken` is present.
|
|
35
|
+
* `SecureStoreUnavailableError` degrades to `fallbackRefreshToken`. A
|
|
36
|
+
* non-keyring failure rolls back the access slot before rethrowing
|
|
37
|
+
* (no partial credentials left behind for an unregistered user).
|
|
38
|
+
* 4. `userRecords.upsert(record)`. On failure, best-effort
|
|
39
|
+
* `Promise.allSettled` rollback of any slot writes that succeeded.
|
|
40
|
+
* 5. Only after a successful upsert: if the bundle has no refresh token,
|
|
41
|
+
* wipe any orphan slot from a prior `setBundle` (best-effort). Doing
|
|
42
|
+
* this BEFORE the upsert would lose refresh state if the upsert then
|
|
43
|
+
* rejected — the new record's `hasRefreshToken` would still claim
|
|
44
|
+
* false but the old slot would be gone with no rollback path.
|
|
45
|
+
*
|
|
46
|
+
* Default promotion is external — preference, not correctness, and an
|
|
47
|
+
* error there must not dirty up a successful credential write.
|
|
48
|
+
*/
|
|
49
|
+
export async function writeBundleWithKeyringFallback(options) {
|
|
50
|
+
const { accessStore, refreshStore, userRecords, account, bundle } = options;
|
|
51
|
+
const accessToken = bundle.accessToken.trim();
|
|
52
|
+
if (!accessToken) {
|
|
53
|
+
throw new CliError('AUTH_STORE_WRITE_FAILED', 'Refusing to persist a bundle with an empty access token.');
|
|
54
|
+
}
|
|
55
|
+
const refreshToken = bundle.refreshToken?.trim();
|
|
56
|
+
let accessStoredSecurely = false;
|
|
23
57
|
try {
|
|
24
|
-
await
|
|
25
|
-
|
|
58
|
+
await accessStore.setSecret(accessToken);
|
|
59
|
+
accessStoredSecurely = true;
|
|
26
60
|
}
|
|
27
61
|
catch (error) {
|
|
28
62
|
if (!(error instanceof SecureStoreUnavailableError))
|
|
29
63
|
throw error;
|
|
30
64
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
65
|
+
let refreshStoredSecurely;
|
|
66
|
+
if (refreshToken) {
|
|
67
|
+
try {
|
|
68
|
+
await refreshStore.setSecret(refreshToken);
|
|
69
|
+
refreshStoredSecurely = true;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (error instanceof SecureStoreUnavailableError) {
|
|
73
|
+
refreshStoredSecurely = false;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (accessStoredSecurely) {
|
|
77
|
+
try {
|
|
78
|
+
await accessStore.deleteSecret();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// best-effort
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const record = {
|
|
89
|
+
account,
|
|
90
|
+
...(accessStoredSecurely ? {} : { fallbackToken: accessToken }),
|
|
91
|
+
...(refreshToken && refreshStoredSecurely === false
|
|
92
|
+
? { fallbackRefreshToken: refreshToken }
|
|
93
|
+
: {}),
|
|
94
|
+
...(bundle.accessTokenExpiresAt !== undefined
|
|
95
|
+
? { accessTokenExpiresAt: bundle.accessTokenExpiresAt }
|
|
96
|
+
: {}),
|
|
97
|
+
...(bundle.refreshTokenExpiresAt !== undefined
|
|
98
|
+
? { refreshTokenExpiresAt: bundle.refreshTokenExpiresAt }
|
|
99
|
+
: {}),
|
|
100
|
+
hasRefreshToken: Boolean(refreshToken),
|
|
101
|
+
};
|
|
34
102
|
try {
|
|
35
103
|
await userRecords.upsert(record);
|
|
36
104
|
}
|
|
37
105
|
catch (error) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
106
|
+
const rollbacks = [];
|
|
107
|
+
if (accessStoredSecurely)
|
|
108
|
+
rollbacks.push(accessStore.deleteSecret());
|
|
109
|
+
if (refreshStoredSecurely === true)
|
|
110
|
+
rollbacks.push(refreshStore.deleteSecret());
|
|
111
|
+
if (rollbacks.length > 0) {
|
|
112
|
+
await Promise.allSettled(rollbacks);
|
|
45
113
|
}
|
|
46
114
|
throw error;
|
|
47
115
|
}
|
|
48
|
-
|
|
116
|
+
// Deferred: wipe any orphan refresh slot from a prior setBundle now
|
|
117
|
+
// that the new record (with `hasRefreshToken: false`) is durable. If
|
|
118
|
+
// this fails the gate already prevents readers from consulting it; the
|
|
119
|
+
// worst case is a stale keyring entry that `clear()` will pick up.
|
|
120
|
+
if (!refreshToken) {
|
|
121
|
+
try {
|
|
122
|
+
await refreshStore.deleteSecret();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// best-effort
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return { accessStoredSecurely, refreshStoredSecurely };
|
|
49
129
|
}
|
|
130
|
+
const NOOP_SECURE_STORE = {
|
|
131
|
+
async getSecret() {
|
|
132
|
+
return null;
|
|
133
|
+
},
|
|
134
|
+
async setSecret() {
|
|
135
|
+
// no-op
|
|
136
|
+
},
|
|
137
|
+
async deleteSecret() {
|
|
138
|
+
return false;
|
|
139
|
+
},
|
|
140
|
+
};
|
|
50
141
|
//# sourceMappingURL=record-write.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record-write.js","sourceRoot":"","sources":["../../../src/auth/keyring/record-write.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"record-write.js","sourceRoot":"","sources":["../../../src/auth/keyring/record-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the refresh slot name from the access slug. Single-sourced so the
|
|
3
|
+
* write and read paths can't drift onto different suffixes. Internal.
|
|
4
|
+
*/
|
|
5
|
+
export declare function refreshAccountSlot(accountSlug: string): string;
|
|
6
|
+
//# sourceMappingURL=slot-naming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot-naming.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/slot-naming.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Derives the refresh slot name from the access slug. Single-sourced so the
|
|
3
|
+
* write and read paths can't drift onto different suffixes. Internal.
|
|
4
|
+
*/
|
|
5
|
+
export function refreshAccountSlot(accountSlug) {
|
|
6
|
+
return `${accountSlug}/refresh`;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=slot-naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slot-naming.js","sourceRoot":"","sources":["../../../src/auth/keyring/slot-naming.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,WAAmB;IAClD,OAAO,GAAG,WAAW,UAAU,CAAA;AACnC,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AccountRef, AuthAccount, TokenStore } from '../types.js';
|
|
1
|
+
import type { AccountRef, AuthAccount, TokenBundle, TokenStore } from '../types.js';
|
|
2
2
|
import type { TokenStorageResult, UserRecordStore } from './types.js';
|
|
3
3
|
export type CreateKeyringTokenStoreOptions<TAccount extends AuthAccount> = {
|
|
4
4
|
/** Application identifier used for every keyring entry (e.g. `'todoist-cli'`). */
|
|
@@ -24,7 +24,15 @@ export type CreateKeyringTokenStoreOptions<TAccount extends AuthAccount> = {
|
|
|
24
24
|
matchAccount?: (account: TAccount, ref: AccountRef) => boolean;
|
|
25
25
|
};
|
|
26
26
|
export type KeyringTokenStore<TAccount extends AuthAccount> = TokenStore<TAccount> & {
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Override `setBundle` as required (not optional) — the keyring store
|
|
29
|
+
* always knows how to persist refresh state. Lets cli-core helpers
|
|
30
|
+
* (`persistBundle`) call it without a non-null assertion.
|
|
31
|
+
*/
|
|
32
|
+
setBundle(account: TAccount, bundle: TokenBundle, options?: {
|
|
33
|
+
promoteDefault?: boolean;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
/** Storage result from the most recent `set()` / `setBundle()` call, or `undefined` before any (and reset to `undefined` when the most recent write threw). */
|
|
28
36
|
getLastStorageResult(): TokenStorageResult | undefined;
|
|
29
37
|
/** Storage result from the most recent `clear()` call, or `undefined` before any (and reset to `undefined` when the most recent `clear()` threw or was a no-op). */
|
|
30
38
|
getLastClearResult(): TokenStorageResult | undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/token-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/token-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAWnF,OAAO,KAAK,EAAE,kBAAkB,EAAc,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjF,MAAM,MAAM,8BAA8B,CAAC,QAAQ,SAAS,WAAW,IAAI;IACvE,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAA;IACnB,oFAAoF;IACpF,WAAW,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACtC;;;;OAIG;IACH,eAAe,EAAE,MAAM,CAAA;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAA;IACvC;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,KAAK,OAAO,CAAA;CACjE,CAAA;AAED,MAAM,MAAM,iBAAiB,CAAC,QAAQ,SAAS,WAAW,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG;IACjF;;;;OAIG;IACH,SAAS,CACL,OAAO,EAAE,QAAQ,EACjB,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,GACvC,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,+JAA+J;IAC/J,oBAAoB,IAAI,kBAAkB,GAAG,SAAS,CAAA;IACtD,oKAAoK;IACpK,kBAAkB,IAAI,kBAAkB,GAAG,SAAS,CAAA;CACvD,CAAA;AAOD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,SAAS,WAAW,EAChE,OAAO,EAAE,8BAA8B,CAAC,QAAQ,CAAC,GAClD,iBAAiB,CAAC,QAAQ,CAAC,CAoS7B"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { CliError } from '../../errors.js';
|
|
1
|
+
import { CliError, getErrorMessage } from '../../errors.js';
|
|
2
2
|
import { accountNotFoundError } from '../user-flag.js';
|
|
3
|
-
import { writeRecordWithKeyringFallback } from './record-write.js';
|
|
3
|
+
import { writeBundleWithKeyringFallback, writeRecordWithKeyringFallback } from './record-write.js';
|
|
4
4
|
import { createSecureStore, DEFAULT_ACCOUNT_FOR_USER, SECURE_STORE_DESCRIPTION, SecureStoreUnavailableError, } from './secure-store.js';
|
|
5
|
+
import { refreshAccountSlot } from './slot-naming.js';
|
|
5
6
|
const DEFAULT_MATCH_ACCOUNT = (account, ref) => account.id === ref || account.label === ref;
|
|
6
7
|
/**
|
|
7
8
|
* Multi-account `TokenStore` that keeps secrets in the OS credential manager
|
|
@@ -37,6 +38,12 @@ export function createKeyringTokenStore(options) {
|
|
|
37
38
|
function secureStoreFor(account) {
|
|
38
39
|
return createSecureStore({ serviceName, account: accountForUser(account.id) });
|
|
39
40
|
}
|
|
41
|
+
function refreshSecureStoreFor(account) {
|
|
42
|
+
return createSecureStore({
|
|
43
|
+
serviceName,
|
|
44
|
+
account: refreshAccountSlot(accountForUser(account.id)),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
40
47
|
/**
|
|
41
48
|
* Read both `list()` and `getDefaultId()` concurrently. Used by paths
|
|
42
49
|
* that need the pinned default (no-ref `active`/`clear`, `list`, and
|
|
@@ -87,6 +94,43 @@ export function createKeyringTokenStore(options) {
|
|
|
87
94
|
warning: `${SECURE_STORE_DESCRIPTION} unavailable; ${action} ${recordsLocation}`,
|
|
88
95
|
};
|
|
89
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Compose a storage result for a write that may have fallen back on
|
|
99
|
+
* either slot. `accessStored === false` indicates the access token went
|
|
100
|
+
* to `fallbackToken`; `refreshStored === false` indicates the refresh
|
|
101
|
+
* token went to `fallbackRefreshToken`. Either falsy slot downgrades
|
|
102
|
+
* the result to `config-file` so consumers see the warning — refresh
|
|
103
|
+
* plaintext is just as security-relevant as access plaintext.
|
|
104
|
+
*/
|
|
105
|
+
function bundleStorageResult(accessStored, refreshStored) {
|
|
106
|
+
const accessFallback = !accessStored;
|
|
107
|
+
const refreshFallback = refreshStored === false;
|
|
108
|
+
if (!accessFallback && !refreshFallback)
|
|
109
|
+
return { storage: 'secure-store' };
|
|
110
|
+
const subject = accessFallback && refreshFallback
|
|
111
|
+
? 'access + refresh tokens'
|
|
112
|
+
: accessFallback
|
|
113
|
+
? 'access token'
|
|
114
|
+
: 'refresh token';
|
|
115
|
+
return fallbackResult(`${subject} saved as plaintext in`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Best-effort default promotion shared by `set` and `setBundle`. The
|
|
119
|
+
* record is already persisted, so a failure here must not surface as
|
|
120
|
+
* `AUTH_STORE_WRITE_FAILED` — the user can recover by setting a
|
|
121
|
+
* default later.
|
|
122
|
+
*/
|
|
123
|
+
async function promoteDefaultIfNeeded(accountId) {
|
|
124
|
+
try {
|
|
125
|
+
const existingDefault = await userRecords.getDefaultId();
|
|
126
|
+
if (!existingDefault) {
|
|
127
|
+
await userRecords.setDefaultId(accountId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// best-effort
|
|
132
|
+
}
|
|
133
|
+
}
|
|
90
134
|
return {
|
|
91
135
|
async active(ref) {
|
|
92
136
|
// Ref-only path skips `getDefaultId()` — `resolveTarget` never
|
|
@@ -98,37 +142,31 @@ export function createKeyringTokenStore(options) {
|
|
|
98
142
|
const record = resolveTarget(snapshot, ref);
|
|
99
143
|
if (!record)
|
|
100
144
|
return null;
|
|
145
|
+
// Reads the access slot only. Refresh-state material lives in
|
|
146
|
+
// the keyring and on the record, but `active()` stays cheap and
|
|
147
|
+
// returns the pre-PR1 snapshot shape — a future bundle-aware
|
|
148
|
+
// read path lights up the refresh slot only when callers
|
|
149
|
+
// actually need it (silent refresh).
|
|
101
150
|
const fallback = record.fallbackToken?.trim();
|
|
102
|
-
if (fallback)
|
|
151
|
+
if (fallback)
|
|
103
152
|
return { token: fallback, account: record.account };
|
|
104
|
-
}
|
|
105
153
|
let raw;
|
|
106
154
|
try {
|
|
107
155
|
raw = await secureStoreFor(record.account).getSecret();
|
|
108
156
|
}
|
|
109
157
|
catch (error) {
|
|
110
|
-
// A matching record exists but the keyring can't be read.
|
|
111
|
-
// Surface a typed failure instead of returning `null`, which
|
|
112
|
-
// would otherwise be indistinguishable from "no stored
|
|
113
|
-
// account" and trigger `ACCOUNT_NOT_FOUND` on `--user <ref>`.
|
|
114
|
-
// `attachLogoutCommand` catches this specific code so an
|
|
115
|
-
// explicit `logout --user <ref>` can still clear the matching
|
|
116
|
-
// record without needing the unreadable token.
|
|
117
158
|
if (error instanceof SecureStoreUnavailableError) {
|
|
118
159
|
throw new CliError('AUTH_STORE_READ_FAILED', `${SECURE_STORE_DESCRIPTION} unavailable; could not read stored token (${error.message})`);
|
|
119
160
|
}
|
|
120
|
-
|
|
161
|
+
// Non-keyring backend failures wrap into the typed code too —
|
|
162
|
+
// a raw exception escaping `active()` would crash the CLI
|
|
163
|
+
// with no useful exit signal.
|
|
164
|
+
throw new CliError('AUTH_STORE_READ_FAILED', `Access-slot read failed (${getErrorMessage(error)})`);
|
|
121
165
|
}
|
|
122
166
|
const token = raw?.trim();
|
|
123
|
-
if (token)
|
|
167
|
+
if (token)
|
|
124
168
|
return { token, account: record.account };
|
|
125
|
-
|
|
126
|
-
// Record exists, no `fallbackToken`, and the keyring slot is
|
|
127
|
-
// empty — the credential was deleted out-of-band (user ran
|
|
128
|
-
// `security delete-generic-password`, `secret-tool clear`, …).
|
|
129
|
-
// This is corrupted state, not a miss; collapsing it to `null`
|
|
130
|
-
// would make `--user <ref>` surface as `ACCOUNT_NOT_FOUND` and
|
|
131
|
-
// hide the real problem.
|
|
169
|
+
// Record exists, no `fallbackToken`, slot empty — corruption.
|
|
132
170
|
throw new CliError('AUTH_STORE_READ_FAILED', `${SECURE_STORE_DESCRIPTION} returned no credential for the stored account; the keyring entry may have been removed externally.`);
|
|
133
171
|
},
|
|
134
172
|
async set(account, token) {
|
|
@@ -138,25 +176,29 @@ export function createKeyringTokenStore(options) {
|
|
|
138
176
|
lastStorageResult = undefined;
|
|
139
177
|
const { storedSecurely } = await writeRecordWithKeyringFallback({
|
|
140
178
|
secureStore: secureStoreFor(account),
|
|
179
|
+
refreshStore: refreshSecureStoreFor(account),
|
|
141
180
|
userRecords,
|
|
142
181
|
account,
|
|
143
182
|
token,
|
|
144
183
|
});
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
184
|
+
await promoteDefaultIfNeeded(account.id);
|
|
185
|
+
lastStorageResult = bundleStorageResult(storedSecurely, undefined);
|
|
186
|
+
},
|
|
187
|
+
async setBundle(account, bundle, options) {
|
|
188
|
+
lastStorageResult = undefined;
|
|
189
|
+
const { accessStoredSecurely, refreshStoredSecurely } = await writeBundleWithKeyringFallback({
|
|
190
|
+
accessStore: secureStoreFor(account),
|
|
191
|
+
refreshStore: refreshSecureStoreFor(account),
|
|
192
|
+
userRecords,
|
|
193
|
+
account,
|
|
194
|
+
bundle,
|
|
195
|
+
});
|
|
196
|
+
// Opt-in: silent refresh omits `promoteDefault` so it can't
|
|
197
|
+
// re-pin selection; login passes `true` to match `set()`.
|
|
198
|
+
if (options?.promoteDefault) {
|
|
199
|
+
await promoteDefaultIfNeeded(account.id);
|
|
156
200
|
}
|
|
157
|
-
lastStorageResult =
|
|
158
|
-
? { storage: 'secure-store' }
|
|
159
|
-
: fallbackResult('token saved as plaintext in');
|
|
201
|
+
lastStorageResult = bundleStorageResult(accessStoredSecurely, refreshStoredSecurely);
|
|
160
202
|
},
|
|
161
203
|
async clear(ref) {
|
|
162
204
|
// Reset up front for the same reason as `set` — and so a no-op
|
|
@@ -183,22 +225,19 @@ export function createKeyringTokenStore(options) {
|
|
|
183
225
|
}
|
|
184
226
|
}
|
|
185
227
|
const fallbackClear = fallbackResult('local auth state cleared in');
|
|
186
|
-
// Always attempt
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
lastClearResult = fallbackClear;
|
|
201
|
-
}
|
|
228
|
+
// Always attempt both deletes — a record's `fallbackToken`
|
|
229
|
+
// doesn't rule out an orphan keyring entry from a prior online
|
|
230
|
+
// write. Failures downgrade to a warning: the record is already
|
|
231
|
+
// gone, re-throwing would corrupt the caller's state.
|
|
232
|
+
const [accessOutcome, refreshOutcome] = await Promise.allSettled([
|
|
233
|
+
secureStoreFor(record.account).deleteSecret(),
|
|
234
|
+
refreshSecureStoreFor(record.account).deleteSecret(),
|
|
235
|
+
]);
|
|
236
|
+
const fellBack = accessOutcome.status === 'rejected' ||
|
|
237
|
+
refreshOutcome.status === 'rejected' ||
|
|
238
|
+
record.fallbackToken !== undefined ||
|
|
239
|
+
record.fallbackRefreshToken !== undefined;
|
|
240
|
+
lastClearResult = fellBack ? fallbackClear : { storage: 'secure-store' };
|
|
202
241
|
},
|
|
203
242
|
async list() {
|
|
204
243
|
const snapshot = await readFullSnapshot();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../../src/auth/keyring/token-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../../src/auth/keyring/token-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -22,6 +22,20 @@ export type UserRecord<TAccount extends AuthAccount> = {
|
|
|
22
22
|
* that would otherwise live in the OS credential manager.
|
|
23
23
|
*/
|
|
24
24
|
fallbackToken?: string;
|
|
25
|
+
/** Same lifecycle and security profile as `fallbackToken`, for the refresh slot. */
|
|
26
|
+
fallbackRefreshToken?: string;
|
|
27
|
+
/** Access-token expiry, unix-epoch ms. */
|
|
28
|
+
accessTokenExpiresAt?: number;
|
|
29
|
+
/** Refresh-token expiry, unix-epoch ms. */
|
|
30
|
+
refreshTokenExpiresAt?: number;
|
|
31
|
+
/**
|
|
32
|
+
* `true` when a refresh secret is stored (in the keyring or as
|
|
33
|
+
* `fallbackRefreshToken`); `false` when explicitly cleared by `set()`
|
|
34
|
+
* or by a no-refresh `setBundle`; `undefined` on legacy records that
|
|
35
|
+
* predate the bundle contract. Read by future bundle-aware accessors;
|
|
36
|
+
* `active()` itself doesn't consult it.
|
|
37
|
+
*/
|
|
38
|
+
hasRefreshToken?: boolean;
|
|
25
39
|
};
|
|
26
40
|
/**
|
|
27
41
|
* Port the consumer implements to expose their per-user config records to
|
|
@@ -39,6 +53,12 @@ export type UserRecordStore<TAccount extends AuthAccount> = {
|
|
|
39
53
|
* `fallbackToken` over the keyring). Records are keyed by `account.id`.
|
|
40
54
|
*/
|
|
41
55
|
upsert(record: UserRecord<TAccount>): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Optional atomic insert. Returns `true` on write, `false` if `account.id`
|
|
58
|
+
* already exists. Migration prefers it to eliminate the existence-check
|
|
59
|
+
* TOCTOU race; callers fall back to list-then-upsert when absent.
|
|
60
|
+
*/
|
|
61
|
+
tryInsert?(record: UserRecord<TAccount>): Promise<boolean>;
|
|
42
62
|
/** Remove the record whose `account.id` matches. */
|
|
43
63
|
remove(id: string): Promise<void>;
|
|
44
64
|
/** The pinned default's `account.id`, or `null` when nothing is pinned. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,8EAA8E;AAC9E,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,aAAa,CAAA;AAEjE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,OAAO,EAAE,oBAAoB,CAAA;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,CAAC,QAAQ,SAAS,WAAW,IAAI;IACnD,OAAO,EAAE,QAAQ,CAAA;IACjB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/keyring/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,8EAA8E;AAC9E,MAAM,MAAM,oBAAoB,GAAG,cAAc,GAAG,aAAa,CAAA;AAEjE,MAAM,MAAM,kBAAkB,GAAG;IAC7B,OAAO,EAAE,oBAAoB,CAAA;IAC7B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,UAAU,CAAC,QAAQ,SAAS,WAAW,IAAI;IACnD,OAAO,EAAE,QAAQ,CAAA;IACjB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oFAAoF;IACpF,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,0CAA0C;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,2CAA2C;IAC3C,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,CAAC,QAAQ,SAAS,WAAW,IAAI;IACxD,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IACvC;;;;;;OAMG;IACH,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD;;;;OAIG;IACH,SAAS,CAAC,CAAC,MAAM,EAAE,UAAU,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC1D,oDAAoD;IACpD,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,2EAA2E;IAC3E,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IACtC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACjD,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AuthAccount, TokenBundle, TokenStore } from './types.js';
|
|
2
|
+
export type PersistBundleOptions<TAccount extends AuthAccount> = {
|
|
3
|
+
store: TokenStore<TAccount>;
|
|
4
|
+
account: TAccount;
|
|
5
|
+
bundle: TokenBundle;
|
|
6
|
+
/** Forwarded to `setBundle` when present. See `TokenStore.setBundle`. */
|
|
7
|
+
promoteDefault?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Persist a bundle against any `TokenStore`. Prefers `setBundle` when the
|
|
11
|
+
* store implements it; otherwise falls back to `set(account, accessToken)`
|
|
12
|
+
* and silently drops refresh state. Wraps non-`CliError` failures as
|
|
13
|
+
* `AUTH_STORE_WRITE_FAILED`.
|
|
14
|
+
*
|
|
15
|
+
* `promoteDefault` is only honoured on the `setBundle` path — the base
|
|
16
|
+
* `set()` contract has no promotion control, so a custom multi-account
|
|
17
|
+
* store that opts out of `setBundle` will run its own promotion policy
|
|
18
|
+
* (typically first-account-wins). Multi-account stores that need
|
|
19
|
+
* silent-refresh-safe selection (no re-pinning on background rotation)
|
|
20
|
+
* MUST implement `setBundle`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function persistBundle<TAccount extends AuthAccount>(options: PersistBundleOptions<TAccount>): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=persist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.d.ts","sourceRoot":"","sources":["../../src/auth/persist.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEtE,MAAM,MAAM,oBAAoB,CAAC,QAAQ,SAAS,WAAW,IAAI;IAC7D,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC3B,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,WAAW,CAAA;IACnB,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,aAAa,CAAC,QAAQ,SAAS,WAAW,EAC5D,OAAO,EAAE,oBAAoB,CAAC,QAAQ,CAAC,GACxC,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { CliError, getErrorMessage } from '../errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Persist a bundle against any `TokenStore`. Prefers `setBundle` when the
|
|
4
|
+
* store implements it; otherwise falls back to `set(account, accessToken)`
|
|
5
|
+
* and silently drops refresh state. Wraps non-`CliError` failures as
|
|
6
|
+
* `AUTH_STORE_WRITE_FAILED`.
|
|
7
|
+
*
|
|
8
|
+
* `promoteDefault` is only honoured on the `setBundle` path — the base
|
|
9
|
+
* `set()` contract has no promotion control, so a custom multi-account
|
|
10
|
+
* store that opts out of `setBundle` will run its own promotion policy
|
|
11
|
+
* (typically first-account-wins). Multi-account stores that need
|
|
12
|
+
* silent-refresh-safe selection (no re-pinning on background rotation)
|
|
13
|
+
* MUST implement `setBundle`.
|
|
14
|
+
*/
|
|
15
|
+
export async function persistBundle(options) {
|
|
16
|
+
const { store, account, bundle, promoteDefault } = options;
|
|
17
|
+
try {
|
|
18
|
+
if (store.setBundle) {
|
|
19
|
+
// Omit the options arg entirely when unset so presence-based
|
|
20
|
+
// handlers can distinguish "default" from explicit opt-out.
|
|
21
|
+
if (promoteDefault === undefined) {
|
|
22
|
+
await store.setBundle(account, bundle);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
await store.setBundle(account, bundle, { promoteDefault });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
await store.set(account, bundle.accessToken);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
if (error instanceof CliError)
|
|
34
|
+
throw error;
|
|
35
|
+
throw new CliError('AUTH_STORE_WRITE_FAILED', `Failed to persist token: ${getErrorMessage(error)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=persist.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persist.js","sourceRoot":"","sources":["../../src/auth/persist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAWxD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAC/B,OAAuC;IAEvC,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAA;IAC1D,IAAI,CAAC;QACD,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;YAClB,6DAA6D;YAC7D,4DAA4D;YAC5D,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;YAC1C,CAAC;iBAAM,CAAC;gBACJ,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAA;YAC9D,CAAC;QACL,CAAC;aAAM,CAAC;YACJ,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAA;QAChD,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,KAAK,YAAY,QAAQ;YAAE,MAAM,KAAK,CAAA;QAC1C,MAAM,IAAI,QAAQ,CACd,yBAAyB,EACzB,4BAA4B,eAAe,CAAC,KAAK,CAAC,EAAE,CACvD,CAAA;IACL,CAAC;AACL,CAAC"}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -40,8 +40,10 @@ export type ExchangeInput = {
|
|
|
40
40
|
export type ExchangeResult<TAccount extends AuthAccount = AuthAccount> = {
|
|
41
41
|
accessToken: string;
|
|
42
42
|
refreshToken?: string;
|
|
43
|
-
/**
|
|
43
|
+
/** Access-token expiry, unix-epoch ms. */
|
|
44
44
|
expiresAt?: number;
|
|
45
|
+
/** Refresh-token expiry, unix-epoch ms. */
|
|
46
|
+
refreshTokenExpiresAt?: number;
|
|
45
47
|
/** Set when the token endpoint already identifies the account; skips `validateToken`. */
|
|
46
48
|
account?: TAccount;
|
|
47
49
|
};
|
|
@@ -50,6 +52,11 @@ export type ValidateInput = {
|
|
|
50
52
|
/** Same shape as `ExchangeInput.handshake` — carries the folded `flags` / `readOnly`. */
|
|
51
53
|
handshake: Record<string, unknown>;
|
|
52
54
|
};
|
|
55
|
+
export type RefreshInput = {
|
|
56
|
+
refreshToken: string;
|
|
57
|
+
/** Same shape as `ExchangeInput.handshake` — empty when called outside `runOAuthFlow`. */
|
|
58
|
+
handshake: Record<string, unknown>;
|
|
59
|
+
};
|
|
53
60
|
/**
|
|
54
61
|
* Strategy interface every auth method implements. cli-core ships
|
|
55
62
|
* `createPkceProvider` for the standard public-client PKCE flow; bespoke
|
|
@@ -62,6 +69,15 @@ export type AuthProvider<TAccount extends AuthAccount = AuthAccount> = {
|
|
|
62
69
|
exchangeCode(input: ExchangeInput): Promise<ExchangeResult<TAccount>>;
|
|
63
70
|
/** Skipped when `exchangeCode` already returned an `account`. */
|
|
64
71
|
validateToken(input: ValidateInput): Promise<TAccount>;
|
|
72
|
+
/** Optional: exchange a refresh token for a fresh bundle. */
|
|
73
|
+
refreshToken?(input: RefreshInput): Promise<ExchangeResult<TAccount>>;
|
|
74
|
+
};
|
|
75
|
+
/** Write-side bundle for `setBundle`. Time fields are unix-epoch ms. */
|
|
76
|
+
export type TokenBundle = {
|
|
77
|
+
accessToken: string;
|
|
78
|
+
refreshToken?: string;
|
|
79
|
+
accessTokenExpiresAt?: number;
|
|
80
|
+
refreshTokenExpiresAt?: number;
|
|
65
81
|
};
|
|
66
82
|
/** Opaque account selector. Stores own the matching rule (id, email, label, …). */
|
|
67
83
|
export type AccountRef = string;
|
|
@@ -86,6 +102,16 @@ export type TokenStore<TAccount extends AuthAccount = AuthAccount> = {
|
|
|
86
102
|
} | null>;
|
|
87
103
|
/** Persist `token` for `account`, replacing any previous entry. Throw `CliError` for typed failures; other thrown values become `AUTH_STORE_WRITE_FAILED`. */
|
|
88
104
|
set(account: TAccount, token: string): Promise<void>;
|
|
105
|
+
/**
|
|
106
|
+
* Persist a full bundle. Optional on the contract — stores that don't
|
|
107
|
+
* implement it get `bundle.accessToken` via `set()` instead (cli-core
|
|
108
|
+
* helpers handle the fallback). Pass `promoteDefault: true` on first
|
|
109
|
+
* login; omit on silent refresh so a background rotation can't re-pin
|
|
110
|
+
* account selection.
|
|
111
|
+
*/
|
|
112
|
+
setBundle?(account: TAccount, bundle: TokenBundle, options?: {
|
|
113
|
+
promoteDefault?: boolean;
|
|
114
|
+
}): Promise<void>;
|
|
89
115
|
/** Remove a stored credential. No-op when `ref` doesn't match. */
|
|
90
116
|
clear(ref?: AccountRef): Promise<void>;
|
|
91
117
|
/** Every stored account with a default marker. */
|
package/dist/auth/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA,oFAAoF;AACpF,MAAM,MAAM,WAAW,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,YAAY,GAAG;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IACxB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IACrE,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Shared core utilities for Doist CLI projects",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -66,18 +66,18 @@
|
|
|
66
66
|
"@semantic-release/changelog": "6.0.3",
|
|
67
67
|
"@semantic-release/exec": "7.1.0",
|
|
68
68
|
"@semantic-release/git": "10.0.1",
|
|
69
|
-
"@types/node": "25.
|
|
69
|
+
"@types/node": "25.7.0",
|
|
70
70
|
"commander": "14.0.3",
|
|
71
71
|
"conventional-changelog-conventionalcommits": "9.3.1",
|
|
72
72
|
"lefthook": "2.1.6",
|
|
73
73
|
"marked": "18.0.3",
|
|
74
74
|
"marked-terminal-renderer": "2.2.0",
|
|
75
75
|
"open": "11.0.0",
|
|
76
|
-
"oxfmt": "0.
|
|
77
|
-
"oxlint": "1.
|
|
76
|
+
"oxfmt": "0.49.0",
|
|
77
|
+
"oxlint": "1.64.0",
|
|
78
78
|
"semantic-release": "25.0.3",
|
|
79
79
|
"typescript": "6.0.3",
|
|
80
|
-
"vitest": "4.1.
|
|
80
|
+
"vitest": "4.1.6"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"chalk": "5.6.2",
|