@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.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/express/index.d.ts +1 -0
- package/express/index.esm.js +321 -0
- package/express/package.json +29 -0
- package/express/src/index.d.ts +1 -0
- package/express/src/lib/bearer.middleware.d.ts +78 -0
- package/express/src/lib/index.d.ts +2 -0
- package/express/src/lib/well-known.router.d.ts +35 -0
- package/firebase/index.d.ts +1 -0
- package/firebase/index.esm.js +1924 -0
- package/firebase/package.json +27 -0
- package/firebase/src/index.d.ts +1 -0
- package/firebase/src/lib/firestore/firestore.sdk-identity.d.ts +165 -0
- package/firebase/src/lib/firestore/index.d.ts +1 -0
- package/firebase/src/lib/index.d.ts +2 -0
- package/firebase/src/lib/session/firebase-client.config.d.ts +86 -0
- package/firebase/src/lib/session/firebase-user-session.d.ts +223 -0
- package/firebase/src/lib/session/firebase-user-session.pool.d.ts +168 -0
- package/firebase/src/lib/session/firestore-session.cache.d.ts +79 -0
- package/firebase/src/lib/session/firestore-session.client.d.ts +149 -0
- package/firebase/src/lib/session/index.d.ts +5 -0
- package/index.d.ts +1 -0
- package/index.esm.js +1066 -0
- package/package.json +53 -0
- package/src/index.d.ts +1 -0
- package/src/lib/auth/index.d.ts +1 -0
- package/src/lib/auth/oauth.resource.auth.d.ts +55 -0
- package/src/lib/challenge/bearer.challenge.d.ts +73 -0
- package/src/lib/challenge/index.d.ts +1 -0
- package/src/lib/error/index.d.ts +1 -0
- package/src/lib/error/oauth.resource.error.d.ts +76 -0
- package/src/lib/index.d.ts +6 -0
- package/src/lib/issuer/index.d.ts +1 -0
- package/src/lib/issuer/issuer.profile.d.ts +98 -0
- package/src/lib/metadata/index.d.ts +1 -0
- package/src/lib/metadata/protected-resource.metadata.d.ts +64 -0
- package/src/lib/verify/index.d.ts +1 -0
- 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,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>;
|