@dereekb/oauth-resource 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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/express/index.d.ts +1 -0
  4. package/express/index.esm.js +321 -0
  5. package/express/package.json +29 -0
  6. package/express/src/index.d.ts +1 -0
  7. package/express/src/lib/bearer.middleware.d.ts +78 -0
  8. package/express/src/lib/index.d.ts +2 -0
  9. package/express/src/lib/well-known.router.d.ts +35 -0
  10. package/firebase/index.d.ts +1 -0
  11. package/firebase/index.esm.js +1924 -0
  12. package/firebase/package.json +27 -0
  13. package/firebase/src/index.d.ts +1 -0
  14. package/firebase/src/lib/firestore/firestore.sdk-identity.d.ts +165 -0
  15. package/firebase/src/lib/firestore/index.d.ts +1 -0
  16. package/firebase/src/lib/index.d.ts +2 -0
  17. package/firebase/src/lib/session/firebase-client.config.d.ts +86 -0
  18. package/firebase/src/lib/session/firebase-user-session.d.ts +223 -0
  19. package/firebase/src/lib/session/firebase-user-session.pool.d.ts +168 -0
  20. package/firebase/src/lib/session/firestore-session.cache.d.ts +79 -0
  21. package/firebase/src/lib/session/firestore-session.client.d.ts +149 -0
  22. package/firebase/src/lib/session/index.d.ts +5 -0
  23. package/index.d.ts +1 -0
  24. package/index.esm.js +1066 -0
  25. package/package.json +53 -0
  26. package/src/index.d.ts +1 -0
  27. package/src/lib/auth/index.d.ts +1 -0
  28. package/src/lib/auth/oauth.resource.auth.d.ts +55 -0
  29. package/src/lib/challenge/bearer.challenge.d.ts +73 -0
  30. package/src/lib/challenge/index.d.ts +1 -0
  31. package/src/lib/error/index.d.ts +1 -0
  32. package/src/lib/error/oauth.resource.error.d.ts +76 -0
  33. package/src/lib/index.d.ts +6 -0
  34. package/src/lib/issuer/index.d.ts +1 -0
  35. package/src/lib/issuer/issuer.profile.d.ts +98 -0
  36. package/src/lib/metadata/index.d.ts +1 -0
  37. package/src/lib/metadata/protected-resource.metadata.d.ts +64 -0
  38. package/src/lib/verify/index.d.ts +1 -0
  39. package/src/lib/verify/verify.bearer.d.ts +95 -0
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@dereekb/oauth-resource/firebase",
3
+ "version": "14.4.0",
4
+ "sideEffects": false,
5
+ "type": "module",
6
+ "peerDependencies": {
7
+ "@dereekb/date": "14.4.0",
8
+ "@dereekb/firebase": "14.4.0",
9
+ "@dereekb/model": "14.4.0",
10
+ "@dereekb/oauth-resource": "14.4.0",
11
+ "@dereekb/rxjs": "14.4.0",
12
+ "@dereekb/util": "14.4.0",
13
+ "firebase": "^12.18.0",
14
+ "make-error": "^1.3.6"
15
+ },
16
+ "exports": {
17
+ "./package.json": "./package.json",
18
+ ".": {
19
+ "types": "./index.d.ts",
20
+ "import": "./index.esm.js",
21
+ "default": "./index.esm.js"
22
+ }
23
+ },
24
+ "module": "./index.esm.js",
25
+ "main": "./index.esm.js",
26
+ "types": "./index.d.ts"
27
+ }
@@ -0,0 +1 @@
1
+ export * from './lib';
@@ -0,0 +1,165 @@
1
+ import { type Maybe } from '@dereekb/util';
2
+ /**
3
+ * The `drivers.firestoreDriverIdentifier` a client-SDK `FirestoreContext` reports.
4
+ *
5
+ * `clientFirebaseFirestoreContextFactory` stamps this; the server's
6
+ * `googleCloudFirestoreContextFactory` stamps `@google-cloud/firestore` instead. Comparing against
7
+ * it is how {@link inspectFirebaseClientFirestoreIdentity} tells an admin-SDK context that reached
8
+ * the client-SDK read path apart from a genuinely broken client one.
9
+ */
10
+ export declare const CLIENT_FIRESTORE_DRIVER_IDENTIFIER = "@firebase/firestore";
11
+ /**
12
+ * The distinct ways a session's Firestore handle can be unusable, in the order
13
+ * {@link inspectFirebaseClientFirestoreIdentity} tests them — most specific diagnosis first.
14
+ */
15
+ export type FirebaseClientFirestoreIdentityProblem =
16
+ /**
17
+ * `firestoreContext` is absent, or its `firestore` is null/undefined. Every `collection()` call
18
+ * built off it throws `Expected first argument to collection() to be a CollectionReference, a
19
+ * DocumentReference or FirebaseFirestore` — the SDK's message for ANY non-Firestore first
20
+ * argument, which is why it never named this.
21
+ */
22
+ 'no-firestore-handle'
23
+ /**
24
+ * The context reports a driver other than {@link CLIENT_FIRESTORE_DRIVER_IDENTIFIER} — an
25
+ * admin/`@google-cloud/firestore` context reached the client-SDK read path.
26
+ *
27
+ * This is the guard for the security property the whole user-scoped session path rests on: the
28
+ * client SDK is the only Firestore transport that carries a user ID token and is therefore
29
+ * rules-evaluated. An Admin-SDK context reaching here is a bug, not a fallback.
30
+ */
31
+ | 'unexpected-driver'
32
+ /**
33
+ * Two copies of `@firebase/firestore` are loaded, so the handle minted by one fails the other's
34
+ * brand check.
35
+ */
36
+ | 'duplicated-firestore-sdk'
37
+ /**
38
+ * The handle is present and the driver looks right, but it is not an instance of THIS copy of the
39
+ * SDK's `Firestore` class and no duplicate install explains it.
40
+ */
41
+ | 'foreign-firestore-instance';
42
+ /**
43
+ * Where one consumer resolved `@firebase/firestore` to, and at which version.
44
+ *
45
+ * Reported for both the calling consumer and `@dereekb/firebase` because the whole point of the
46
+ * duplicated-SDK hypothesis is that those two answers can differ.
47
+ */
48
+ export interface FirebaseClientFirestoreSdkModuleIdentity {
49
+ /**
50
+ * The resolved package directory, or `undefined` when resolution failed.
51
+ */
52
+ readonly packageDir?: Maybe<string>;
53
+ readonly version?: Maybe<string>;
54
+ /**
55
+ * Why resolution failed, when it did.
56
+ */
57
+ readonly error?: Maybe<string>;
58
+ }
59
+ /**
60
+ * The provenance + brand-check report {@link inspectFirebaseClientFirestoreIdentity} produces.
61
+ *
62
+ * Everything here is reported whether or not the check passed: a `sdkDuplicated: true` alongside
63
+ * `ok: true` is a latent hazard worth seeing before it becomes an outage.
64
+ */
65
+ export interface FirebaseClientFirestoreIdentityReport {
66
+ readonly ok: boolean;
67
+ readonly problem?: FirebaseClientFirestoreIdentityProblem;
68
+ readonly firestorePresent: boolean;
69
+ /**
70
+ * Whether the handle passes `instanceof Firestore` against the copy of `@firebase/firestore` THIS
71
+ * package loaded.
72
+ */
73
+ readonly firestoreIsSdkInstance: boolean;
74
+ /**
75
+ * The handle's constructor name, which distinguishes a duplicate `Firestore` (same name, different
76
+ * class) from a genuinely foreign object.
77
+ */
78
+ readonly firestoreConstructor?: Maybe<string>;
79
+ readonly firestoreDriverIdentifier?: Maybe<string>;
80
+ /**
81
+ * The version of the `firebase` umbrella package resolved at runtime.
82
+ */
83
+ readonly firebaseVersion?: Maybe<string>;
84
+ /**
85
+ * Where the CONSUMER (this package, or whoever supplied a `consumerRequire`) resolves the SDK.
86
+ */
87
+ readonly sdkFromConsumer: FirebaseClientFirestoreSdkModuleIdentity;
88
+ readonly sdkFromDbxFirebase: FirebaseClientFirestoreSdkModuleIdentity;
89
+ /**
90
+ * True when the two resolutions above name DIFFERENT package directories.
91
+ */
92
+ readonly sdkDuplicated: boolean;
93
+ }
94
+ /**
95
+ * The slice of a session context {@link inspectFirebaseClientFirestoreIdentity} reads.
96
+ *
97
+ * Deliberately looser than `FirestoreContext`: this check exists precisely for the case where the
98
+ * object is not the shape its declared type claims, so narrowing it here would assume away the
99
+ * failure. A real `FirebaseUserSession['firestoreContext']` is structurally assignable.
100
+ */
101
+ export interface FirebaseClientFirestoreIdentityContext {
102
+ readonly firestore?: unknown;
103
+ readonly drivers?: {
104
+ readonly firestoreDriverIdentifier?: unknown;
105
+ };
106
+ }
107
+ /**
108
+ * Input for {@link inspectFirebaseClientFirestoreIdentity}.
109
+ */
110
+ export interface InspectFirebaseClientFirestoreIdentityInput {
111
+ readonly firestoreContext?: Maybe<FirebaseClientFirestoreIdentityContext>;
112
+ /**
113
+ * `createRequire(import.meta.url)` from the CONSUMER package. The duplicated-SDK hypothesis is
114
+ * exactly "do these two consumers see the same copy", so the resolution root must be the
115
+ * caller's. Defaults to this package's own.
116
+ */
117
+ readonly consumerRequire?: Maybe<NodeJS.Require>;
118
+ /**
119
+ * Names the consumer in the suggestion text. Defaults to `'@dereekb/oauth-resource/firebase'`.
120
+ */
121
+ readonly consumerName?: Maybe<string>;
122
+ }
123
+ /**
124
+ * Default {@link InspectFirebaseClientFirestoreIdentityInput.consumerName}.
125
+ */
126
+ export declare const DEFAULT_FIRESTORE_IDENTITY_CONSUMER_NAME = "@dereekb/oauth-resource/firebase";
127
+ /**
128
+ * Checks that a session's Firestore handle is one THIS copy of the client SDK will accept, and
129
+ * reports where every consumer resolved the SDK from.
130
+ *
131
+ * Exists because `collection()` refuses any non-`Firestore` first argument with one message —
132
+ * `Expected first argument to collection() to be a CollectionReference, a DocumentReference or
133
+ * FirebaseFirestore` — that names neither the model, nor the collection, nor which of the four
134
+ * distinct causes ({@link FirebaseClientFirestoreIdentityProblem}) produced it.
135
+ *
136
+ * Lives in this package rather than in a consumer because the `instanceof Firestore` brand check
137
+ * must be performed by the module that CONSTRUCTS the handle, and that module is
138
+ * `openFirebaseUserSession`.
139
+ *
140
+ * The `instanceof` test is the load-bearing one and it is deliberately NOT structural: an identity
141
+ * check across the package boundary is the only thing that can detect a duplicated
142
+ * `@firebase/firestore`, which is exactly what a structural check would hide.
143
+ *
144
+ * @param input - The session's `firestoreContext`, when one was built, plus the consumer's
145
+ * resolution root.
146
+ * @returns The provenance + brand-check report.
147
+ */
148
+ export declare function inspectFirebaseClientFirestoreIdentity(input: InspectFirebaseClientFirestoreIdentityInput): FirebaseClientFirestoreIdentityReport;
149
+ /**
150
+ * The actionable next step for a failed {@link inspectFirebaseClientFirestoreIdentity}, keyed on
151
+ * which problem was found.
152
+ *
153
+ * Each branch names the fix rather than the symptom — a raw SDK sentence sends an operator looking at
154
+ * security rules and App Check, neither of which is ever the cause here.
155
+ *
156
+ * @param report - The report to describe.
157
+ * @param overrides - Per-problem replacement text, for a consumer whose operator-facing wording
158
+ * names its own remediation (`re-run with --verbose`, `rebuild the CLI`, …).
159
+ * @param consumerName - Names the consumer in the duplicated-SDK text. Defaults to
160
+ * {@link DEFAULT_FIRESTORE_IDENTITY_CONSUMER_NAME}.
161
+ * @returns The suggestion, or `undefined` when the report passed.
162
+ *
163
+ * @__NO_SIDE_EFFECTS__
164
+ */
165
+ export declare function firebaseClientFirestoreIdentitySuggestion(report: FirebaseClientFirestoreIdentityReport, overrides?: Maybe<Partial<Record<FirebaseClientFirestoreIdentityProblem, string>>>, consumerName?: Maybe<string>): Maybe<string>;
@@ -0,0 +1 @@
1
+ export * from './firestore.sdk-identity';
@@ -0,0 +1,2 @@
1
+ export * from './firestore';
2
+ export * from './session';
@@ -0,0 +1,86 @@
1
+ import { type Maybe, type PortNumber } from '@dereekb/util';
2
+ /**
3
+ * Local Firebase emulator targets for a Firebase client config.
4
+ *
5
+ * Mirrors the semantics of `DbxFirebaseEmulatorsConfig` in `@dereekb/dbx-firebase` (whose parse
6
+ * helper is Angular-bound and not reusable here): the presence of this object means "use emulators"
7
+ * unless {@link useEmulators} is explicitly `false`.
8
+ *
9
+ * App Check is auto-disabled whenever emulators are in use — the emulators do not verify
10
+ * attestations, and `initializeAppCheck` against a fake project only gets in the way.
11
+ */
12
+ export interface FirebaseClientEmulatorsConfig {
13
+ /**
14
+ * Set `false` to keep the emulator targets configured but inactive. Defaults to `true`.
15
+ */
16
+ readonly useEmulators?: boolean;
17
+ /**
18
+ * Host the emulators are reachable at. Defaults to {@link DEFAULT_FIREBASE_CLIENT_EMULATOR_HOST}.
19
+ */
20
+ readonly host?: string;
21
+ /**
22
+ * Port of the Auth emulator. When unset, Auth is not redirected to an emulator.
23
+ */
24
+ readonly authPort?: PortNumber;
25
+ /**
26
+ * Port of the Firestore emulator. When unset, Firestore is not redirected to an emulator.
27
+ */
28
+ readonly firestorePort?: PortNumber;
29
+ }
30
+ /**
31
+ * Firebase client-SDK configuration used to open a user-scoped Firestore session.
32
+ *
33
+ * These are the same public values the app's browser client initializes with — copy them from the
34
+ * target app's environment file. `appId` in particular must be the registered **web** app, since the
35
+ * server mints its App Check attestation for that app.
36
+ */
37
+ export interface FirebaseClientConfig {
38
+ /**
39
+ * The Firebase web API key.
40
+ */
41
+ readonly apiKey?: string;
42
+ /**
43
+ * The project's auth domain (e.g. `my-project.firebaseapp.com`).
44
+ */
45
+ readonly authDomain?: string;
46
+ /**
47
+ * The Firebase project id.
48
+ */
49
+ readonly projectId?: string;
50
+ /**
51
+ * The registered **web** app id (e.g. `1:1234567890:web:abcdef`).
52
+ */
53
+ readonly appId?: string;
54
+ /**
55
+ * Optional emulator targets for local development.
56
+ */
57
+ readonly emulators?: FirebaseClientEmulatorsConfig;
58
+ }
59
+ /**
60
+ * A {@link FirebaseClientConfig} carrying everything a session needs to initialize an app.
61
+ */
62
+ export type CompleteFirebaseClientConfig = Required<Pick<FirebaseClientConfig, 'apiKey' | 'projectId' | 'appId'>> & FirebaseClientConfig;
63
+ /**
64
+ * Default host used for Firebase emulator connections when a {@link FirebaseClientEmulatorsConfig}
65
+ * omits one.
66
+ */
67
+ export declare const DEFAULT_FIREBASE_CLIENT_EMULATOR_HOST = "localhost";
68
+ /**
69
+ * Returns true when the config carries the minimum Firebase client values needed to open a
70
+ * user-scoped Firestore session.
71
+ *
72
+ * Deliberately a standalone predicate rather than a field of a wider "config is complete" check: a
73
+ * host's Firebase config is optional, and folding it into a general completeness check would break
74
+ * every consumer that only talks to the HTTP API.
75
+ *
76
+ * @param firebase - The Firebase client config, if any.
77
+ * @returns `true` when `apiKey`, `projectId`, and `appId` are all present and non-empty.
78
+ */
79
+ export declare function isFirebaseClientConfigComplete(firebase: Maybe<FirebaseClientConfig>): firebase is CompleteFirebaseClientConfig;
80
+ /**
81
+ * Returns true when the config's emulator targets are present and active.
82
+ *
83
+ * @param firebase - The Firebase client config, if any.
84
+ * @returns `true` when emulators are configured and not explicitly disabled.
85
+ */
86
+ export declare function firebaseClientEmulatorsInUse(firebase: Maybe<FirebaseClientConfig>): boolean;
@@ -0,0 +1,223 @@
1
+ import { type FirebaseApp } from 'firebase/app';
2
+ import { type Auth } from 'firebase/auth';
3
+ import { type Firestore } from 'firebase/firestore';
4
+ import { type FirebaseAuthUserId, type FirestoreContext } from '@dereekb/firebase';
5
+ import { type Getter, type Maybe, type Milliseconds, type UnixDateTimeMillisecondsNumber, type WebsiteUrl } from '@dereekb/util';
6
+ import { type FirebaseClientConfig } from './firebase-client.config';
7
+ import { type FirestoreSessionCredentials, type FirestoreSessionErrorFactory } from './firestore-session.client';
8
+ import { type FirestoreSessionCredentialsCache } from './firestore-session.cache';
9
+ /**
10
+ * Separator between the segments of a {@link firebaseUserSessionAppName}.
11
+ *
12
+ * `::` rather than `-` because a Firebase project id is `[a-z0-9-]+`, so a single `-` makes
13
+ * `(ns='srv', scope='a-b', uid='c')` and `(ns='srv', scope='a', uid='b-c')` collide on `srv-a-b-c`.
14
+ * `:` cannot appear in a project id, so `::` is collision-free for any namespace that does not itself
15
+ * contain `::`.
16
+ */
17
+ export declare const FIREBASE_USER_SESSION_KEY_SEPARATOR = "::";
18
+ export interface FirebaseUserSessionKeyInput {
19
+ /**
20
+ * The teardown-sweep key — every app this owner registered shares it as a prefix.
21
+ *
22
+ * Required rather than defaulted: a silent default lets two independently-configured pools in one
23
+ * process sweep each other's apps.
24
+ */
25
+ readonly namespace: string;
26
+ /**
27
+ * Discriminates sessions that share a uid but not a target. Normally the Firebase project id.
28
+ */
29
+ readonly scope: string;
30
+ readonly uid: FirebaseAuthUserId;
31
+ }
32
+ /**
33
+ * The base Firebase app name a user-scoped session registers.
34
+ *
35
+ * A DERIVED name rather than a side registry is what lets {@link closeFirebaseUserSessionApps} find
36
+ * every app this owner opened from `getApps()` alone — no registry to keep in sync, and idempotent by
37
+ * construction.
38
+ *
39
+ * Note this is the BASE name: {@link openFirebaseUserSession} appends `#2`, `#3`, … when the base is
40
+ * already taken, because a session that mints its own credentials must never reuse an existing app
41
+ * registration (see the note on {@link openFirebaseUserSession}).
42
+ *
43
+ * @param input - The namespace, scope, and uid the session targets.
44
+ * @returns The base Firebase app name.
45
+ * @__NO_SIDE_EFFECTS__
46
+ */
47
+ export declare function firebaseUserSessionAppName(input: FirebaseUserSessionKeyInput): string;
48
+ /**
49
+ * The `getApps()` name prefix every session opened under a namespace shares.
50
+ *
51
+ * @param namespace - The namespace to sweep.
52
+ * @returns The app-name prefix.
53
+ * @__NO_SIDE_EFFECTS__
54
+ */
55
+ export declare function firebaseUserSessionNamespacePrefix(namespace: string): string;
56
+ /**
57
+ * A live user-scoped Firestore session: the Firebase client objects signed in as the user, plus the
58
+ * `FirestoreContext` an app's collections factory consumes.
59
+ *
60
+ * The `firestoreContext` is built by `clientFirebaseFirestoreContextFactory`, the exact analogue of
61
+ * the server's `googleCloudFirestoreContextFactory` — both satisfy `FirestoreContextFactory` — so an
62
+ * app's `make<App>FirestoreCollections(context)` accepts it unchanged, and the holder runs the SAME
63
+ * queries the Angular app runs, through the SAME security rules.
64
+ *
65
+ * This is deliberately NOT an Admin-SDK "act as user" path: the client SDK is the only Firestore
66
+ * transport that carries a user ID token and is therefore rules-evaluated.
67
+ */
68
+ export interface FirebaseUserSession {
69
+ readonly uid: FirebaseAuthUserId;
70
+ /**
71
+ * The credential bundle the API minted for this session.
72
+ */
73
+ readonly credentials: FirestoreSessionCredentials;
74
+ /**
75
+ * True when {@link credentials} came from a supplied cache rather than a fresh mint. Diagnostic
76
+ * only — never control flow.
77
+ */
78
+ readonly fromCache: boolean;
79
+ readonly appName: string;
80
+ readonly app: FirebaseApp;
81
+ readonly auth: Auth;
82
+ readonly firestore: Firestore;
83
+ readonly firestoreContext: FirestoreContext;
84
+ readonly createdAt: UnixDateTimeMillisecondsNumber;
85
+ /**
86
+ * `min(credentials.expiresAt, createdAt + maxSessionAgeMs)`, falling back to the ceiling when the
87
+ * API returned an unparsable timestamp.
88
+ */
89
+ readonly expiresAt: UnixDateTimeMillisecondsNumber;
90
+ }
91
+ export interface OpenFirebaseUserSessionInput {
92
+ /**
93
+ * The teardown-sweep key. See {@link FirebaseUserSessionKeyInput.namespace}.
94
+ */
95
+ readonly namespace: string;
96
+ /**
97
+ * Discriminates sessions sharing a uid. Defaults to `firebase.projectId`.
98
+ */
99
+ readonly scope?: Maybe<string>;
100
+ readonly firebase: Maybe<FirebaseClientConfig>;
101
+ readonly apiBaseUrl: WebsiteUrl;
102
+ readonly accessToken: string;
103
+ /**
104
+ * When set, asserted against the minted credentials' uid.
105
+ *
106
+ * This makes the mint endpoint's central security property — "the custom token is always minted
107
+ * for `auth.uid`, with no way to name another user" — locally checkable at the CONSUME side, which
108
+ * matters far more for a server holding N users than for a one-user CLI.
109
+ */
110
+ readonly uid?: Maybe<FirebaseAuthUserId>;
111
+ readonly fetcher?: Maybe<typeof fetch>;
112
+ readonly errorFactory?: Maybe<FirestoreSessionErrorFactory>;
113
+ /**
114
+ * Optional credentials cache. When supplied, a live cached envelope for {@link cacheKey} is reused
115
+ * instead of re-minting one, and a freshly minted envelope is written back.
116
+ */
117
+ readonly credentialsCache?: Maybe<FirestoreSessionCredentialsCache>;
118
+ /**
119
+ * Key to read/write in {@link credentialsCache}. Defaults to the session's uid.
120
+ */
121
+ readonly cacheKey?: Maybe<string>;
122
+ /**
123
+ * Skips the cache read for this call and re-mints, still writing the result back. Used by
124
+ * diagnostics and by the retry after a sign-in failure.
125
+ */
126
+ readonly refreshCredentials?: boolean;
127
+ /**
128
+ * Hard ceiling on the session's usable age. Defaults to {@link FIRESTORE_SESSION_MAX_CACHE_MS}.
129
+ */
130
+ readonly maxSessionAgeMs?: Maybe<Milliseconds>;
131
+ /**
132
+ * Clock seam, for deterministic tests.
133
+ */
134
+ readonly now?: Maybe<Getter<UnixDateTimeMillisecondsNumber>>;
135
+ }
136
+ /**
137
+ * Opens a user-scoped Firestore session. The seam a pool (or a test) substitutes.
138
+ */
139
+ export type FirebaseUserSessionOpener = (input: OpenFirebaseUserSessionInput) => Promise<FirebaseUserSession>;
140
+ /**
141
+ * Opens a direct Firestore connection as the user the presented access token belongs to.
142
+ *
143
+ * Steps, in a strict order:
144
+ *
145
+ * 0. When a `credentialsCache` is supplied, reuse the cached credential envelope if it is still live.
146
+ * Sessions are cached for up to an hour (see {@link FIRESTORE_SESSION_MAX_CACHE_MS}), which is the
147
+ * ceiling the Firebase credentials themselves sit under. A hit skips step 1 only — the Firebase
148
+ * app is per-session, so the sign-in in step 5 always runs.
149
+ * 1. `GET <apiBaseUrl>/session/firestore` for a custom token + App Check attestation.
150
+ * 2. `initializeApp` under the first FREE name derived from {@link firebaseUserSessionAppName}.
151
+ *
152
+ * **Never reuse an existing registration.** `initializeAppCheck` on an app whose App Check
153
+ * provider is already initialized silently returns the EXISTING instance when
154
+ * `CustomProvider.isEqual` matches — and `isEqual` compares `getToken.toString()`, the source text
155
+ * of the arrow function, which is byte-identical across two closures built at this call site over
156
+ * different tokens. Reusing an app therefore keeps the FIRST attestation and drops the freshly
157
+ * minted one on the floor, so requests go out under a stale (eventually expired) App Check token.
158
+ * App reuse is the POOL's responsibility, at the session-object level, never at the app-registry
159
+ * level.
160
+ * 3. `initializeAppCheck` with a `CustomProvider` handing back the server-minted token. **This must
161
+ * happen before any other Firebase call** — `dbx-firebase`'s provider documents the same
162
+ * constraint: "App Check must be initialized before any Firebase request goes out, otherwise
163
+ * requests are sent without an App Check token and are rejected in production." Skipped when the
164
+ * config targets emulators (which do not verify attestations) or when the API minted no token.
165
+ * 4. `getAuth` / `getFirestore`, connecting each to its emulator when configured.
166
+ * 5. `signInWithCustomToken`. The user's stored custom claims land at the top level of the exchanged
167
+ * ID token, so `request.auth.token.<claim>` reads in security rules behave exactly as in the app.
168
+ *
169
+ * There is deliberately NO fallback to an Admin-SDK context — the whole point is that rules stay in
170
+ * force, so a failure here throws.
171
+ *
172
+ * @param input - The namespace, Firebase client config, API target, and access token.
173
+ * @returns The live {@link FirebaseUserSession}.
174
+ * @throws {FirestoreSessionError} (or the `errorFactory`'s type) When the config is incomplete, or any step of the handshake fails.
175
+ */
176
+ export declare function openFirebaseUserSession(input: OpenFirebaseUserSessionInput): Promise<FirebaseUserSession>;
177
+ /**
178
+ * Tears down a session opened by {@link openFirebaseUserSession}.
179
+ *
180
+ * Required, not optional hygiene. A signed-in `Auth` runs a token-refresh timer and a live
181
+ * `Firestore` holds open handles, so both leak per user without teardown — and in a CLI-shaped
182
+ * process they keep the Node event loop alive indefinitely, so the process never exits.
183
+ *
184
+ * `deleteApp` is the single call that covers it — it disposes every registered component, which for
185
+ * Firestore runs the same shutdown `terminate()` does, and for Auth stops the token-refresh timer.
186
+ *
187
+ * Deliberately tolerant: teardown normally runs after the caller's result is already produced, so a
188
+ * failure here must not change the outcome.
189
+ *
190
+ * @param session - The session to close.
191
+ */
192
+ export declare function closeFirebaseUserSession(session: Pick<FirebaseUserSession, 'app'>): Promise<void>;
193
+ /**
194
+ * Deletes one Firebase app, swallowing any failure.
195
+ *
196
+ * @param app - The app to delete.
197
+ */
198
+ export declare function closeFirebaseUserSessionApp(app: FirebaseApp): Promise<void>;
199
+ /**
200
+ * Deletes every still-live Firebase app opened under a namespace, whether or not a session was
201
+ * handed back.
202
+ *
203
+ * The last line of defence against a leak. {@link closeFirebaseUserSession} covers the normal path,
204
+ * but it needs a session to be handed to it, and three cases never produce one:
205
+ *
206
+ * - a handshake that fails AFTER `initializeApp` — a rejected custom token, a failed App Check
207
+ * registration — throws, so the caller that catches it has an initialized app and no session;
208
+ * - a caller that opens its own session outside a pool owns its own teardown, and forgetting it
209
+ * leaks;
210
+ * - an owner orphaned mid-invocation carries the only reference to its session.
211
+ *
212
+ * `getApps()` already tracks every live app and `deleteApp` removes it from that list, so the app
213
+ * names {@link firebaseUserSessionAppName} derives are enough to find them again — no side registry
214
+ * to keep in sync, and idempotent by construction.
215
+ *
216
+ * @param input - The function inputs.
217
+ * @param input.namespace - The namespace whose apps should be closed. Apps belonging to other
218
+ * Firebase consumers in the same process are left alone.
219
+ * @returns Resolves once every matching app has been deleted.
220
+ */
221
+ export declare function closeFirebaseUserSessionApps(input: {
222
+ readonly namespace: string;
223
+ }): Promise<void>;