@doist/cli-core 0.16.0 → 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.
@@ -1,50 +1,141 @@
1
+ import { CliError } from '../../errors.js';
1
2
  import { SecureStoreUnavailableError } from './secure-store.js';
2
3
  /**
3
- * Shared keyring-then-record write used by `createKeyringTokenStore.set` and
4
- * `migrateLegacyAuth`. Encapsulates the order-of-operations contract that
5
- * matters for credential safety:
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
- * 1. Keyring `setSecret` first. On `SecureStoreUnavailableError`, swallow
8
- * the failure and record a `fallbackToken` on the user record instead.
9
- * Any other error rethrows.
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 trimmed = token.trim();
22
- let storedSecurely = false;
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 secureStore.setSecret(trimmed);
25
- storedSecurely = true;
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
- const record = storedSecurely
32
- ? { account }
33
- : { account, fallbackToken: trimmed };
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
- if (storedSecurely) {
39
- try {
40
- await secureStore.deleteSecret();
41
- }
42
- catch {
43
- // best-effort the user record failure is the real cause
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
- return { storedSecurely };
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":"AACA,OAAO,EAAoB,2BAA2B,EAAE,MAAM,mBAAmB,CAAA;AAgBjF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAChD,OAAqC;IAErC,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,OAAO,CAAA;IAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAA;IAE5B,IAAI,cAAc,GAAG,KAAK,CAAA;IAC1B,IAAI,CAAC;QACD,MAAM,WAAW,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;QACpC,cAAc,GAAG,IAAI,CAAA;IACzB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,CAAC,CAAC,KAAK,YAAY,2BAA2B,CAAC;YAAE,MAAM,KAAK,CAAA;IACpE,CAAC;IAED,MAAM,MAAM,GAAyB,cAAc;QAC/C,CAAC,CAAC,EAAE,OAAO,EAAE;QACb,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,CAAA;IAEzC,IAAI,CAAC;QACD,MAAM,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACb,IAAI,cAAc,EAAE,CAAC;YACjB,IAAI,CAAC;gBACD,MAAM,WAAW,CAAC,YAAY,EAAE,CAAA;YACpC,CAAC;YAAC,MAAM,CAAC;gBACL,0DAA0D;YAC9D,CAAC;QACL,CAAC;QACD,MAAM,KAAK,CAAA;IACf,CAAC;IAED,OAAO,EAAE,cAAc,EAAE,CAAA;AAC7B,CAAC"}
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
- /** Storage result from the most recent `set()` call, or `undefined` before any (and reset to `undefined` when the most recent `set()` threw). */
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;AAUtE,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,iJAAiJ;IACjJ,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,CAgP7B"}
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
- throw error;
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
- // Best-effort default promotion: the record is already persisted,
146
- // so a failure here must not turn into `AUTH_STORE_WRITE_FAILED`
147
- // (the user can recover by setting a default later).
148
- try {
149
- const existingDefault = await userRecords.getDefaultId();
150
- if (!existingDefault) {
151
- await userRecords.setDefaultId(account.id);
152
- }
153
- }
154
- catch {
155
- // best-effort
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 = storedSecurely
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 the keyring delete. Even when the record carried
187
- // a `fallbackToken`, an older keyring entry may still be parked
188
- // there from a prior keyring-online write that was later replaced
189
- // by an offline-fallback write skipping the delete would leak
190
- // that orphan. Downgrade *any* failure to a warning: the record
191
- // is already gone, so re-throwing would corrupt local state
192
- // (caller sees an exception and assumes nothing was cleared,
193
- // even though the next `account list` will show the user gone).
194
- try {
195
- await secureStoreFor(record.account).deleteSecret();
196
- lastClearResult =
197
- record.fallbackToken !== undefined ? fallbackClear : { storage: 'secure-store' };
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":"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"}
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;CACzB,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,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"}
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"}