@doist/cli-core 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +45 -18
- package/dist/auth/errors.d.ts +1 -1
- package/dist/auth/errors.d.ts.map +1 -1
- 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/providers/dcr.d.ts +72 -0
- package/dist/auth/providers/dcr.d.ts.map +1 -0
- package/dist/auth/providers/dcr.js +204 -0
- package/dist/auth/providers/dcr.js.map +1 -0
- package/dist/auth/providers/oauth.d.ts +105 -0
- package/dist/auth/providers/oauth.d.ts.map +1 -0
- package/dist/auth/providers/oauth.js +145 -0
- package/dist/auth/providers/oauth.js.map +1 -0
- package/dist/auth/providers/pkce.d.ts +15 -4
- package/dist/auth/providers/pkce.d.ts.map +1 -1
- package/dist/auth/providers/pkce.js +30 -83
- package/dist/auth/providers/pkce.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [0.20.1](https://github.com/Doist/cli-core/compare/v0.20.0...v0.20.1) (2026-05-21)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
* **auth:** send DCR HTTP Basic credentials raw, not form-url-encoded ([#43](https://github.com/Doist/cli-core/issues/43)) ([97a626a](https://github.com/Doist/cli-core/commit/97a626a69a52761117e70fccc7049d24ad001937))
|
|
6
|
+
|
|
7
|
+
## [0.20.0](https://github.com/Doist/cli-core/compare/v0.19.0...v0.20.0) (2026-05-21)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **auth:** add createDcrProvider for RFC 7591 dynamic client registration ([#31](https://github.com/Doist/cli-core/issues/31)) ([dd34551](https://github.com/Doist/cli-core/commit/dd34551d7f27df83ecd4a0aa888ec079af2007a0))
|
|
12
|
+
|
|
1
13
|
## [0.19.0](https://github.com/Doist/cli-core/compare/v0.18.0...v0.19.0) (2026-05-21)
|
|
2
14
|
|
|
3
15
|
### Features
|
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`, `refreshAccessToken`, `createPkceProvider`, `createSecureStore`, `createKeyringTokenStore`, `migrateLegacyAuth`, `persistBundle`, `bundleFromExchange`, PKCE helpers, `AuthProvider` / `TokenStore` / `TokenBundle` / `ActiveBundleSnapshot` / `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
|
|
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`, `refreshAccessToken`, `createPkceProvider`, `createDcrProvider`, `createSecureStore`, `createKeyringTokenStore`, `migrateLegacyAuth`, `persistBundle`, `bundleFromExchange`, PKCE helpers, `AuthProvider` / `TokenStore` / `TokenBundle` / `ActiveBundleSnapshot` / `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`), the RFC 7591 Dynamic Client Registration flow (`createDcrProvider`), 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 fully bespoke backends (device code, magic-link, …). `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), `@napi-rs/keyring` (when using `createSecureStore` or the keyring `TokenStore`), and `oauth4webapi` (when a consumer opts into silent refresh or uses `createDcrProvider`) 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
|
|
|
@@ -124,7 +124,7 @@ The semver helpers (`parseVersion`, `compareVersions`, `isNewer`, `getInstallTag
|
|
|
124
124
|
|
|
125
125
|
### Auth (optional subpath)
|
|
126
126
|
|
|
127
|
-
Wire `<cli> [auth] login` and the supporting OAuth runtime. cli-core ships the standard public-client PKCE flow (`createPkceProvider`) and the `attachLoginCommand` Commander helper that drives `runOAuthFlow` end-to-end.
|
|
127
|
+
Wire `<cli> [auth] login` and the supporting OAuth runtime. cli-core ships the standard public-client PKCE flow (`createPkceProvider`), the RFC 7591 Dynamic Client Registration flow (`createDcrProvider`), and the `attachLoginCommand` Commander helper that drives `runOAuthFlow` end-to-end. Other bespoke flows (device code, magic link, username / password) implement the `AuthProvider` interface directly — no cli-core release needed. Token storage is a `TokenStore` the consumer provides; cli-core does not ship a default.
|
|
128
128
|
|
|
129
129
|
#### Install
|
|
130
130
|
|
|
@@ -171,6 +171,32 @@ attachLoginCommand<Account>(auth, {
|
|
|
171
171
|
|
|
172
172
|
The `authorizeUrl` / `tokenUrl` / `clientId` resolvers may return `string` **or** `Promise<string>` — so a consumer can resolve the base URL or client id asynchronously (reading config, prompting the user) without abandoning `createPkceProvider`. An injected `fetchImpl` is used for the token exchange **and** the refresh grant (threaded into `oauth4webapi` via its `customFetch`), so a custom transport — proxy dispatcher, decompression — applies on every OAuth call rather than being bypassed by the library's global `fetch`.
|
|
173
173
|
|
|
174
|
+
#### Quick start (Dynamic Client Registration)
|
|
175
|
+
|
|
176
|
+
For providers that issue per-install `client_id` / `client_secret` via [RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591). `createDcrProvider` registers in `prepare()`, then drives the standard PKCE authorize / token-exchange dance against the resulting client. Registration and token exchange run through [`oauth4webapi`](https://github.com/panva/oauth4webapi) (the same optional peer dep PKCE refresh uses — `npm install oauth4webapi`), so endpoints must be HTTPS and the registration endpoint must return RFC 7591-conformant `201` responses.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
import { attachLoginCommand, createDcrProvider } from '@doist/cli-core/auth'
|
|
180
|
+
|
|
181
|
+
const provider = createDcrProvider<Account>({
|
|
182
|
+
registrationUrl: 'https://example.com/oauth/register',
|
|
183
|
+
authorizeUrl: 'https://example.com/oauth/authorize',
|
|
184
|
+
tokenUrl: 'https://example.com/oauth/token',
|
|
185
|
+
clientMetadata: {
|
|
186
|
+
clientName: 'Example CLI',
|
|
187
|
+
clientUri: 'https://github.com/example/cli',
|
|
188
|
+
logoUri: 'https://example.com/logo.png',
|
|
189
|
+
applicationType: 'native',
|
|
190
|
+
tokenEndpointAuthMethod: 'client_secret_basic', // default
|
|
191
|
+
},
|
|
192
|
+
validate: async ({ token }) => probeUser(token),
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
The DCR-issued `client_id` (and `client_secret`, if returned) are stashed in the handshake and threaded through the rest of the flow. The server-returned `token_endpoint_auth_method` is authoritative (RFC 7591 §3.2.1) and overrides the configured one. By default the token exchange uses `Authorization: Basic` with each credential component percent-encoded via `encodeURIComponent` (RFC 3986) rather than oauth4webapi's stricter RFC 6749 §2.3.1 form-url-encoding. Both escape genuinely reserved chars (`:` / `%` / `+` / `/`) so a conformant server reconstructs them, but §2.3.1 _also_ escapes the unreserved `-` `_` `.` `~` — which breaks servers that don't url-decode the Basic credential (a DCR-issued `twd_…` would arrive as `twd%5F…` and miss the lookup). Pass `tokenEndpointAuthMethod: 'client_secret_post'` to send credentials in the body instead, or `'none'` for a public-client registration. When the registration response carries no `client_secret`, the exchange falls back to a public-client POST regardless of the requested method. Any extra registration metadata (e.g. `software_statement`) goes in `clientMetadata.extra`. cli-core does **not** cache the registered client — each login mints a fresh one.
|
|
197
|
+
|
|
198
|
+
Both `createPkceProvider` and `createDcrProvider` accept an optional `errorHints: string[]` that is prepended to every `CliError` they throw. Use it for CLI-specific remediation that should accompany every auth failure (e.g. `['Try again: tw auth login', 'Or set TWIST_API_TOKEN environment variable']`). Server-returned response bodies (for non-2xx replies) are appended after the user hints so the actionable hint stays at the top.
|
|
199
|
+
|
|
174
200
|
#### Sibling attachers (`logout` / `status` / `token`)
|
|
175
201
|
|
|
176
202
|
The same registrar shape covers the other three auth subcommands. Each returns the new `Command` for chaining and shares the same `TokenStore<TAccount>` instance.
|
|
@@ -310,7 +336,7 @@ Error contract:
|
|
|
310
336
|
- `AUTH_REFRESH_TRANSIENT` — 5xx, network, non-JSON body, lock timeout. Caller may retry.
|
|
311
337
|
- `AUTH_REFRESH_UNAVAILABLE` — refresh isn't possible in the current setup: no refresh token stored, the store doesn't implement **both** `activeBundle` and `setBundle` (a full bundle must be readable and persistable), the credential was removed mid-refresh, the provider doesn't implement `refreshToken`, or the optional `oauth4webapi` peer dep isn't installed.
|
|
312
338
|
|
|
313
|
-
The PKCE provider (`createPkceProvider`) implements `refreshToken` via the [`oauth4webapi`](https://github.com/panva/oauth4webapi) library, declared as an **optional peer dependency** — only CLIs that opt into refresh need to install it (`npm install oauth4webapi`). Providers built directly against the `AuthProvider` interface (e.g.
|
|
339
|
+
The PKCE provider (`createPkceProvider`) implements `refreshToken` via the [`oauth4webapi`](https://github.com/panva/oauth4webapi) library, declared as an **optional peer dependency** — only CLIs that opt into refresh or use `createDcrProvider` need to install it (`npm install oauth4webapi`). Providers built directly against the `AuthProvider` interface (e.g. device code) implement the `refreshToken?` hook themselves; the storage and helper contract is identical.
|
|
314
340
|
|
|
315
341
|
#### Keyring primitive (`createSecureStore`)
|
|
316
342
|
|
|
@@ -447,9 +473,9 @@ Account-selection resolvers (env > `--user` > default > single-only > error), `a
|
|
|
447
473
|
|
|
448
474
|
A `TokenStore` MAY throw `CliError('AUTH_STORE_READ_FAILED', …)` from `active(ref)` when a matching record exists but the token itself can't be read (e.g. an OS keyring backing the store is offline). `attachLogoutCommand` catches this specific code on the explicit-ref path and proceeds with `clear(ref)` — local logout doesn't need the token, and the `revokeToken` hook is skipped because there's no token to send. Every other error from `active(ref)` (notably `ACCOUNT_NOT_FOUND` from a genuine ref miss, plus any consumer-thrown code) still propagates so a real miss isn't masked. Without `--user`, the logout pre-flight swallows any snapshot read failure so the local clear always runs. `attachStatusCommand` and `attachTokenViewCommand` propagate `AUTH_STORE_READ_FAILED` since they have no way to render or print without the token.
|
|
449
475
|
|
|
450
|
-
#### Custom `AuthProvider` (non-PKCE flows)
|
|
476
|
+
#### Custom `AuthProvider` (non-PKCE, non-DCR flows)
|
|
451
477
|
|
|
452
|
-
Implement `AuthProvider` directly for
|
|
478
|
+
Implement `AuthProvider` directly for device code, magic-link, username / password, or any other flow not covered by `createPkceProvider` / `createDcrProvider`. The four hooks fire in this order during `runOAuthFlow`:
|
|
453
479
|
|
|
454
480
|
| Hook | When | Purpose |
|
|
455
481
|
| --------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
@@ -493,6 +519,7 @@ Every failure in this subpath surfaces as a `CliError`:
|
|
|
493
519
|
| `AUTH_OAUTH_FAILED` | Provider returned `?error=...`, the flow was aborted via `signal`, or the callback server stopped before completion. |
|
|
494
520
|
| `AUTH_CALLBACK_TIMEOUT` | No valid callback within `timeoutMs` (default 3 minutes). |
|
|
495
521
|
| `AUTH_PORT_BIND_FAILED` | Could not bind any port in `[preferredPort, preferredPort + portFallbackCount]`, or `--callback-port` was out of range. |
|
|
522
|
+
| `AUTH_DCR_FAILED` | `createDcrProvider` registration failed (network error, non-`201`, non-JSON body, response missing `client_id`, or the `oauth4webapi` peer dep isn't installed). |
|
|
496
523
|
| `AUTH_TOKEN_EXCHANGE_FAILED` | Token endpoint network error, non-2xx response, non-JSON body, or missing `access_token`. |
|
|
497
524
|
| `AUTH_STORE_WRITE_FAILED` | `TokenStore.set` threw a non-`CliError`. (`CliError`s thrown from `set` propagate unchanged.) |
|
|
498
525
|
| `NOT_AUTHENTICATED` | `status` / `token` ran with an empty `TokenStore` (and no `onNotAuthenticated` callback for `status`). Default message: `'Not signed in.'`. |
|
package/dist/auth/errors.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* aggregator in `../errors.ts` so consumers don't have to redeclare them in
|
|
4
4
|
* their own `TCode` union when catching.
|
|
5
5
|
*/
|
|
6
|
-
export type AuthErrorCode = 'AUTH_OAUTH_FAILED' | 'AUTH_CALLBACK_TIMEOUT' | 'AUTH_PORT_BIND_FAILED' | 'AUTH_TOKEN_EXCHANGE_FAILED' | 'AUTH_STORE_WRITE_FAILED' | 'AUTH_STORE_READ_FAILED' | 'AUTH_REFRESH_EXPIRED' | 'AUTH_REFRESH_TRANSIENT' | 'AUTH_REFRESH_UNAVAILABLE' | 'NOT_AUTHENTICATED' | 'TOKEN_FROM_ENV' | 'NO_ACCOUNT_SELECTED' | 'ACCOUNT_NOT_FOUND';
|
|
6
|
+
export type AuthErrorCode = 'AUTH_OAUTH_FAILED' | 'AUTH_CALLBACK_TIMEOUT' | 'AUTH_PORT_BIND_FAILED' | 'AUTH_DCR_FAILED' | 'AUTH_TOKEN_EXCHANGE_FAILED' | 'AUTH_STORE_WRITE_FAILED' | 'AUTH_STORE_READ_FAILED' | 'AUTH_REFRESH_EXPIRED' | 'AUTH_REFRESH_TRANSIENT' | 'AUTH_REFRESH_UNAVAILABLE' | 'NOT_AUTHENTICATED' | 'TOKEN_FROM_ENV' | 'NO_ACCOUNT_SELECTED' | 'ACCOUNT_NOT_FOUND';
|
|
7
7
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACnB,mBAAmB,GACnB,uBAAuB,GACvB,uBAAuB,GACvB,4BAA4B,GAC5B,yBAAyB,GACzB,wBAAwB,GACxB,sBAAsB,GACtB,wBAAwB,GACxB,0BAA0B,GAC1B,mBAAmB,GACnB,gBAAgB,GAChB,qBAAqB,GACrB,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/auth/errors.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GACnB,mBAAmB,GACnB,uBAAuB,GACvB,uBAAuB,GACvB,iBAAiB,GACjB,4BAA4B,GAC5B,yBAAyB,GACzB,wBAAwB,GACxB,sBAAsB,GACtB,wBAAwB,GACxB,0BAA0B,GAC1B,mBAAmB,GACnB,gBAAgB,GAChB,qBAAqB,GACrB,mBAAmB,CAAA"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -14,9 +14,11 @@ export type { GenerateVerifierOptions } from './pkce.js';
|
|
|
14
14
|
export { bundleFromExchange, persistBundle } from './persist.js';
|
|
15
15
|
export type { PersistBundleOptions } from './persist.js';
|
|
16
16
|
export { createPkceProvider } from './providers/pkce.js';
|
|
17
|
-
export type { PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
|
|
17
|
+
export type { OAuthLazyString, PkceLazyString, PkceProviderOptions } from './providers/pkce.js';
|
|
18
18
|
export { refreshAccessToken } from './refresh.js';
|
|
19
19
|
export type { RefreshAccessTokenOptions, RefreshAccessTokenResult } from './refresh.js';
|
|
20
|
+
export { createDcrProvider } from './providers/dcr.js';
|
|
21
|
+
export type { DcrClientMetadata, DcrProviderOptions, DcrTokenEndpointAuthMethod, } from './providers/dcr.js';
|
|
20
22
|
export type { AccountRef, ActiveBundleSnapshot, AuthAccount, AuthorizeInput, AuthorizeResult, AuthProvider, ExchangeInput, ExchangeResult, PrepareInput, PrepareResult, RefreshInput, TokenBundle, TokenStore, ValidateInput, } from './types.js';
|
|
21
23
|
export { SecureStoreUnavailableError, createKeyringTokenStore, createSecureStore, migrateLegacyAuth, } from './keyring/index.js';
|
|
22
24
|
export type { CreateKeyringTokenStoreOptions, CreateSecureStoreOptions, KeyringTokenStore, MigrateAuthResult, MigrateLegacyAuthOptions, MigrateSkipReason, SecureStore, TokenStorageLocation, TokenStorageResult, UserRecord, UserRecordStore, } from './keyring/index.js';
|
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,aAAa,EAAE,MAAM,cAAc,CAAA;AAChE,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;
|
|
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,aAAa,EAAE,MAAM,cAAc,CAAA;AAChE,YAAY,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AACxD,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC/F,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,YAAY,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAA;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AACtD,YAAY,EACR,iBAAiB,EACjB,kBAAkB,EAClB,0BAA0B,GAC7B,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACR,UAAU,EACV,oBAAoB,EACpB,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
|
@@ -7,5 +7,6 @@ export { DEFAULT_VERIFIER_ALPHABET, deriveChallenge, generateState, generateVeri
|
|
|
7
7
|
export { bundleFromExchange, persistBundle } from './persist.js';
|
|
8
8
|
export { createPkceProvider } from './providers/pkce.js';
|
|
9
9
|
export { refreshAccessToken } from './refresh.js';
|
|
10
|
+
export { createDcrProvider } from './providers/dcr.js';
|
|
10
11
|
export { SecureStoreUnavailableError, createKeyringTokenStore, createSecureStore, migrateLegacyAuth, } from './keyring/index.js';
|
|
11
12
|
//# 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,aAAa,EAAE,MAAM,cAAc,CAAA;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,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,kBAAkB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEhE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAA;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAEjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAsBtD,OAAO,EACH,2BAA2B,EAC3B,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,GACpB,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AuthAccount, AuthProvider, ValidateInput } from '../types.js';
|
|
2
|
+
import type { OAuthLazyString } from './pkce.js';
|
|
3
|
+
export type DcrTokenEndpointAuthMethod = 'client_secret_basic' | 'client_secret_post' | 'none';
|
|
4
|
+
/**
|
|
5
|
+
* RFC 7591 Dynamic Client Registration metadata POSTed to the registration
|
|
6
|
+
* endpoint. Only fields the CLI typically cares about are named; pass anything
|
|
7
|
+
* else (`software_statement`, `jwks`, …) via `extra`.
|
|
8
|
+
*/
|
|
9
|
+
export type DcrClientMetadata = {
|
|
10
|
+
clientName: string;
|
|
11
|
+
clientUri?: string;
|
|
12
|
+
logoUri?: string;
|
|
13
|
+
applicationType?: 'native' | 'web';
|
|
14
|
+
/**
|
|
15
|
+
* Requested token-endpoint auth method. Defaults to `'client_secret_basic'`.
|
|
16
|
+
* The registration response is authoritative per RFC 7591 §3.2.1 — when
|
|
17
|
+
* the server returns its own `token_endpoint_auth_method`, that value
|
|
18
|
+
* wins over this configured one.
|
|
19
|
+
*/
|
|
20
|
+
tokenEndpointAuthMethod?: DcrTokenEndpointAuthMethod;
|
|
21
|
+
/** Defaults to `['authorization_code']`. */
|
|
22
|
+
grantTypes?: string[];
|
|
23
|
+
/** Defaults to `['code']`. */
|
|
24
|
+
responseTypes?: string[];
|
|
25
|
+
/** Merged verbatim into the registration POST body. */
|
|
26
|
+
extra?: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
export type DcrProviderOptions<TAccount extends AuthAccount = AuthAccount> = {
|
|
29
|
+
/** RFC 7591 registration endpoint. Function form supports per-flow base URLs. */
|
|
30
|
+
registrationUrl: OAuthLazyString;
|
|
31
|
+
/** OAuth 2.0 authorize endpoint. */
|
|
32
|
+
authorizeUrl: OAuthLazyString;
|
|
33
|
+
/** OAuth 2.0 token endpoint. */
|
|
34
|
+
tokenUrl: OAuthLazyString;
|
|
35
|
+
clientMetadata: DcrClientMetadata;
|
|
36
|
+
/** How to join scopes in the authorize URL. Default `' '` (RFC 6749). */
|
|
37
|
+
scopeSeparator?: string;
|
|
38
|
+
verifierAlphabet?: string;
|
|
39
|
+
/** Default 64. */
|
|
40
|
+
verifierLength?: number;
|
|
41
|
+
/** Probe an authenticated endpoint to confirm the token works and resolve the account. */
|
|
42
|
+
validate: (input: ValidateInput) => Promise<TAccount>;
|
|
43
|
+
/**
|
|
44
|
+
* User-facing remediation hints attached to every CliError this factory
|
|
45
|
+
* throws (`AUTH_DCR_FAILED` from `prepare()` / `authorize()` and
|
|
46
|
+
* `AUTH_TOKEN_EXCHANGE_FAILED` from `exchangeCode()`). Server-returned
|
|
47
|
+
* error details are appended after these so the actionable hint stays
|
|
48
|
+
* first.
|
|
49
|
+
*/
|
|
50
|
+
errorHints?: string[];
|
|
51
|
+
/** Inject a fetch implementation (tests / custom transport). */
|
|
52
|
+
fetchImpl?: typeof fetch;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Build an `AuthProvider` for the RFC 7591 Dynamic Client Registration flow,
|
|
56
|
+
* driven by [`oauth4webapi`](https://github.com/panva/oauth4webapi) (an
|
|
57
|
+
* optional peer dep — installed only by DCR/refresh consumers).
|
|
58
|
+
*
|
|
59
|
+
* - `prepare`: register via `dynamicClientRegistrationRequest`. Stash the
|
|
60
|
+
* issued `client_id`, optional `client_secret`, and the server-returned
|
|
61
|
+
* `token_endpoint_auth_method` (RFC 7591 §3.2.1 — server is authoritative)
|
|
62
|
+
* in the handshake.
|
|
63
|
+
* - `authorize`: standard PKCE S256 with `client_id` read from the handshake.
|
|
64
|
+
* - `exchangeCode`: `authorizationCodeGrantRequest` authenticated per the
|
|
65
|
+
* handshake's server-returned auth method (falling back to the configured
|
|
66
|
+
* one) — HTTP Basic (RFC 3986-encoded, see `clientSecretBasicRfc3986`),
|
|
67
|
+
* client-secret POST, or public-client `None` (the last also when the
|
|
68
|
+
* registration response carried no `client_secret`).
|
|
69
|
+
* - `validateToken`: caller-supplied.
|
|
70
|
+
*/
|
|
71
|
+
export declare function createDcrProvider<TAccount extends AuthAccount>(options: DcrProviderOptions<TAccount>): AuthProvider<TAccount>;
|
|
72
|
+
//# sourceMappingURL=dcr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dcr.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/dcr.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACR,WAAW,EACX,YAAY,EAOZ,aAAa,EAChB,MAAM,aAAa,CAAA;AAQpB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAEhD,MAAM,MAAM,0BAA0B,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,MAAM,CAAA;AAE9F;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,eAAe,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IAClC;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,0BAA0B,CAAA;IACpD,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,kBAAkB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IACzE,iFAAiF;IACjF,eAAe,EAAE,eAAe,CAAA;IAChC,oCAAoC;IACpC,YAAY,EAAE,eAAe,CAAA;IAC7B,gCAAgC;IAChC,QAAQ,EAAE,eAAe,CAAA;IACzB,cAAc,EAAE,iBAAiB,CAAA;IACjC,yEAAyE;IACzE,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;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAC3B,CAAA;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,SAAS,WAAW,EAC1D,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAAC,GACtC,YAAY,CAAC,QAAQ,CAAC,CAkLxB"}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { getErrorMessage } from '../../errors.js';
|
|
2
|
+
import { deriveChallenge, generateVerifier } from '../pkce.js';
|
|
3
|
+
import { buildAuthError, buildPkceAuthorizeUrl, expiresAtFromExpiresIn, loadOauth4webapi, resolve, } from './oauth.js';
|
|
4
|
+
const MISSING_PEER_HINTS = ['Run `npm install oauth4webapi` in your CLI.'];
|
|
5
|
+
const VALID_AUTH_METHODS = new Set([
|
|
6
|
+
'client_secret_basic',
|
|
7
|
+
'client_secret_post',
|
|
8
|
+
'none',
|
|
9
|
+
]);
|
|
10
|
+
/**
|
|
11
|
+
* Build an `AuthProvider` for the RFC 7591 Dynamic Client Registration flow,
|
|
12
|
+
* driven by [`oauth4webapi`](https://github.com/panva/oauth4webapi) (an
|
|
13
|
+
* optional peer dep — installed only by DCR/refresh consumers).
|
|
14
|
+
*
|
|
15
|
+
* - `prepare`: register via `dynamicClientRegistrationRequest`. Stash the
|
|
16
|
+
* issued `client_id`, optional `client_secret`, and the server-returned
|
|
17
|
+
* `token_endpoint_auth_method` (RFC 7591 §3.2.1 — server is authoritative)
|
|
18
|
+
* in the handshake.
|
|
19
|
+
* - `authorize`: standard PKCE S256 with `client_id` read from the handshake.
|
|
20
|
+
* - `exchangeCode`: `authorizationCodeGrantRequest` authenticated per the
|
|
21
|
+
* handshake's server-returned auth method (falling back to the configured
|
|
22
|
+
* one) — HTTP Basic (RFC 3986-encoded, see `clientSecretBasicRfc3986`),
|
|
23
|
+
* client-secret POST, or public-client `None` (the last also when the
|
|
24
|
+
* registration response carried no `client_secret`).
|
|
25
|
+
* - `validateToken`: caller-supplied.
|
|
26
|
+
*/
|
|
27
|
+
export function createDcrProvider(options) {
|
|
28
|
+
const scopeSeparator = options.scopeSeparator ?? ' ';
|
|
29
|
+
const configuredAuthMethod = options.clientMetadata.tokenEndpointAuthMethod ?? 'client_secret_basic';
|
|
30
|
+
return {
|
|
31
|
+
async prepare(input) {
|
|
32
|
+
const oauth = await loadOauth4webapi({
|
|
33
|
+
code: 'AUTH_DCR_FAILED',
|
|
34
|
+
missingMessage: 'oauth4webapi is required for Dynamic Client Registration.',
|
|
35
|
+
userHints: options.errorHints,
|
|
36
|
+
missingHints: MISSING_PEER_HINTS,
|
|
37
|
+
});
|
|
38
|
+
const registrationUrl = await resolve(options.registrationUrl, {}, input.flags);
|
|
39
|
+
const as = {
|
|
40
|
+
issuer: registrationUrl,
|
|
41
|
+
registration_endpoint: registrationUrl,
|
|
42
|
+
};
|
|
43
|
+
const metadata = buildRegistrationMetadata(options.clientMetadata, input.redirectUri, configuredAuthMethod);
|
|
44
|
+
let client;
|
|
45
|
+
try {
|
|
46
|
+
const response = await oauth.dynamicClientRegistrationRequest(as, metadata, customFetchOptions(oauth, options.fetchImpl));
|
|
47
|
+
client = await oauth.processDynamicClientRegistrationResponse(response);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw mapOauthError(error, oauth, 'AUTH_DCR_FAILED', 'Dynamic Client Registration failed.', options.errorHints);
|
|
51
|
+
}
|
|
52
|
+
const handshake = { clientId: client.client_id };
|
|
53
|
+
if (typeof client.client_secret === 'string') {
|
|
54
|
+
handshake.clientSecret = client.client_secret;
|
|
55
|
+
}
|
|
56
|
+
// Per RFC 7591 §3.2.1 the server's chosen method is authoritative.
|
|
57
|
+
// Honour a supported one; fail fast on a method we can't perform
|
|
58
|
+
// (e.g. `private_key_jwt`) rather than silently authenticating the
|
|
59
|
+
// token request with the wrong scheme.
|
|
60
|
+
const serverMethod = client.token_endpoint_auth_method;
|
|
61
|
+
if (typeof serverMethod === 'string') {
|
|
62
|
+
if (!VALID_AUTH_METHODS.has(serverMethod)) {
|
|
63
|
+
throw buildAuthError('AUTH_DCR_FAILED', `Registration server selected an unsupported token_endpoint_auth_method: ${serverMethod}.`, options.errorHints);
|
|
64
|
+
}
|
|
65
|
+
handshake.tokenEndpointAuthMethod = serverMethod;
|
|
66
|
+
}
|
|
67
|
+
return { handshake };
|
|
68
|
+
},
|
|
69
|
+
async authorize(input) {
|
|
70
|
+
const clientId = input.handshake.clientId;
|
|
71
|
+
if (typeof clientId !== 'string') {
|
|
72
|
+
throw buildAuthError('AUTH_DCR_FAILED', 'Internal: DCR handshake missing clientId before authorize.', options.errorHints);
|
|
73
|
+
}
|
|
74
|
+
const verifier = generateVerifier({
|
|
75
|
+
alphabet: options.verifierAlphabet,
|
|
76
|
+
length: options.verifierLength,
|
|
77
|
+
});
|
|
78
|
+
const challenge = deriveChallenge(verifier);
|
|
79
|
+
const authorizeUrl = buildPkceAuthorizeUrl({
|
|
80
|
+
authorizeUrl: await resolve(options.authorizeUrl, input.handshake, input.flags),
|
|
81
|
+
clientId,
|
|
82
|
+
redirectUri: input.redirectUri,
|
|
83
|
+
state: input.state,
|
|
84
|
+
scopes: input.scopes,
|
|
85
|
+
scopeSeparator,
|
|
86
|
+
codeChallenge: challenge,
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
authorizeUrl,
|
|
90
|
+
handshake: { ...input.handshake, codeVerifier: verifier },
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
async exchangeCode(input) {
|
|
94
|
+
const verifier = input.handshake.codeVerifier;
|
|
95
|
+
const clientId = input.handshake.clientId;
|
|
96
|
+
if (typeof verifier !== 'string' || typeof clientId !== 'string') {
|
|
97
|
+
throw buildAuthError('AUTH_TOKEN_EXCHANGE_FAILED', 'Internal: DCR handshake state lost between authorize and exchange.', options.errorHints);
|
|
98
|
+
}
|
|
99
|
+
const clientSecretRaw = input.handshake.clientSecret;
|
|
100
|
+
const clientSecret = typeof clientSecretRaw === 'string' ? clientSecretRaw : undefined;
|
|
101
|
+
const issuedMethodRaw = input.handshake.tokenEndpointAuthMethod;
|
|
102
|
+
const issuedMethod = typeof issuedMethodRaw === 'string' &&
|
|
103
|
+
VALID_AUTH_METHODS.has(issuedMethodRaw)
|
|
104
|
+
? issuedMethodRaw
|
|
105
|
+
: undefined;
|
|
106
|
+
// Server-issued method wins (RFC 7591 §3.2.1). Fall back to the
|
|
107
|
+
// configured one only when the server didn't echo a known method.
|
|
108
|
+
const effectiveAuthMethod = issuedMethod ?? configuredAuthMethod;
|
|
109
|
+
const oauth = await loadOauth4webapi({
|
|
110
|
+
code: 'AUTH_TOKEN_EXCHANGE_FAILED',
|
|
111
|
+
missingMessage: 'oauth4webapi is required for the DCR token exchange.',
|
|
112
|
+
userHints: options.errorHints,
|
|
113
|
+
missingHints: MISSING_PEER_HINTS,
|
|
114
|
+
});
|
|
115
|
+
const flags = input.handshake.flags ?? {};
|
|
116
|
+
const tokenUrl = await resolve(options.tokenUrl, input.handshake, flags);
|
|
117
|
+
const as = { issuer: tokenUrl, token_endpoint: tokenUrl };
|
|
118
|
+
const client = { client_id: clientId };
|
|
119
|
+
// Public-client fallback: a registration with no `client_secret`
|
|
120
|
+
// can't authenticate Basic/Post regardless of the requested method,
|
|
121
|
+
// so we POST `client_id` like a non-confidential client. Otherwise
|
|
122
|
+
// honour the effective auth method.
|
|
123
|
+
let clientAuth;
|
|
124
|
+
if (!clientSecret || effectiveAuthMethod === 'none') {
|
|
125
|
+
clientAuth = oauth.None();
|
|
126
|
+
}
|
|
127
|
+
else if (effectiveAuthMethod === 'client_secret_post') {
|
|
128
|
+
clientAuth = oauth.ClientSecretPost(clientSecret);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
clientAuth = clientSecretBasicRfc3986(clientSecret);
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
// The flow runtime owns CSRF state validation; skip oauth4webapi's
|
|
135
|
+
// own state check (it only brands the params for the grant call).
|
|
136
|
+
const callbackParameters = oauth.validateAuthResponse(as, client, new URLSearchParams({ code: input.code }), oauth.skipStateCheck);
|
|
137
|
+
const response = await oauth.authorizationCodeGrantRequest(as, client, clientAuth, callbackParameters, input.redirectUri, verifier, customFetchOptions(oauth, options.fetchImpl));
|
|
138
|
+
const result = await oauth.processAuthorizationCodeResponse(as, client, response);
|
|
139
|
+
return {
|
|
140
|
+
accessToken: result.access_token,
|
|
141
|
+
refreshToken: result.refresh_token,
|
|
142
|
+
expiresAt: expiresAtFromExpiresIn(result.expires_in),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
throw mapOauthError(error, oauth, 'AUTH_TOKEN_EXCHANGE_FAILED', 'Token exchange failed.', options.errorHints);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
validateToken: options.validate,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* HTTP Basic client auth that percent-encodes each credential component with
|
|
154
|
+
* `encodeURIComponent` (RFC 3986) rather than oauth4webapi's stricter RFC 6749
|
|
155
|
+
* §2.3.1 `application/x-www-form-urlencoded` form. Both escape the genuinely
|
|
156
|
+
* reserved chars (`:` `%` `+` `/` …) so a conformant server reconstructs them —
|
|
157
|
+
* but §2.3.1 *also* escapes the unreserved `-` `_` `.` `~`, which breaks servers
|
|
158
|
+
* that don't url-decode the Basic credential (a DCR-issued `twd_…` would arrive
|
|
159
|
+
* as `twd%5F…` and miss the lookup). Leaving those unreserved chars intact keeps
|
|
160
|
+
* such servers working while still transmitting reserved chars safely.
|
|
161
|
+
*/
|
|
162
|
+
function clientSecretBasicRfc3986(clientSecret) {
|
|
163
|
+
return (_as, client, _body, headers) => {
|
|
164
|
+
const credentials = Buffer.from(`${encodeURIComponent(client.client_id)}:${encodeURIComponent(clientSecret)}`, 'utf8').toString('base64');
|
|
165
|
+
headers.set('authorization', `Basic ${credentials}`);
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/** Thread an injected `fetchImpl` into oauth4webapi via its `customFetch` symbol. */
|
|
169
|
+
function customFetchOptions(oauth, fetchImpl) {
|
|
170
|
+
return fetchImpl ? { [oauth.customFetch]: fetchImpl } : undefined;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Translate an oauth4webapi failure into a typed `CliError`. A `ResponseBodyError`
|
|
174
|
+
* carries the server's OAuth error JSON (`error` / `error_description`) — surface
|
|
175
|
+
* it so a misconfigured server is diagnosable. Everything else (non-conform
|
|
176
|
+
* status, non-JSON body, network failure) collapses to the raw message.
|
|
177
|
+
*/
|
|
178
|
+
function mapOauthError(error, oauth, code, message, hints) {
|
|
179
|
+
if (error instanceof oauth.ResponseBodyError) {
|
|
180
|
+
const detail = error.error_description
|
|
181
|
+
? `${error.error} (${error.error_description})`
|
|
182
|
+
: error.error;
|
|
183
|
+
return buildAuthError(code, message, hints, detail);
|
|
184
|
+
}
|
|
185
|
+
return buildAuthError(code, message, hints, getErrorMessage(error));
|
|
186
|
+
}
|
|
187
|
+
function buildRegistrationMetadata(metadata, redirectUri, tokenEndpointAuthMethod) {
|
|
188
|
+
const body = {
|
|
189
|
+
...metadata.extra,
|
|
190
|
+
client_name: metadata.clientName,
|
|
191
|
+
redirect_uris: [redirectUri],
|
|
192
|
+
grant_types: metadata.grantTypes ?? ['authorization_code'],
|
|
193
|
+
response_types: metadata.responseTypes ?? ['code'],
|
|
194
|
+
token_endpoint_auth_method: tokenEndpointAuthMethod,
|
|
195
|
+
};
|
|
196
|
+
if (metadata.clientUri)
|
|
197
|
+
body.client_uri = metadata.clientUri;
|
|
198
|
+
if (metadata.logoUri)
|
|
199
|
+
body.logo_uri = metadata.logoUri;
|
|
200
|
+
if (metadata.applicationType)
|
|
201
|
+
body.application_type = metadata.applicationType;
|
|
202
|
+
return body;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=dcr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dcr.js","sourceRoot":"","sources":["../../../src/auth/providers/dcr.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { CliError } from '../../errors.js';
|
|
2
|
+
import type { AuthErrorCode } from '../errors.js';
|
|
3
|
+
import type { OAuthLazyString } from './pkce.js';
|
|
4
|
+
/**
|
|
5
|
+
* Build a `CliError` with user-supplied `errorHints` prepended and an optional
|
|
6
|
+
* server-derived `extra` detail appended. Centralises the "user-actionable
|
|
7
|
+
* first, diagnostic second" ordering used everywhere in this directory.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildAuthError(code: AuthErrorCode, message: string, userHints: string[] | undefined, extra?: string): CliError;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve a literal-or-function endpoint/clientId against the current handshake
|
|
12
|
+
* and runtime flags. Used by every provider in this directory.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolve(resolver: OAuthLazyString, handshake: Record<string, unknown>, flags: Record<string, unknown>): Promise<string>;
|
|
15
|
+
/** Read a response body without letting a stream error escape — used for hints. */
|
|
16
|
+
export declare function safeReadText(response: Response): Promise<string | undefined>;
|
|
17
|
+
type BuildPkceAuthorizeUrlInput = {
|
|
18
|
+
authorizeUrl: string;
|
|
19
|
+
clientId: string;
|
|
20
|
+
redirectUri: string;
|
|
21
|
+
state: string;
|
|
22
|
+
scopes: string[];
|
|
23
|
+
scopeSeparator: string;
|
|
24
|
+
codeChallenge: string;
|
|
25
|
+
};
|
|
26
|
+
/** Construct the standard PKCE S256 authorize URL. */
|
|
27
|
+
export declare function buildPkceAuthorizeUrl(input: BuildPkceAuthorizeUrlInput): string;
|
|
28
|
+
type PostAndParseJsonInput = {
|
|
29
|
+
url: string;
|
|
30
|
+
headers: Record<string, string>;
|
|
31
|
+
/** Pre-encoded request body. */
|
|
32
|
+
body: string;
|
|
33
|
+
/** Error code wrapped around every failure mode. */
|
|
34
|
+
errorCode: AuthErrorCode;
|
|
35
|
+
/** Prefix for error messages, e.g. `'Token endpoint'` or `'Registration endpoint'`. */
|
|
36
|
+
errorLabel: string;
|
|
37
|
+
errorHints?: string[];
|
|
38
|
+
fetchImpl: typeof fetch;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* POST a request, parse a JSON response, and wrap every failure mode as a
|
|
42
|
+
* typed `CliError`. Common backbone for the OAuth token endpoint and the
|
|
43
|
+
* RFC 7591 dynamic-client-registration endpoint — both POST a body, both
|
|
44
|
+
* expect a JSON reply, both want uniform error handling.
|
|
45
|
+
*
|
|
46
|
+
* Throws `errorCode` with the configured hints on:
|
|
47
|
+
* - network failure (fetch rejection)
|
|
48
|
+
* - non-2xx response (body text appended as a hint after `errorHints`)
|
|
49
|
+
* - non-JSON 2xx body (a misconfigured proxy returning HTML, etc.)
|
|
50
|
+
*
|
|
51
|
+
* Success-shape validation (e.g. `access_token` present) is the caller's
|
|
52
|
+
* job, because it differs per endpoint.
|
|
53
|
+
*/
|
|
54
|
+
export declare function postAndParseJson<T>(input: PostAndParseJsonInput): Promise<T>;
|
|
55
|
+
type PostTokenEndpointInput = {
|
|
56
|
+
url: string;
|
|
57
|
+
/** Form-encoded body. Caller owns grant_type + grant-specific params. */
|
|
58
|
+
body: URLSearchParams;
|
|
59
|
+
/**
|
|
60
|
+
* User-facing remediation hints attached to every `CliError` this helper
|
|
61
|
+
* throws (network failure, non-2xx, parse failure, missing access_token).
|
|
62
|
+
* The server-returned response body (for non-2xx) is appended after these
|
|
63
|
+
* so user hints stay at the top.
|
|
64
|
+
*/
|
|
65
|
+
errorHints?: string[];
|
|
66
|
+
fetchImpl: typeof fetch;
|
|
67
|
+
};
|
|
68
|
+
type PostTokenEndpointResult = {
|
|
69
|
+
accessToken: string;
|
|
70
|
+
refreshToken?: string;
|
|
71
|
+
/** Unix-epoch ms. Computed from `expires_in` when the server returns it. */
|
|
72
|
+
expiresAt?: number;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* POST to an OAuth 2.0 token endpoint and parse the standard JSON response.
|
|
76
|
+
* Covers the public-client `authorization_code` exchange (PKCE) — the caller
|
|
77
|
+
* owns `grant_type` and the grant-specific params via `body`.
|
|
78
|
+
*
|
|
79
|
+
* Failures uniformly throw `CliError('AUTH_TOKEN_EXCHANGE_FAILED', …)`:
|
|
80
|
+
* network errors, non-2xx responses (with body text as a hint), non-JSON
|
|
81
|
+
* bodies, and responses missing `access_token`.
|
|
82
|
+
*/
|
|
83
|
+
export declare function postTokenEndpoint(input: PostTokenEndpointInput): Promise<PostTokenEndpointResult>;
|
|
84
|
+
/** Convert an OAuth `expires_in` (seconds from now) into a Unix-epoch ms deadline. */
|
|
85
|
+
export declare function expiresAtFromExpiresIn(expiresIn: number | undefined): number | undefined;
|
|
86
|
+
type LoadOauthOptions = {
|
|
87
|
+
/** Error code wrapped around a missing/broken peer dep. */
|
|
88
|
+
code: AuthErrorCode;
|
|
89
|
+
/** Message when the peer dep isn't installed. */
|
|
90
|
+
missingMessage: string;
|
|
91
|
+
/** Caller-supplied remediation hints (e.g. provider `errorHints`), prepended first. */
|
|
92
|
+
userHints?: string[];
|
|
93
|
+
/** Install hint for the missing-peer case, appended after `userHints`. */
|
|
94
|
+
missingHints?: string[];
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Lazily import `oauth4webapi`, surfacing a typed `CliError` when the optional
|
|
98
|
+
* peer dep is absent (vs. installed-but-broken). Shared by `createPkceProvider`
|
|
99
|
+
* (refresh) and `createDcrProvider` (registration + token exchange). Caller
|
|
100
|
+
* `userHints` are prepended on both failure branches so the provider's
|
|
101
|
+
* `errorHints` contract holds even when the dep is missing.
|
|
102
|
+
*/
|
|
103
|
+
export declare function loadOauth4webapi(options: LoadOauthOptions): Promise<typeof import('oauth4webapi')>;
|
|
104
|
+
export {};
|
|
105
|
+
//# sourceMappingURL=oauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/auth/providers/oauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
|
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
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
|
|
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":"AAGA,OAAO,KAAK,EACR,WAAW,EACX,YAAY,EAMZ,aAAa,EAChB,MAAM,aAAa,CAAA;
|
|
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,12 +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';
|
|
3
4
|
// Upper bound on the refresh-token POST. Kept under the refresh helper's
|
|
4
5
|
// stale-lock threshold so a timed-out grant releases the lock before another
|
|
5
6
|
// invocation would consider it abandoned.
|
|
6
7
|
const REFRESH_TIMEOUT_MS = 10_000;
|
|
7
|
-
function expiresAtFromExpiresIn(expiresIn) {
|
|
8
|
-
return typeof expiresIn === 'number' ? Date.now() + expiresIn * 1000 : undefined;
|
|
9
|
-
}
|
|
10
8
|
/**
|
|
11
9
|
* Build an `AuthProvider` for the standard "PKCE S256, public client (no
|
|
12
10
|
* client_secret)" flow. Covers Outline (user-supplied client_id + base_url)
|
|
@@ -17,8 +15,8 @@ function expiresAtFromExpiresIn(expiresIn) {
|
|
|
17
15
|
* `runOAuthFlow` and arrives on `AuthorizeInput.scopes`; this factory does
|
|
18
16
|
* not own scope resolution.
|
|
19
17
|
*
|
|
20
|
-
* Flows that need DCR or HTTP Basic auth on the token endpoint
|
|
21
|
-
* 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).
|
|
22
20
|
*/
|
|
23
21
|
export function createPkceProvider(options) {
|
|
24
22
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
@@ -30,22 +28,22 @@ export function createPkceProvider(options) {
|
|
|
30
28
|
length: options.verifierLength,
|
|
31
29
|
});
|
|
32
30
|
const challenge = deriveChallenge(verifier);
|
|
33
|
-
|
|
31
|
+
// Resolve concurrently — both may be async (config read / prompt).
|
|
32
|
+
const [clientId, authorizeBaseUrl] = await Promise.all([
|
|
34
33
|
resolve(options.clientId, input.handshake, input.flags),
|
|
35
34
|
resolve(options.authorizeUrl, input.handshake, input.flags),
|
|
36
35
|
]);
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
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
|
+
});
|
|
47
45
|
return {
|
|
48
|
-
authorizeUrl
|
|
46
|
+
authorizeUrl,
|
|
49
47
|
handshake: { ...input.handshake, codeVerifier: verifier, clientId },
|
|
50
48
|
};
|
|
51
49
|
},
|
|
@@ -53,7 +51,7 @@ export function createPkceProvider(options) {
|
|
|
53
51
|
const verifier = input.handshake.codeVerifier;
|
|
54
52
|
const clientId = input.handshake.clientId;
|
|
55
53
|
if (typeof verifier !== 'string' || typeof clientId !== 'string') {
|
|
56
|
-
throw
|
|
54
|
+
throw buildAuthError('AUTH_TOKEN_EXCHANGE_FAILED', 'Internal: PKCE handshake state lost between authorize and exchange.', options.errorHints);
|
|
57
55
|
}
|
|
58
56
|
// `runOAuthFlow` folds the runtime `flags` into the handshake
|
|
59
57
|
// before calling exchange, so a `tokenUrl: ({ flags }) => ...`
|
|
@@ -67,45 +65,25 @@ export function createPkceProvider(options) {
|
|
|
67
65
|
client_id: clientId,
|
|
68
66
|
code_verifier: verifier,
|
|
69
67
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Accept: 'application/json',
|
|
77
|
-
},
|
|
78
|
-
body: body.toString(),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
catch (error) {
|
|
82
|
-
throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', `Token endpoint request failed: ${getErrorMessage(error)}`);
|
|
83
|
-
}
|
|
84
|
-
if (!response.ok) {
|
|
85
|
-
const detail = await safeReadText(response);
|
|
86
|
-
throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', `Token endpoint returned HTTP ${response.status}.`, detail ? { hints: [detail] } : {});
|
|
87
|
-
}
|
|
88
|
-
// Parse defensively — a misconfigured proxy can return a 2xx HTML
|
|
89
|
-
// error page that would otherwise blow up with a raw SyntaxError.
|
|
90
|
-
let payload;
|
|
91
|
-
try {
|
|
92
|
-
payload = (await response.json());
|
|
93
|
-
}
|
|
94
|
-
catch (error) {
|
|
95
|
-
throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', `Token endpoint returned non-JSON response: ${getErrorMessage(error)}`);
|
|
96
|
-
}
|
|
97
|
-
if (!payload.access_token) {
|
|
98
|
-
throw new CliError('AUTH_TOKEN_EXCHANGE_FAILED', 'Token endpoint response missing access_token.');
|
|
99
|
-
}
|
|
68
|
+
const result = await postTokenEndpoint({
|
|
69
|
+
url: tokenUrl,
|
|
70
|
+
body,
|
|
71
|
+
errorHints: options.errorHints,
|
|
72
|
+
fetchImpl,
|
|
73
|
+
});
|
|
100
74
|
return {
|
|
101
|
-
accessToken:
|
|
102
|
-
refreshToken:
|
|
103
|
-
expiresAt:
|
|
75
|
+
accessToken: result.accessToken,
|
|
76
|
+
refreshToken: result.refreshToken,
|
|
77
|
+
expiresAt: result.expiresAt,
|
|
104
78
|
};
|
|
105
79
|
},
|
|
106
80
|
validateToken: options.validate,
|
|
107
81
|
async refreshToken(input) {
|
|
108
|
-
const oauth = await loadOauth4webapi(
|
|
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
|
+
});
|
|
109
87
|
// Mirror `exchangeCode`: a resolver that reads `flags` sees the
|
|
110
88
|
// same view during silent refresh as it did at authorize time.
|
|
111
89
|
const flags = input.handshake.flags ?? {};
|
|
@@ -162,35 +140,4 @@ export function createPkceProvider(options) {
|
|
|
162
140
|
},
|
|
163
141
|
};
|
|
164
142
|
}
|
|
165
|
-
async function resolve(resolver, handshake, flags) {
|
|
166
|
-
return typeof resolver === 'function' ? resolver({ handshake, flags }) : resolver;
|
|
167
|
-
}
|
|
168
|
-
// Optional peer dep — only refresh consumers install it. The dynamic import
|
|
169
|
-
// (and a missing-peer failure) is memoised so it isn't repeated on every
|
|
170
|
-
// refresh, which sits on the authenticated-call path.
|
|
171
|
-
let oauthModulePromise;
|
|
172
|
-
async function loadOauth4webapi() {
|
|
173
|
-
oauthModulePromise ??= import('oauth4webapi');
|
|
174
|
-
try {
|
|
175
|
-
return await oauthModulePromise;
|
|
176
|
-
}
|
|
177
|
-
catch (error) {
|
|
178
|
-
const code = error?.code;
|
|
179
|
-
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
|
|
180
|
-
throw new CliError('AUTH_REFRESH_UNAVAILABLE', 'oauth4webapi is required for refresh-token support.', { hints: ['Run `npm install oauth4webapi` in your CLI.'] });
|
|
181
|
-
}
|
|
182
|
-
// Installed but failed to initialise — surface the real cause rather
|
|
183
|
-
// than a misleading "install it" hint.
|
|
184
|
-
throw new CliError('AUTH_REFRESH_UNAVAILABLE', `Failed to load oauth4webapi: ${getErrorMessage(error)}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
async function safeReadText(response) {
|
|
188
|
-
try {
|
|
189
|
-
const text = (await response.text()).trim();
|
|
190
|
-
return text.length > 0 ? text : undefined;
|
|
191
|
-
}
|
|
192
|
-
catch {
|
|
193
|
-
return undefined;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
143
|
//# sourceMappingURL=pkce.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/providers/pkce.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAC3D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/providers/pkce.ts"],"names":[],"mappings":"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"}
|