@dereekb/dbx-cli 14.2.0 → 14.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli-lint-cache",
3
- "version": "14.2.0",
3
+ "version": "14.4.0",
4
4
  "sideEffects": false,
5
5
  "private": true,
6
6
  "type": "module",
@@ -9,7 +9,7 @@
9
9
  "eslint": "10.9.1"
10
10
  },
11
11
  "peerDependencies": {
12
- "@dereekb/util": "14.2.0",
12
+ "@dereekb/util": "14.4.0",
13
13
  "yargs": "^18.0.0"
14
14
  }
15
15
  }
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/manifest-extract",
3
- "version": "14.2.0",
3
+ "version": "14.4.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@dereekb/date": "14.2.0",
8
- "@dereekb/dbx-cli": "14.2.0",
9
- "@dereekb/firebase": "14.2.0",
10
- "@dereekb/model": "14.2.0",
11
- "@dereekb/nestjs": "14.2.0",
12
- "@dereekb/rxjs": "14.2.0",
13
- "@dereekb/util": "14.2.0",
7
+ "@dereekb/date": "14.4.0",
8
+ "@dereekb/dbx-cli": "14.4.0",
9
+ "@dereekb/firebase": "14.4.0",
10
+ "@dereekb/model": "14.4.0",
11
+ "@dereekb/nestjs": "14.4.0",
12
+ "@dereekb/oauth-resource": "14.4.0",
13
+ "@dereekb/rxjs": "14.4.0",
14
+ "@dereekb/util": "14.4.0",
14
15
  "ts-morph": "^28.0.0"
15
16
  },
17
+ "devDependencies": {
18
+ "@dereekb/firebase": "14.4.0"
19
+ },
16
20
  "exports": {
17
21
  "./package.json": "./package.json",
18
22
  ".": {
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/model-test",
3
- "version": "14.2.0",
3
+ "version": "14.4.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@dereekb/util": "14.2.0",
7
+ "@dereekb/util": "14.4.0",
8
8
  "ts-morph": "^28.0.0"
9
9
  },
10
10
  "exports": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli",
3
- "version": "14.2.0",
3
+ "version": "14.4.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "bin": {
@@ -66,11 +66,12 @@
66
66
  }
67
67
  },
68
68
  "peerDependencies": {
69
- "@dereekb/date": "14.2.0",
70
- "@dereekb/firebase": "14.2.0",
71
- "@dereekb/model": "14.2.0",
72
- "@dereekb/nestjs": "14.2.0",
73
- "@dereekb/util": "14.2.0",
69
+ "@dereekb/date": "14.4.0",
70
+ "@dereekb/firebase": "14.4.0",
71
+ "@dereekb/model": "14.4.0",
72
+ "@dereekb/nestjs": "14.4.0",
73
+ "@dereekb/oauth-resource": "14.4.0",
74
+ "@dereekb/util": "14.4.0",
74
75
  "@nestjs/common": "^12.0.1",
75
76
  "arktype": "^2.2.0",
76
77
  "firebase": "^12.18.0",
@@ -83,7 +84,9 @@
83
84
  "devDependencies": {
84
85
  "@angular/core": "22.1.4",
85
86
  "@types/yargs": "^17.0.35",
86
- "eslint": "10.9.1"
87
+ "@typescript-eslint/parser": "8.69.0",
88
+ "eslint": "10.9.1",
89
+ "firebase": "^12.18.0"
87
90
  },
88
91
  "module": "./index.esm.js",
89
92
  "main": "./index.esm.js",
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@dereekb/dbx-cli/route",
3
- "version": "14.2.0",
3
+ "version": "14.4.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "peerDependencies": {
7
- "@dereekb/date": "14.2.0",
8
- "@dereekb/dbx-cli": "14.2.0",
9
- "@dereekb/firebase": "14.2.0",
10
- "@dereekb/model": "14.2.0",
11
- "@dereekb/nestjs": "14.2.0",
12
- "@dereekb/util": "14.2.0"
7
+ "@dereekb/date": "14.4.0",
8
+ "@dereekb/dbx-cli": "14.4.0",
9
+ "@dereekb/firebase": "14.4.0",
10
+ "@dereekb/model": "14.4.0",
11
+ "@dereekb/nestjs": "14.4.0",
12
+ "@dereekb/oauth-resource": "14.4.0",
13
+ "@dereekb/util": "14.4.0"
13
14
  },
14
15
  "exports": {
15
16
  "./package.json": "./package.json",
@@ -1,33 +1,20 @@
1
+ import { type FirestoreSessionCredentials, type FirestoreSessionErrorFactory } from '@dereekb/oauth-resource/firebase';
2
+ export { FIRESTORE_SESSION_API_PATH } from '@dereekb/oauth-resource/firebase';
1
3
  /**
2
- * Path (relative to the API base URL) of the direct-Firestore session endpoint served by
3
- * `@dereekb/firebase-server`'s `SessionApiController`.
4
+ * The credential bundle `GET <apiBaseUrl>/session/firestore` returns.
4
5
  *
5
- * Duplicated here rather than imported so `dbx-cli` keeps no dependency on the server package —
6
- * the same arrangement `CALL_MODEL_API_PATH` uses.
6
+ * The CLI's name for {@link FirestoreSessionCredentials}, which is the shared implementation in
7
+ * `@dereekb/oauth-resource/firebase`.
7
8
  */
8
- export declare const FIRESTORE_SESSION_API_PATH = "/session/firestore";
9
+ export type CliFirestoreSession = FirestoreSessionCredentials;
9
10
  /**
10
- * The credential bundle `GET <apiBaseUrl>/session/firestore` returns.
11
+ * Maps a generic session failure onto a {@link CliError}, re-attaching the CLI-flavored remediation
12
+ * an operator can act on (`<cli> auth login --env <env>` and friends).
13
+ *
14
+ * @param input - The generic failure.
15
+ * @returns The `CliError` to throw.
11
16
  */
12
- export interface CliFirestoreSession {
13
- /**
14
- * The uid the session was minted for.
15
- */
16
- readonly uid: string;
17
- /**
18
- * A Firebase Auth custom token to exchange via `signInWithCustomToken`.
19
- */
20
- readonly customToken: string;
21
- /**
22
- * An App Check attestation minted server-side for the project's registered web app. Absent when
23
- * the API has no `appCheckAppId` configured (a project that does not enforce App Check).
24
- */
25
- readonly appCheckToken?: string;
26
- /**
27
- * ISO timestamp at which the session's shortest-lived credential expires.
28
- */
29
- readonly expiresAt: string;
30
- }
17
+ export declare const cliFirestoreSessionErrorFactory: FirestoreSessionErrorFactory;
31
18
  export interface FetchFirestoreSessionInput {
32
19
  /**
33
20
  * The API base URL — typically `<host>/<project>/us-central1/api` or `https://<domain>/api`.
@@ -44,11 +31,18 @@ export interface FetchFirestoreSessionInput {
44
31
  /**
45
32
  * Fetches a direct-Firestore session from the API with the cached Bearer access token.
46
33
  *
47
- * The endpoint is admin-only and additionally gated on the `session.firestore` OIDC scope, so a 403
48
- * here usually means the logged-in user is not an admin or logged in without that scope.
34
+ * The thin CLI wrapper over `@dereekb/oauth-resource/firebase`'s `fetchFirestoreSession`: it binds
35
+ * the CLI's `tracedFetch` (so `--verbose` and `--timeout` apply) and the `CliError` factory.
49
36
  *
50
37
  * @param input - The API target, access token, and optional fetch override.
51
38
  * @returns The parsed {@link CliFirestoreSession}.
52
39
  * @throws {CliError} When the endpoint answers non-2xx or returns an unusable body.
53
40
  */
54
41
  export declare function fetchFirestoreSession(input: FetchFirestoreSessionInput): Promise<CliFirestoreSession>;
42
+ /**
43
+ * Binds the CLI's verbose-trace + `--timeout` fetch behavior onto the supplied (or global) fetch.
44
+ *
45
+ * @param fetcher - The underlying fetch impl, when one was injected.
46
+ * @returns A `fetch`-shaped function carrying the CLI's tracing.
47
+ */
48
+ export declare function cliFirestoreSessionFetcher(fetcher?: typeof fetch): typeof fetch;
@@ -1,3 +1,4 @@
1
1
  export * from './auth.command.factory';
2
2
  export * from './oidc.client';
3
3
  export * from './oidc.flow';
4
+ export * from './oidc.loopback';
@@ -0,0 +1,127 @@
1
+ import { type Maybe, type PortNumber, type WebsiteUrl } from '@dereekb/util';
2
+ /**
3
+ * Hostnames RFC 8252 §7.3 treats as the loopback interface, and the exact set `oidc-provider`
4
+ * recognizes as a loopback redirect host.
5
+ *
6
+ * `[::1]` carries its brackets because that is what `URL.hostname` returns for an IPv6 literal —
7
+ * the brackets are stripped again before the address is handed to `server.listen`.
8
+ */
9
+ export declare const LOOPBACK_REDIRECT_HOSTNAMES: readonly string[];
10
+ /**
11
+ * The loopback port the CLI suggests when the configured redirect URI has no bindable port.
12
+ *
13
+ * Deliberately outside the 9900-9910 block the Firebase emulators occupy, so a suggested redirect
14
+ * URI never collides with a running emulator.
15
+ */
16
+ export declare const SUGGESTED_CLI_LOOPBACK_REDIRECT_PORT: PortNumber;
17
+ /**
18
+ * Error code raised when the loopback listener cannot bind the redirect URI's port (e.g. the port
19
+ * is already in use, or is privileged).
20
+ */
21
+ export declare const LOOPBACK_REDIRECT_LISTEN_FAILED_ERROR_CODE = "AUTH_REDIRECT_LISTEN_FAILED";
22
+ /**
23
+ * Error code raised when no redirect reached the loopback listener within the allotted time.
24
+ */
25
+ export declare const LOOPBACK_REDIRECT_TIMEOUT_ERROR_CODE = "AUTH_REDIRECT_TIMEOUT";
26
+ /**
27
+ * A redirect URI the CLI can bind a local HTTP listener to, decomposed into its parts.
28
+ */
29
+ export interface LoopbackRedirectTarget {
30
+ /**
31
+ * The normalized redirect URI to send as `redirect_uri`. Identical to the configured URI unless a
32
+ * port override was applied.
33
+ */
34
+ readonly redirectUri: WebsiteUrl;
35
+ /**
36
+ * The loopback hostname, as `URL.hostname` reports it (so `[::1]` keeps its brackets).
37
+ */
38
+ readonly hostname: string;
39
+ /**
40
+ * The port to bind. Always a positive integer — a `0`/absent port is not a capturable target.
41
+ */
42
+ readonly port: PortNumber;
43
+ /**
44
+ * The path the provider redirects to. Requests to any other path are answered `404` so a stray
45
+ * `/favicon.ico` cannot be mistaken for the authorization redirect.
46
+ */
47
+ readonly pathname: string;
48
+ }
49
+ export interface ParseLoopbackRedirectUriInput {
50
+ /**
51
+ * The configured redirect URI.
52
+ */
53
+ readonly redirectUri: Maybe<string>;
54
+ /**
55
+ * Optional port override, replacing whatever port the redirect URI carries.
56
+ *
57
+ * The resulting URI is what gets sent as `redirect_uri`, so it must be registered with the OAuth
58
+ * client just like the configured one.
59
+ */
60
+ readonly port?: Maybe<PortNumber>;
61
+ }
62
+ /**
63
+ * Decomposes a redirect URI into a {@link LoopbackRedirectTarget} when the CLI can bind a local
64
+ * listener for it.
65
+ *
66
+ * A target is only produced for an `http:` loopback URI carrying a concrete, non-zero port. The
67
+ * conventional `http://127.0.0.1:0/callback` placeholder is deliberately NOT capturable: binding an
68
+ * ephemeral port would mean sending a `redirect_uri` that differs from the registered one, which
69
+ * every OAuth provider rejects unless the client is registered as a native app (where loopback
70
+ * ports are compared port-insensitively).
71
+ *
72
+ * @param input - The parse inputs.
73
+ * @param input.redirectUri - The configured redirect URI.
74
+ * @param input.port - Optional port override applied to the parsed URI.
75
+ * @returns The bindable target, or `undefined` when the URI cannot be captured locally.
76
+ * @__NO_SIDE_EFFECTS__
77
+ */
78
+ export declare function parseLoopbackRedirectUri(input: ParseLoopbackRedirectUriInput): Maybe<LoopbackRedirectTarget>;
79
+ export interface StartLoopbackRedirectCaptureInput {
80
+ /**
81
+ * The bindable target, from {@link parseLoopbackRedirectUri}.
82
+ */
83
+ readonly target: LoopbackRedirectTarget;
84
+ /**
85
+ * Text shown in the browser tab once the redirect is captured.
86
+ */
87
+ readonly successMessage?: Maybe<string>;
88
+ }
89
+ export interface LoopbackRedirectCapture {
90
+ /**
91
+ * The `redirect_uri` the listener is bound to. Send this value in the authorization request.
92
+ */
93
+ readonly redirectUri: WebsiteUrl;
94
+ /**
95
+ * The bound port.
96
+ */
97
+ readonly port: PortNumber;
98
+ /**
99
+ * Resolves with the full redirect URL (query string included) the browser was sent to.
100
+ *
101
+ * A provider error redirect resolves rather than rejects — the returned URL carries the `error`
102
+ * params, which the shared redirect parser already turns into an `AUTH_PROVIDER_ERROR`.
103
+ *
104
+ * @param timeoutMs - Optional milliseconds to wait before rejecting with
105
+ * {@link LOOPBACK_REDIRECT_TIMEOUT_ERROR_CODE}. Waits indefinitely when omitted.
106
+ */
107
+ readonly waitForRedirect: (timeoutMs?: Maybe<number>) => Promise<WebsiteUrl>;
108
+ /**
109
+ * Closes the listener and destroys any open sockets, so the CLI's event loop can drain.
110
+ */
111
+ readonly close: () => Promise<void>;
112
+ }
113
+ /**
114
+ * Binds a local HTTP listener to a loopback redirect URI so the authorization code can be read
115
+ * straight out of the browser redirect instead of being pasted back by hand.
116
+ *
117
+ * The listener answers exactly one path — the redirect URI's own — and serves a short "you can close
118
+ * this tab" page. Everything else is answered `404`, which is what keeps the browser's automatic
119
+ * `/favicon.ico` request from being mistaken for the redirect.
120
+ *
121
+ * @param input - The capture inputs.
122
+ * @param input.target - The bindable target, from {@link parseLoopbackRedirectUri}.
123
+ * @param input.successMessage - Optional override for the browser success page's body line.
124
+ * @returns The started {@link LoopbackRedirectCapture}.
125
+ * @throws {CliError} `AUTH_REDIRECT_LISTEN_FAILED` when the port cannot be bound.
126
+ */
127
+ export declare function startLoopbackRedirectCapture(input: StartLoopbackRedirectCaptureInput): Promise<LoopbackRedirectCapture>;
@@ -1,4 +1,5 @@
1
- import { type Maybe } from '@dereekb/util';
1
+ import { type Maybe, type OidcClientAuthMethod } from '@dereekb/util';
2
+ import { type FirebaseClientConfig, type FirebaseClientEmulatorsConfig, firebaseClientEmulatorsInUse, isFirebaseClientConfigComplete } from '@dereekb/oauth-resource/firebase';
2
3
  import { type CliTokenEntry } from './token.cache';
3
4
  /**
4
5
  * The default OAuth/OIDC scopes requested by the CLI when none are configured.
@@ -15,7 +16,12 @@ export declare const MODEL_WRITE_OIDC_SCOPES: readonly ["model.create", "model.u
15
16
  /**
16
17
  * The default redirect URI used by the CLI.
17
18
  *
18
- * Opens up to nothing in the browser so the user can copy/paste the resulting token url back into the CLI.
19
+ * The `0` port is a placeholder, not a bindable port: the redirect resolves to nothing in the
20
+ * browser and the user copy/pastes the resulting URL back into the CLI.
21
+ *
22
+ * To have `auth login` capture the redirect automatically instead, configure a redirect URI with a
23
+ * concrete loopback port (e.g. `http://127.0.0.1:8976/callback`) and register that exact URI with
24
+ * the OAuth client — `auth login` binds it and reads the code straight out of the browser redirect.
19
25
  */
20
26
  export declare const DEFAULT_CLI_REDIRECT_URI = "http://127.0.0.1:0/callback";
21
27
  /**
@@ -112,67 +118,22 @@ export declare function mergeCliEnvWithDefault(input: MergeCliEnvWithDefaultInpu
112
118
  */
113
119
  export declare function mergeCliFirebaseConfig(env: Maybe<CliFirebaseConfig>, defaultEnv: Maybe<CliFirebaseConfig>): CliFirebaseConfig | undefined;
114
120
  /**
115
- * Local Firebase emulator targets for a CLI env.
116
- *
117
- * Mirrors the semantics of `DbxFirebaseEmulatorsConfig` in `@dereekb/dbx-firebase` (whose parse
118
- * helper is Angular-bound and not reusable here): the presence of this object means "use emulators"
119
- * unless {@link useEmulators} is explicitly `false`.
120
- *
121
- * App Check is auto-disabled whenever emulators are in use — the emulators do not verify
122
- * attestations, and `initializeAppCheck` against a fake project only gets in the way.
121
+ * Local Firebase emulator targets for a CLI env. The CLI's name for
122
+ * {@link FirebaseClientEmulatorsConfig}.
123
123
  */
124
- export interface CliFirebaseEmulatorsConfig {
125
- /**
126
- * Set `false` to keep the emulator targets configured but inactive. Defaults to `true`.
127
- */
128
- readonly useEmulators?: boolean;
129
- /**
130
- * Host the emulators are reachable at. Defaults to {@link DEFAULT_CLI_FIREBASE_EMULATOR_HOST}.
131
- */
132
- readonly host?: string;
133
- /**
134
- * Port of the Auth emulator. When unset, Auth is not redirected to an emulator.
135
- */
136
- readonly authPort?: number;
137
- /**
138
- * Port of the Firestore emulator. When unset, Firestore is not redirected to an emulator.
139
- */
140
- readonly firestorePort?: number;
141
- }
124
+ export type CliFirebaseEmulatorsConfig = FirebaseClientEmulatorsConfig;
142
125
  /**
143
126
  * Firebase client-SDK configuration for a CLI env, used only by the direct-Firestore session
144
127
  * (`CliContext.getFirestoreContext`). Everything else the CLI does goes over the model HTTP API and
145
128
  * needs none of this.
146
129
  *
147
- * These are the same public values the app's browser client initializes with — copy them from the
148
- * target app's environment file. `appId` in particular must be the registered **web** app, since the
149
- * server mints its App Check attestation for that app.
130
+ * The CLI's name for {@link FirebaseClientConfig}: the same shape any resource server opening a
131
+ * user-scoped Firestore session supplies.
150
132
  */
151
- export interface CliFirebaseConfig {
152
- /**
153
- * The Firebase web API key.
154
- */
155
- readonly apiKey?: string;
156
- /**
157
- * The project's auth domain (e.g. `my-project.firebaseapp.com`).
158
- */
159
- readonly authDomain?: string;
160
- /**
161
- * The Firebase project id.
162
- */
163
- readonly projectId?: string;
164
- /**
165
- * The registered **web** app id (e.g. `1:1234567890:web:abcdef`).
166
- */
167
- readonly appId?: string;
168
- /**
169
- * Optional emulator targets for local development.
170
- */
171
- readonly emulators?: CliFirebaseEmulatorsConfig;
172
- }
133
+ export type CliFirebaseConfig = FirebaseClientConfig;
173
134
  /**
174
135
  * Default host used for Firebase emulator connections when a {@link CliFirebaseEmulatorsConfig}
175
- * omits one.
136
+ * omits one. See {@link DEFAULT_FIREBASE_CLIENT_EMULATOR_HOST}.
176
137
  */
177
138
  export declare const DEFAULT_CLI_FIREBASE_EMULATOR_HOST = "localhost";
178
139
  /**
@@ -182,17 +143,22 @@ export declare const DEFAULT_CLI_FIREBASE_EMULATOR_HOST = "localhost";
182
143
  * Deliberately separate from {@link isCliEnvConfigComplete}: the Firebase config is optional, and
183
144
  * folding it into the general completeness check would break every CLI that only uses the model API.
184
145
  *
185
- * @param firebase - The env's Firebase client config, if any.
186
- * @returns `true` when `apiKey`, `projectId`, and `appId` are all present and non-empty.
146
+ * See {@link isFirebaseClientConfigComplete}.
147
+ */
148
+ export declare const isCliFirebaseConfigComplete: typeof isFirebaseClientConfigComplete;
149
+ /**
150
+ * Returns true when the env's emulator config is present and active. See
151
+ * {@link firebaseClientEmulatorsInUse}.
187
152
  */
188
- export declare function isCliFirebaseConfigComplete(firebase: Maybe<CliFirebaseConfig>): firebase is Required<Pick<CliFirebaseConfig, 'apiKey' | 'projectId' | 'appId'>> & CliFirebaseConfig;
153
+ export declare const cliFirebaseEmulatorsInUse: typeof firebaseClientEmulatorsInUse;
189
154
  /**
190
- * Returns true when the env's emulator config is present and active.
155
+ * The OAuth client's registered `token_endpoint_auth_method`, as far as the CLI needs to model it.
191
156
  *
192
- * @param firebase - The env's Firebase client config, if any.
193
- * @returns `true` when emulators are configured and not explicitly disabled.
157
+ * Wider than {@link OidcClientAuthMethod} by exactly one member: `'none'`, the public-client case.
158
+ * That value is meaningless to the protocol layer — a public client sends no credential, so there is
159
+ * no presentation to choose — but it is precisely what the CLI needs in order to stop asking for one.
194
160
  */
195
- export declare function cliFirebaseEmulatorsInUse(firebase: Maybe<CliFirebaseConfig>): boolean;
161
+ export type OidcCliTokenEndpointAuthMethod = OidcClientAuthMethod | 'none';
196
162
  /**
197
163
  * Environment-targeting config for a CLI invocation.
198
164
  *
@@ -231,12 +197,30 @@ export interface CliEnvConfig {
231
197
  readonly clientId?: string;
232
198
  /**
233
199
  * The OAuth client secret registered with the target app.
200
+ *
201
+ * Absent for a public client — see {@link tokenEndpointAuthMethod}.
234
202
  */
235
203
  readonly clientSecret?: string;
236
204
  /**
237
- * The redirect URI registered with the OAuth client. The CLI does not bind a server — it parses
238
- * the URL the user pastes back, so this can be any value the OIDC provider accepts as a
239
- * registered redirect URI (e.g. `http://127.0.0.1:0/callback` or another loopback/placeholder URL).
205
+ * How the client authenticates at the token endpoint, mirroring the OAuth client's registered
206
+ * `token_endpoint_auth_method`.
207
+ *
208
+ * `'none'` marks a PUBLIC client: it holds no secret and proves itself with PKCE instead. Recording
209
+ * it lets `auth setup` skip the client-secret prompt outright rather than asking for a credential
210
+ * that must not exist — the prompt cannot infer this, since "no secret yet" and "never a secret"
211
+ * look identical from an empty config.
212
+ *
213
+ * Omit to leave it unknown, which keeps the existing prompt-and-accept-empty behaviour.
214
+ */
215
+ readonly tokenEndpointAuthMethod?: OidcCliTokenEndpointAuthMethod;
216
+ /**
217
+ * The redirect URI registered with the OAuth client.
218
+ *
219
+ * When this is an `http:` loopback URI with a concrete, non-zero port (e.g.
220
+ * `http://127.0.0.1:8976/callback`), `auth login` binds that port and reads the authorization code
221
+ * straight out of the browser redirect. Any other value — including the
222
+ * {@link DEFAULT_CLI_REDIRECT_URI} `:0` placeholder and out-of-band URNs — falls back to the user
223
+ * pasting the redirect URL back into the CLI.
240
224
  *
241
225
  * Defaults to {@link DEFAULT_CLI_REDIRECT_URI}.
242
226
  */
@@ -312,10 +296,15 @@ export declare function applyEnvVarOverrides(input: EnvVarOverrideInput): Maybe<
312
296
  /**
313
297
  * Returns true when the env has the minimum fields needed to attempt an OAuth login or token refresh.
314
298
  *
299
+ * `clientSecret` is NOT among them. A CLI is a public client in the usual case
300
+ * (`token_endpoint_auth_method: 'none'`), authenticating with PKCE rather than a secret, so requiring
301
+ * one here would report a correctly-configured public env as incomplete — and send the caller back to
302
+ * `auth setup` to supply a credential the provider would then reject.
303
+ *
315
304
  * @param env - The env config to check.
316
- * @returns `true` when `apiBaseUrl`, `oidcIssuer`, `clientId`, `clientSecret`, and `redirectUri` are all present and non-empty.
305
+ * @returns `true` when `apiBaseUrl`, `oidcIssuer`, `clientId`, and `redirectUri` are all present and non-empty.
317
306
  */
318
- export declare function isCliEnvConfigComplete(env: Maybe<CliEnvConfig>): env is Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'clientSecret' | 'redirectUri'>> & CliEnvConfig;
307
+ export declare function isCliEnvConfigComplete(env: Maybe<CliEnvConfig>): env is Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'redirectUri'>> & CliEnvConfig;
319
308
  /**
320
309
  * Inputs to {@link readEnvTokenEntry}.
321
310
  */
@@ -8,7 +8,7 @@ import { type CliPaths } from './paths';
8
8
  * Guarantees that the OIDC client fields are present so callers can pass them through to the
9
9
  * OIDC client/token helpers without a non-null assertion or extra runtime check.
10
10
  */
11
- export type CliEnvConfigComplete = Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'clientSecret' | 'redirectUri'>> & CliEnvConfig;
11
+ export type CliEnvConfigComplete = Required<Pick<CliEnvConfig, 'apiBaseUrl' | 'oidcIssuer' | 'clientId' | 'redirectUri'>> & CliEnvConfig;
12
12
  /**
13
13
  * Builds the conventional `<CLINAME>_ENV` env var name from the CLI binary name.
14
14
  *
@@ -71,8 +71,9 @@ export declare function resolveCliEnv(input: ResolveCliEnvInput): Promise<Resolv
71
71
  export interface ResolveCliEnvOrThrowInput extends ResolveCliEnvInput {
72
72
  /**
73
73
  * When `true`, throws `AUTH_ENV_INCOMPLETE` if the resolved env is missing OIDC fields
74
- * (apiBaseUrl, oidcIssuer, clientId, clientSecret, redirectUri). The returned env is narrowed
75
- * to {@link CliEnvConfigComplete}. Defaults to `false`.
74
+ * (apiBaseUrl, oidcIssuer, clientId, redirectUri). `clientSecret` is not required — see
75
+ * {@link isCliEnvConfigComplete}. The returned env is narrowed to {@link CliEnvConfigComplete}.
76
+ * Defaults to `false`.
76
77
  */
77
78
  readonly requireComplete?: boolean;
78
79
  }
@@ -1,40 +1,19 @@
1
- import { type AsyncKeyedValueCache, type Maybe } from '@dereekb/util';
2
- import { type CliFirestoreSession } from '../api/firestore-session.client';
1
+ import { type FirestoreSessionCacheEntry, type FirestoreSessionCredentialsCache, firestoreSessionEntryExpiresAt, isFirestoreSessionExpired } from '@dereekb/oauth-resource/firebase';
3
2
  /**
4
3
  * Hard ceiling on how long a minted direct-Firestore session may be reused, regardless of what the
5
- * API reported in `expiresAt`.
6
- *
7
- * One hour, because that is the Firebase ceiling the credentials themselves sit under: a custom
8
- * token is exchangeable for one hour, and the ID token it mints lives one hour. Holding a session
9
- * past that buys nothing — the sign-in would fail — and re-minting is one HTTP round-trip.
4
+ * API reported in `expiresAt`. See {@link FIRESTORE_SESSION_MAX_CACHE_MS}.
10
5
  */
11
6
  export declare const CLI_FIRESTORE_SESSION_MAX_CACHE_MS: number;
12
7
  /**
13
- * Default skew/latency buffer applied when deciding whether a cached session is still usable.
8
+ * Default skew/latency buffer applied when deciding whether a cached session is still usable. See
9
+ * {@link FIRESTORE_SESSION_EXPIRY_BUFFER_MS}.
14
10
  */
15
- export declare const CLI_FIRESTORE_SESSION_EXPIRY_BUFFER_MS = 60000;
11
+ export declare const CLI_FIRESTORE_SESSION_EXPIRY_BUFFER_MS: number;
16
12
  /**
17
- * A cached direct-Firestore session for a single env.
18
- *
19
- * Stores the credential envelope the API minted, not the live Firebase objects — those are
20
- * per-process and cannot be serialized. A cache hit still signs in; it just skips the
21
- * `GET /session/firestore` round-trip.
13
+ * A cached direct-Firestore session for a single env. The CLI's name for
14
+ * {@link FirestoreSessionCacheEntry}.
22
15
  */
23
- export interface CliFirestoreSessionEntry {
24
- /**
25
- * The credential bundle returned by `GET /session/firestore`.
26
- */
27
- readonly session: CliFirestoreSession;
28
- /**
29
- * Unix epoch milliseconds at which the entry was written.
30
- */
31
- readonly cachedAt: number;
32
- /**
33
- * The uid the entry was minted for, denormalized so a stale entry belonging to a different user
34
- * can be detected without parsing the custom token.
35
- */
36
- readonly uid: string;
37
- }
16
+ export type CliFirestoreSessionEntry = FirestoreSessionCacheEntry;
38
17
  /**
39
18
  * Firestore session cache shape on disk — keyed by env name.
40
19
  */
@@ -45,13 +24,18 @@ export type CliFirestoreSessionCache = Record<string, CliFirestoreSessionEntry>;
45
24
  * Backed by a single JSON file with per-process in-memory memoization, exactly like the token
46
25
  * cache — see {@link createMemoizedJsonFileAsyncKeyedValueCache}.
47
26
  */
48
- export type CliFirestoreSessionCacheStore = AsyncKeyedValueCache<CliFirestoreSessionEntry>;
27
+ export type CliFirestoreSessionCacheStore = FirestoreSessionCredentialsCache;
49
28
  export interface CreateCliFirestoreSessionCacheStoreInput {
50
29
  readonly firestoreSessionCachePath: string;
51
30
  }
52
31
  /**
53
32
  * Creates a per-env direct-Firestore session cache store backed by a single JSON file.
54
33
  *
34
+ * The file-backed half of the session cache stays HERE rather than moving to
35
+ * `@dereekb/oauth-resource/firebase`: it depends on `@dereekb/nestjs`, and a Nest peer has no business
36
+ * in a package whose whole point is a clean install graph. Only the expiry policy and the
37
+ * {@link FirestoreSessionCredentialsCache} port are shared.
38
+ *
55
39
  * Entries are written with mode 0o600 — they hold a Firebase custom token, which is a bearer
56
40
  * credential for the user it was minted for.
57
41
  *
@@ -62,25 +46,12 @@ export interface CreateCliFirestoreSessionCacheStoreInput {
62
46
  */
63
47
  export declare function createCliFirestoreSessionCacheStore(input: CreateCliFirestoreSessionCacheStoreInput): CliFirestoreSessionCacheStore;
64
48
  /**
65
- * Resolves the epoch-millis instant at which a cached session stops being usable.
66
- *
67
- * The effective expiry is the EARLIER of the API-reported `expiresAt` and
68
- * {@link CLI_FIRESTORE_SESSION_MAX_CACHE_MS} past the write. Taking the earlier of the two means a
69
- * server that reports an over-long (or unparsable) window still cannot push a session past the
70
- * Firebase credential ceiling.
71
- *
72
- * @param entry - The cached entry.
73
- * @returns The effective expiry in unix epoch milliseconds.
74
- *
75
- * @__NO_SIDE_EFFECTS__
49
+ * Resolves the epoch-millis instant at which a cached session stops being usable. See
50
+ * {@link firestoreSessionEntryExpiresAt}.
76
51
  */
77
- export declare function cliFirestoreSessionEntryExpiresAt(entry: CliFirestoreSessionEntry): number;
52
+ export declare const cliFirestoreSessionEntryExpiresAt: typeof firestoreSessionEntryExpiresAt;
78
53
  /**
79
- * Returns true when the cached session is at or near its effective expiry.
80
- *
81
- * @param entry - The cached entry (`null`/`undefined` is treated as expired).
82
- * @param nowMs - The current time in unix epoch milliseconds. Defaults to `Date.now()`.
83
- * @param bufferMs - Skew/latency buffer; the entry is treated as expired this far ahead of its effective expiry.
84
- * @returns `true` when the entry is unusable, otherwise `false`.
54
+ * Returns true when the cached session is at or near its effective expiry. See
55
+ * {@link isFirestoreSessionExpired}.
85
56
  */
86
- export declare function isCliFirestoreSessionExpired(entry: Maybe<CliFirestoreSessionEntry>, nowMs?: number, bufferMs?: number): boolean;
57
+ export declare const isCliFirestoreSessionExpired: typeof isFirestoreSessionExpired;