@doist/cli-core 0.18.0 → 0.20.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +63 -20
  3. package/dist/auth/errors.d.ts +1 -1
  4. package/dist/auth/errors.d.ts.map +1 -1
  5. package/dist/auth/flow.d.ts.map +1 -1
  6. package/dist/auth/flow.js +7 -8
  7. package/dist/auth/flow.js.map +1 -1
  8. package/dist/auth/index.d.ts +7 -3
  9. package/dist/auth/index.d.ts.map +1 -1
  10. package/dist/auth/index.js +3 -1
  11. package/dist/auth/index.js.map +1 -1
  12. package/dist/auth/keyring/internal.d.ts +25 -0
  13. package/dist/auth/keyring/internal.d.ts.map +1 -1
  14. package/dist/auth/keyring/internal.js +24 -7
  15. package/dist/auth/keyring/internal.js.map +1 -1
  16. package/dist/auth/keyring/token-store.d.ts +7 -1
  17. package/dist/auth/keyring/token-store.d.ts.map +1 -1
  18. package/dist/auth/keyring/token-store.js +59 -20
  19. package/dist/auth/keyring/token-store.js.map +1 -1
  20. package/dist/auth/persist.d.ts +9 -1
  21. package/dist/auth/persist.d.ts.map +1 -1
  22. package/dist/auth/persist.js +20 -0
  23. package/dist/auth/persist.js.map +1 -1
  24. package/dist/auth/providers/dcr.d.ts +71 -0
  25. package/dist/auth/providers/dcr.d.ts.map +1 -0
  26. package/dist/auth/providers/dcr.js +187 -0
  27. package/dist/auth/providers/dcr.js.map +1 -0
  28. package/dist/auth/providers/oauth.d.ts +105 -0
  29. package/dist/auth/providers/oauth.d.ts.map +1 -0
  30. package/dist/auth/providers/oauth.js +145 -0
  31. package/dist/auth/providers/oauth.js.map +1 -0
  32. package/dist/auth/providers/pkce.d.ts +16 -5
  33. package/dist/auth/providers/pkce.d.ts.map +1 -1
  34. package/dist/auth/providers/pkce.js +92 -63
  35. package/dist/auth/providers/pkce.js.map +1 -1
  36. package/dist/auth/refresh.d.ts +49 -0
  37. package/dist/auth/refresh.d.ts.map +1 -0
  38. package/dist/auth/refresh.js +184 -0
  39. package/dist/auth/refresh.js.map +1 -0
  40. package/dist/auth/status.d.ts +12 -4
  41. package/dist/auth/status.d.ts.map +1 -1
  42. package/dist/auth/status.js +45 -5
  43. package/dist/auth/status.js.map +1 -1
  44. package/dist/auth/types.d.ts +17 -0
  45. package/dist/auth/types.d.ts.map +1 -1
  46. package/package.json +9 -4
@@ -0,0 +1,145 @@
1
+ import { CliError, getErrorMessage } from '../../errors.js';
2
+ /**
3
+ * Build a `CliError` with user-supplied `errorHints` prepended and an optional
4
+ * server-derived `extra` detail appended. Centralises the "user-actionable
5
+ * first, diagnostic second" ordering used everywhere in this directory.
6
+ */
7
+ export function buildAuthError(code, message, userHints, extra) {
8
+ const hints = [...(userHints ?? []), ...(extra ? [extra] : [])];
9
+ return new CliError(code, message, hints.length > 0 ? { hints } : {});
10
+ }
11
+ /**
12
+ * Resolve a literal-or-function endpoint/clientId against the current handshake
13
+ * and runtime flags. Used by every provider in this directory.
14
+ */
15
+ export async function resolve(resolver, handshake, flags) {
16
+ return typeof resolver === 'function' ? resolver({ handshake, flags }) : resolver;
17
+ }
18
+ /** Read a response body without letting a stream error escape — used for hints. */
19
+ export async function safeReadText(response) {
20
+ try {
21
+ const text = (await response.text()).trim();
22
+ return text.length > 0 ? text : undefined;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ /** Construct the standard PKCE S256 authorize URL. */
29
+ export function buildPkceAuthorizeUrl(input) {
30
+ const url = new URL(input.authorizeUrl);
31
+ url.searchParams.set('response_type', 'code');
32
+ url.searchParams.set('client_id', input.clientId);
33
+ url.searchParams.set('redirect_uri', input.redirectUri);
34
+ url.searchParams.set('state', input.state);
35
+ url.searchParams.set('code_challenge', input.codeChallenge);
36
+ url.searchParams.set('code_challenge_method', 'S256');
37
+ if (input.scopes.length > 0) {
38
+ url.searchParams.set('scope', input.scopes.join(input.scopeSeparator));
39
+ }
40
+ return url.toString();
41
+ }
42
+ /**
43
+ * POST a request, parse a JSON response, and wrap every failure mode as a
44
+ * typed `CliError`. Common backbone for the OAuth token endpoint and the
45
+ * RFC 7591 dynamic-client-registration endpoint — both POST a body, both
46
+ * expect a JSON reply, both want uniform error handling.
47
+ *
48
+ * Throws `errorCode` with the configured hints on:
49
+ * - network failure (fetch rejection)
50
+ * - non-2xx response (body text appended as a hint after `errorHints`)
51
+ * - non-JSON 2xx body (a misconfigured proxy returning HTML, etc.)
52
+ *
53
+ * Success-shape validation (e.g. `access_token` present) is the caller's
54
+ * job, because it differs per endpoint.
55
+ */
56
+ export async function postAndParseJson(input) {
57
+ const fail = (message, extra) => buildAuthError(input.errorCode, message, input.errorHints, extra);
58
+ let response;
59
+ try {
60
+ response = await input.fetchImpl(input.url, {
61
+ method: 'POST',
62
+ headers: input.headers,
63
+ body: input.body,
64
+ });
65
+ }
66
+ catch (error) {
67
+ throw fail(`${input.errorLabel} request failed: ${getErrorMessage(error)}`);
68
+ }
69
+ if (!response.ok) {
70
+ const detail = await safeReadText(response);
71
+ throw fail(`${input.errorLabel} returned HTTP ${response.status}.`, detail);
72
+ }
73
+ // Parse defensively — a misconfigured proxy can return a 2xx HTML error
74
+ // page that would otherwise blow up with a raw SyntaxError.
75
+ try {
76
+ return (await response.json());
77
+ }
78
+ catch (error) {
79
+ throw fail(`${input.errorLabel} returned non-JSON response: ${getErrorMessage(error)}`);
80
+ }
81
+ }
82
+ /**
83
+ * POST to an OAuth 2.0 token endpoint and parse the standard JSON response.
84
+ * Covers the public-client `authorization_code` exchange (PKCE) — the caller
85
+ * owns `grant_type` and the grant-specific params via `body`.
86
+ *
87
+ * Failures uniformly throw `CliError('AUTH_TOKEN_EXCHANGE_FAILED', …)`:
88
+ * network errors, non-2xx responses (with body text as a hint), non-JSON
89
+ * bodies, and responses missing `access_token`.
90
+ */
91
+ export async function postTokenEndpoint(input) {
92
+ const headers = {
93
+ 'Content-Type': 'application/x-www-form-urlencoded',
94
+ Accept: 'application/json',
95
+ };
96
+ const payload = await postAndParseJson({
97
+ url: input.url,
98
+ headers,
99
+ body: input.body.toString(),
100
+ errorCode: 'AUTH_TOKEN_EXCHANGE_FAILED',
101
+ errorLabel: 'Token endpoint',
102
+ errorHints: input.errorHints,
103
+ fetchImpl: input.fetchImpl,
104
+ });
105
+ if (!payload.access_token) {
106
+ throw buildAuthError('AUTH_TOKEN_EXCHANGE_FAILED', 'Token endpoint response missing access_token.', input.errorHints);
107
+ }
108
+ return {
109
+ accessToken: payload.access_token,
110
+ refreshToken: payload.refresh_token,
111
+ expiresAt: expiresAtFromExpiresIn(payload.expires_in),
112
+ };
113
+ }
114
+ /** Convert an OAuth `expires_in` (seconds from now) into a Unix-epoch ms deadline. */
115
+ export function expiresAtFromExpiresIn(expiresIn) {
116
+ return typeof expiresIn === 'number' ? Date.now() + expiresIn * 1000 : undefined;
117
+ }
118
+ // Optional peer dep — only DCR and refresh consumers install it. The dynamic
119
+ // import (and a missing-peer failure) is memoised so it isn't repeated on every
120
+ // call that sits on the authenticated-call path.
121
+ let oauthModulePromise;
122
+ /**
123
+ * Lazily import `oauth4webapi`, surfacing a typed `CliError` when the optional
124
+ * peer dep is absent (vs. installed-but-broken). Shared by `createPkceProvider`
125
+ * (refresh) and `createDcrProvider` (registration + token exchange). Caller
126
+ * `userHints` are prepended on both failure branches so the provider's
127
+ * `errorHints` contract holds even when the dep is missing.
128
+ */
129
+ export async function loadOauth4webapi(options) {
130
+ oauthModulePromise ??= import('oauth4webapi');
131
+ try {
132
+ return await oauthModulePromise;
133
+ }
134
+ catch (error) {
135
+ const moduleCode = error?.code;
136
+ if (moduleCode === 'ERR_MODULE_NOT_FOUND' || moduleCode === 'MODULE_NOT_FOUND') {
137
+ const hints = [...(options.userHints ?? []), ...(options.missingHints ?? [])];
138
+ throw new CliError(options.code, options.missingMessage, hints.length > 0 ? { hints } : {});
139
+ }
140
+ // Installed but failed to initialise — surface the real cause rather
141
+ // than a misleading "install it" hint.
142
+ throw buildAuthError(options.code, `Failed to load oauth4webapi: ${getErrorMessage(error)}`, options.userHints);
143
+ }
144
+ }
145
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/auth/providers/oauth.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,17 @@
1
1
  import type { AuthAccount, AuthProvider, ValidateInput } from '../types.js';
2
2
  /**
3
3
  * Lazy resolver: a literal string, or a function that builds one from the
4
- * current PKCE handshake (so callers can derive the URL or client_id from
5
- * the active session's `baseUrl` / per-flow flags).
4
+ * current OAuth handshake (so callers can derive the URL or client_id from
5
+ * the active session's `baseUrl` / per-flow flags). Used by both
6
+ * `createPkceProvider` and `createDcrProvider`; prefer the grant-agnostic
7
+ * alias `OAuthLazyString` for new code.
6
8
  */
7
9
  export type PkceLazyString = string | ((ctx: {
8
10
  handshake: Record<string, unknown>;
9
11
  flags: Record<string, unknown>;
10
- }) => string);
12
+ }) => string | Promise<string>);
13
+ /** Grant-agnostic alias for {@link PkceLazyString}. Identical type. */
14
+ export type OAuthLazyString = PkceLazyString;
11
15
  export type PkceProviderOptions<TAccount extends AuthAccount = AuthAccount> = {
12
16
  /** OAuth 2.0 authorize endpoint. Function form supports per-flow base URLs (Outline self-hosted). */
13
17
  authorizeUrl: PkceLazyString;
@@ -22,6 +26,13 @@ export type PkceProviderOptions<TAccount extends AuthAccount = AuthAccount> = {
22
26
  verifierLength?: number;
23
27
  /** Probe an authenticated endpoint to confirm the token works and resolve the account. */
24
28
  validate: (input: ValidateInput) => Promise<TAccount>;
29
+ /**
30
+ * User-facing remediation hints attached to every CliError this factory
31
+ * throws (token-endpoint failures, internal handshake-state guards).
32
+ * Server-returned response bodies are appended after these so the
33
+ * actionable hint stays first.
34
+ */
35
+ errorHints?: string[];
25
36
  /** Inject a fetch implementation (tests). */
26
37
  fetchImpl?: typeof fetch;
27
38
  };
@@ -35,8 +46,8 @@ export type PkceProviderOptions<TAccount extends AuthAccount = AuthAccount> = {
35
46
  * `runOAuthFlow` and arrives on `AuthorizeInput.scopes`; this factory does
36
47
  * not own scope resolution.
37
48
  *
38
- * Flows that need DCR or HTTP Basic auth on the token endpoint implement
39
- * the `AuthProvider` interface directly.
49
+ * Flows that need DCR or HTTP Basic auth on the token endpoint use
50
+ * `createDcrProvider` (or implement the `AuthProvider` interface directly).
40
51
  */
41
52
  export declare function createPkceProvider<TAccount extends AuthAccount>(options: PkceProviderOptions<TAccount>): AuthProvider<TAccount>;
42
53
  //# sourceMappingURL=pkce.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/pkce.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACR,WAAW,EACX,YAAY,EAKZ,aAAa,EAChB,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,MAAM,cAAc,GACpB,MAAM,GACN,CAAC,CAAC,GAAG,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,KAAK,MAAM,CAAC,CAAA;AAE/F,MAAM,MAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IAC1E,qGAAqG;IACrG,YAAY,EAAE,cAAc,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,EAAE,cAAc,CAAA;IACxB,mFAAmF;IACnF,QAAQ,EAAE,cAAc,CAAA;IACxB,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0FAA0F;IAC1F,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrD,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,WAAW,EAC3D,OAAO,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GACvC,YAAY,CAAC,QAAQ,CAAC,CA6GxB"}
1
+ {"version":3,"file":"pkce.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/pkce.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACR,WAAW,EACX,YAAY,EAMZ,aAAa,EAChB,MAAM,aAAa,CAAA;AAepB;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GACpB,MAAM,GACN,CAAC,CAAC,GAAG,EAAE;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACjC,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;AAErC,uEAAuE;AACvE,MAAM,MAAM,eAAe,GAAG,cAAc,CAAA;AAE5C,MAAM,MAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IAC1E,qGAAqG;IACrG,YAAY,EAAE,cAAc,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,EAAE,cAAc,CAAA;IACxB,mFAAmF;IACnF,QAAQ,EAAE,cAAc,CAAA;IACxB,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,kBAAkB;IAClB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0FAA0F;IAC1F,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrD;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAC3B,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,WAAW,EAC3D,OAAO,EAAE,mBAAmB,CAAC,QAAQ,CAAC,GACvC,YAAY,CAAC,QAAQ,CAAC,CAqJxB"}
@@ -1,5 +1,10 @@
1
1
  import { CliError, getErrorMessage } from '../../errors.js';
2
2
  import { deriveChallenge, generateVerifier } from '../pkce.js';
3
+ import { buildAuthError, buildPkceAuthorizeUrl, expiresAtFromExpiresIn, loadOauth4webapi, postTokenEndpoint, resolve, } from './oauth.js';
4
+ // Upper bound on the refresh-token POST. Kept under the refresh helper's
5
+ // stale-lock threshold so a timed-out grant releases the lock before another
6
+ // invocation would consider it abandoned.
7
+ const REFRESH_TIMEOUT_MS = 10_000;
3
8
  /**
4
9
  * Build an `AuthProvider` for the standard "PKCE S256, public client (no
5
10
  * client_secret)" flow. Covers Outline (user-supplied client_id + base_url)
@@ -10,8 +15,8 @@ import { deriveChallenge, generateVerifier } from '../pkce.js';
10
15
  * `runOAuthFlow` and arrives on `AuthorizeInput.scopes`; this factory does
11
16
  * not own scope resolution.
12
17
  *
13
- * Flows that need DCR or HTTP Basic auth on the token endpoint implement
14
- * the `AuthProvider` interface directly.
18
+ * Flows that need DCR or HTTP Basic auth on the token endpoint use
19
+ * `createDcrProvider` (or implement the `AuthProvider` interface directly).
15
20
  */
16
21
  export function createPkceProvider(options) {
17
22
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -23,20 +28,22 @@ export function createPkceProvider(options) {
23
28
  length: options.verifierLength,
24
29
  });
25
30
  const challenge = deriveChallenge(verifier);
26
- const clientId = resolve(options.clientId, input.handshake, input.flags);
27
- const authorizeUrl = resolve(options.authorizeUrl, input.handshake, input.flags);
28
- const url = new URL(authorizeUrl);
29
- url.searchParams.set('response_type', 'code');
30
- url.searchParams.set('client_id', clientId);
31
- url.searchParams.set('redirect_uri', input.redirectUri);
32
- url.searchParams.set('state', input.state);
33
- url.searchParams.set('code_challenge', challenge);
34
- url.searchParams.set('code_challenge_method', 'S256');
35
- if (input.scopes.length > 0) {
36
- url.searchParams.set('scope', input.scopes.join(scopeSeparator));
37
- }
31
+ // Resolve concurrently — both may be async (config read / prompt).
32
+ const [clientId, authorizeBaseUrl] = await Promise.all([
33
+ resolve(options.clientId, input.handshake, input.flags),
34
+ resolve(options.authorizeUrl, input.handshake, input.flags),
35
+ ]);
36
+ const authorizeUrl = buildPkceAuthorizeUrl({
37
+ authorizeUrl: authorizeBaseUrl,
38
+ clientId,
39
+ redirectUri: input.redirectUri,
40
+ state: input.state,
41
+ scopes: input.scopes,
42
+ scopeSeparator,
43
+ codeChallenge: challenge,
44
+ });
38
45
  return {
39
- authorizeUrl: url.toString(),
46
+ authorizeUrl,
40
47
  handshake: { ...input.handshake, codeVerifier: verifier, clientId },
41
48
  };
42
49
  },
@@ -44,13 +51,13 @@ export function createPkceProvider(options) {
44
51
  const verifier = input.handshake.codeVerifier;
45
52
  const clientId = input.handshake.clientId;
46
53
  if (typeof verifier !== 'string' || typeof clientId !== 'string') {
47
- throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', 'Internal: PKCE handshake state lost between authorize and exchange.');
54
+ throw buildAuthError('AUTH_TOKEN_EXCHANGE_FAILED', 'Internal: PKCE handshake state lost between authorize and exchange.', options.errorHints);
48
55
  }
49
56
  // `runOAuthFlow` folds the runtime `flags` into the handshake
50
57
  // before calling exchange, so a `tokenUrl: ({ flags }) => ...`
51
58
  // resolver sees the same flags it saw during authorize.
52
59
  const flags = input.handshake.flags ?? {};
53
- const tokenUrl = resolve(options.tokenUrl, input.handshake, flags);
60
+ const tokenUrl = await resolve(options.tokenUrl, input.handshake, flags);
54
61
  const body = new URLSearchParams({
55
62
  grant_type: 'authorization_code',
56
63
  code: input.code,
@@ -58,57 +65,79 @@ export function createPkceProvider(options) {
58
65
  client_id: clientId,
59
66
  code_verifier: verifier,
60
67
  });
61
- let response;
62
- try {
63
- response = await fetchImpl(tokenUrl, {
64
- method: 'POST',
65
- headers: {
66
- 'Content-Type': 'application/x-www-form-urlencoded',
67
- Accept: 'application/json',
68
- },
69
- body: body.toString(),
70
- });
71
- }
72
- catch (error) {
73
- throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', `Token endpoint request failed: ${getErrorMessage(error)}`);
74
- }
75
- if (!response.ok) {
76
- const detail = await safeReadText(response);
77
- throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', `Token endpoint returned HTTP ${response.status}.`, detail ? { hints: [detail] } : {});
78
- }
79
- // Parse defensively — a misconfigured proxy can return a 2xx HTML
80
- // error page that would otherwise blow up with a raw SyntaxError.
81
- let payload;
68
+ const result = await postTokenEndpoint({
69
+ url: tokenUrl,
70
+ body,
71
+ errorHints: options.errorHints,
72
+ fetchImpl,
73
+ });
74
+ return {
75
+ accessToken: result.accessToken,
76
+ refreshToken: result.refreshToken,
77
+ expiresAt: result.expiresAt,
78
+ };
79
+ },
80
+ validateToken: options.validate,
81
+ async refreshToken(input) {
82
+ const oauth = await loadOauth4webapi({
83
+ code: 'AUTH_REFRESH_UNAVAILABLE',
84
+ missingMessage: 'oauth4webapi is required for refresh-token support.',
85
+ missingHints: ['Run `npm install oauth4webapi` in your CLI.'],
86
+ });
87
+ // Mirror `exchangeCode`: a resolver that reads `flags` sees the
88
+ // same view during silent refresh as it did at authorize time.
89
+ const flags = input.handshake.flags ?? {};
90
+ const [tokenUrl, clientId] = await Promise.all([
91
+ resolve(options.tokenUrl, input.handshake, flags),
92
+ resolve(options.clientId, input.handshake, flags),
93
+ ]);
94
+ const as = { issuer: tokenUrl, token_endpoint: tokenUrl };
95
+ const client = { client_id: clientId, token_endpoint_auth_method: 'none' };
96
+ // Bound the network call so a hung token endpoint can't block the
97
+ // CLI indefinitely (and, for refresh consumers, can't hold the
98
+ // refresh lock forever). Route through the consumer's injected
99
+ // fetch when present, so a custom transport (proxy dispatcher,
100
+ // decompression) applies to the refresh grant too — oauth4webapi
101
+ // otherwise captures the global `fetch`.
102
+ const requestOptions = {
103
+ signal: AbortSignal.timeout(REFRESH_TIMEOUT_MS),
104
+ ...(options.fetchImpl ? { [oauth.customFetch]: options.fetchImpl } : {}),
105
+ };
82
106
  try {
83
- payload = (await response.json());
107
+ const response = await oauth.refreshTokenGrantRequest(as, client, oauth.None(), input.refreshToken, requestOptions);
108
+ const result = await oauth.processRefreshTokenResponse(as, client, response);
109
+ return {
110
+ accessToken: result.access_token,
111
+ refreshToken: result.refresh_token,
112
+ expiresAt: expiresAtFromExpiresIn(result.expires_in),
113
+ };
84
114
  }
85
115
  catch (error) {
86
- throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', `Token endpoint returned non-JSON response: ${getErrorMessage(error)}`);
87
- }
88
- if (!payload.access_token) {
89
- throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', 'Token endpoint response missing access_token.');
116
+ // A ResponseBodyError carries the server's OAuth error JSON.
117
+ // `invalid_grant` (any status — some proxies remap 400 → 401)
118
+ // means the refresh token itself was rejected; re-login is the
119
+ // only recovery. Every other code is transient from cli-core's
120
+ // POV — but surface the actual `error`/`error_description` so a
121
+ // misconfigured server (e.g. `invalid_request: Missing
122
+ // client_secret`) is diagnosable rather than hidden behind
123
+ // oauth4webapi's generic "server responded with an error".
124
+ if (error instanceof oauth.ResponseBodyError) {
125
+ const detail = error.error_description
126
+ ? `${error.error} (${error.error_description})`
127
+ : error.error;
128
+ if (error.error === 'invalid_grant') {
129
+ throw new CliError('AUTH_REFRESH_EXPIRED', `Refresh token rejected: ${detail}`, {
130
+ hints: ['Re-run the login command to reauthorize.'],
131
+ });
132
+ }
133
+ throw new CliError('AUTH_REFRESH_TRANSIENT', `Refresh request failed: ${detail}`, {
134
+ hints: ['Try again.'],
135
+ });
136
+ }
137
+ // Network failure, non-JSON body, WWWAuthenticateChallengeError, …
138
+ throw new CliError('AUTH_REFRESH_TRANSIENT', `Refresh request failed: ${getErrorMessage(error)}`, { hints: ['Try again.'] });
90
139
  }
91
- return {
92
- accessToken: payload.access_token,
93
- refreshToken: payload.refresh_token,
94
- expiresAt: typeof payload.expires_in === 'number'
95
- ? Date.now() + payload.expires_in * 1000
96
- : undefined,
97
- };
98
140
  },
99
- validateToken: options.validate,
100
141
  };
101
142
  }
102
- function resolve(resolver, handshake, flags) {
103
- return typeof resolver === 'function' ? resolver({ handshake, flags }) : resolver;
104
- }
105
- async function safeReadText(response) {
106
- try {
107
- const text = (await response.text()).trim();
108
- return text.length > 0 ? text : undefined;
109
- }
110
- catch {
111
- return undefined;
112
- }
113
- }
114
143
  //# sourceMappingURL=pkce.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/providers/pkce.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/providers/pkce.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import type { AccountRef, AuthAccount, AuthProvider, TokenBundle, TokenStore } from './types.js';
2
+ export type RefreshAccessTokenOptions<TAccount extends AuthAccount> = {
3
+ store: TokenStore<TAccount>;
4
+ provider: AuthProvider<TAccount>;
5
+ ref?: AccountRef;
6
+ /**
7
+ * Refresh proactively when the access token's expiry is within this many
8
+ * ms of now. Default 60_000 (60s). Ignored when `force: true`.
9
+ */
10
+ skewMs?: number;
11
+ /**
12
+ * Reactive path: caller hit a 401 and wants a rotation regardless of
13
+ * expiry. Skips the skew check; still honours all the "unavailable"
14
+ * gates (no refresh token, no provider hook, no `activeBundle`/`setBundle`).
15
+ */
16
+ force?: boolean;
17
+ /**
18
+ * Path to the O_EXCL concurrency lock file. Required — cli-core does not
19
+ * interpret `~` or know where the consumer's config lives. Recommended:
20
+ * `${getConfigPath(serviceName)}.refresh.lock`.
21
+ */
22
+ lockPath: string;
23
+ /**
24
+ * Forwarded to `provider.refreshToken` as its `handshake`, so consumers
25
+ * can pass runtime context the provider's resolvers need (e.g. a
26
+ * `--env`-derived base URL / client id). Defaults to `{}`.
27
+ */
28
+ handshake?: Record<string, unknown>;
29
+ };
30
+ export type RefreshAccessTokenResult<TAccount extends AuthAccount> = {
31
+ rotated: boolean;
32
+ bundle: TokenBundle;
33
+ account: TAccount;
34
+ };
35
+ /**
36
+ * Rotate the access token using the stored refresh token. Proactive when
37
+ * `accessTokenExpiresAt` is within `skewMs` of now; reactive when `force:
38
+ * true`. Uses an `O_EXCL` file lock at `lockPath` so concurrent CLI
39
+ * invocations don't issue parallel refresh-token grants — one POSTs, the
40
+ * others re-read the rotated bundle from the store.
41
+ *
42
+ * Throws `AUTH_REFRESH_UNAVAILABLE` when refresh isn't possible in the
43
+ * current setup: store doesn't implement `activeBundle` + `setBundle`,
44
+ * provider doesn't implement `refreshToken`, no credential, or no refresh
45
+ * token. Server-side rejections surface as `AUTH_REFRESH_EXPIRED` (re-login
46
+ * required) or `AUTH_REFRESH_TRANSIENT` (retryable).
47
+ */
48
+ export declare function refreshAccessToken<TAccount extends AuthAccount>(options: RefreshAccessTokenOptions<TAccount>): Promise<RefreshAccessTokenResult<TAccount>>;
49
+ //# sourceMappingURL=refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/auth/refresh.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEhG,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,WAAW,IAAI;IAClE,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAA;IAC3B,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;IAChC,GAAG,CAAC,EAAE,UAAU,CAAA;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACtC,CAAA;AAED,MAAM,MAAM,wBAAwB,CAAC,QAAQ,SAAS,WAAW,IAAI;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,EAAE,QAAQ,CAAA;CACpB,CAAA;AAUD;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CAAC,QAAQ,SAAS,WAAW,EACjE,OAAO,EAAE,yBAAyB,CAAC,QAAQ,CAAC,GAC7C,OAAO,CAAC,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CA4F7C"}