@dereekb/firebase 14.0.1 → 14.2.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/eslint/index.esm.js +0 -194
- package/eslint/package.json +4 -3
- package/index.esm.js +352 -8
- package/package.json +5 -5
- package/src/lib/common/firestore/snapshot/snapshot.field.d.ts +67 -0
- package/src/lib/model/calendar/calendar.d.ts +1 -1
- package/src/lib/model/calendar/calendar.id.d.ts +6 -0
- package/src/lib/model/formspace/formspace.d.ts +6 -0
- package/src/lib/model/userexternalconnection/index.d.ts +1 -0
- package/src/lib/model/userexternalconnection/userexternalconnection.api.d.ts +41 -1
- package/src/lib/model/userexternalconnection/userexternalconnection.d.ts +115 -5
- package/src/lib/model/userexternalconnection/userexternalconnection.error.d.ts +41 -0
- package/src/lib/model/userexternalconnection/userexternalconnection.id.d.ts +34 -0
- package/src/lib/model/userexternalconnection/userexternalconnection.query.d.ts +22 -1
- package/src/lib/model/userexternalconnection/userexternalconnection.util.d.ts +138 -3
- package/test/package.json +7 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase",
|
|
3
|
-
"version": "14.0
|
|
3
|
+
"version": "14.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"@dereekb/date": "14.0
|
|
26
|
-
"@dereekb/model": "14.0
|
|
27
|
-
"@dereekb/rxjs": "14.0
|
|
28
|
-
"@dereekb/util": "14.0
|
|
25
|
+
"@dereekb/date": "14.2.0",
|
|
26
|
+
"@dereekb/model": "14.2.0",
|
|
27
|
+
"@dereekb/rxjs": "14.2.0",
|
|
28
|
+
"@dereekb/util": "14.2.0",
|
|
29
29
|
"@firebase/rules-unit-testing": "5.0.2",
|
|
30
30
|
"@marcbachmann/cel-js": "^8.0.0",
|
|
31
31
|
"@typescript-eslint/parser": "8.69.0",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
* - **Arrays**: `firestoreArray`, `firestoreUniqueArray`, `firestoreEnumArray`, `firestoreEncodedArray`
|
|
26
26
|
* - **Maps**: `firestoreMap`, `firestoreEncodedObjectMap`, `firestoreObjectMap`, `firestoreArrayMap`
|
|
27
27
|
* - **Objects**: `firestoreSubObject`, `firestoreObjectArray`
|
|
28
|
+
* - **Unmodelled json**: `optionalFirestorePassthroughJsonField` (native map, queryable),
|
|
29
|
+
* `optionalFirestoreJsonStringField` (serialized string, holds json a map cannot)
|
|
28
30
|
* - **Specialized**: `firestoreUID`, `firestoreLatLngString`, `firestoreWebsiteLink`,
|
|
29
31
|
* `firestoreDateCellRange`, `firestoreBitwiseSet`, `firestoreUnitedStatesAddress`
|
|
30
32
|
*/
|
|
@@ -378,6 +380,71 @@ export interface OptionalFirestorePassthroughJsonFieldConfig<T extends object> e
|
|
|
378
380
|
* @__NO_SIDE_EFFECTS__
|
|
379
381
|
*/
|
|
380
382
|
export declare function optionalFirestorePassthroughJsonField<T extends object>(config?: OptionalFirestorePassthroughJsonFieldConfig<T>): FirestoreModelFieldMapFunctionsConfig<Maybe<T>, Maybe<T>>;
|
|
383
|
+
/**
|
|
384
|
+
* Configuration for {@link optionalFirestoreJsonStringField}.
|
|
385
|
+
*
|
|
386
|
+
* Extends {@link CopyValueDeepConfig} for parity with {@link optionalFirestorePassthroughJsonField}, so a
|
|
387
|
+
* field can move between the two without its write-side filtering changing meaning.
|
|
388
|
+
*
|
|
389
|
+
* `defaultReadValue` is deliberately absent: the base field applies it in DATA space, which here is the
|
|
390
|
+
* serialized string rather than the object, and a default expressed as raw json is a worse thing to
|
|
391
|
+
* write than the `?? {}` at the read site it would replace. That leaves nothing for a type parameter to
|
|
392
|
+
* describe, so — unlike its passthrough sibling — this config is not generic.
|
|
393
|
+
*/
|
|
394
|
+
export interface OptionalFirestoreJsonStringFieldConfig extends CopyValueDeepConfig {
|
|
395
|
+
/**
|
|
396
|
+
* Whether to store `null` instead of a value that has no keys left after filtering. Defaults to `false`.
|
|
397
|
+
*/
|
|
398
|
+
readonly dontStoreIfEmpty?: boolean;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Creates a field mapping configuration for an optional object field that is stored as a JSON STRING.
|
|
402
|
+
*
|
|
403
|
+
* The counterpart to {@link optionalFirestorePassthroughJsonField}, and the one to reach for when the
|
|
404
|
+
* json is arbitrary rather than merely unmodelled: a json schema, a tool definition, whatever an llm
|
|
405
|
+
* returned. The passthrough field stores a native Firestore map, and a map cannot represent every legal
|
|
406
|
+
* json value — Firestore forbids an array directly inside an array, which an array-valued `enum`,
|
|
407
|
+
* `const`, `default`, or `examples` produces immediately. That write does not degrade, it FAILS, and it
|
|
408
|
+
* fails from inside whatever was doing the writing with an opaque "invalid nested entity" error.
|
|
409
|
+
*
|
|
410
|
+
* Serializing sidesteps the entire Firestore type system: the stored value is one string, so anything
|
|
411
|
+
* `JSON.stringify` accepts round-trips exactly, including the shapes a map rejects. The cost is that the
|
|
412
|
+
* field is no longer queryable and no longer readable in the Firestore console — pick this one when the
|
|
413
|
+
* json is never a query target, and the passthrough field when it is.
|
|
414
|
+
*
|
|
415
|
+
* Two behaviours worth knowing:
|
|
416
|
+
*
|
|
417
|
+
* - A value only `JSON.stringify` knows how to narrow is narrowed. A `Date` becomes an ISO string and
|
|
418
|
+
* stays a string on read, where the passthrough field would have kept it a `Timestamp`. Anything
|
|
419
|
+
* carrying non-json values wants the passthrough field, not this one.
|
|
420
|
+
* - Reads tolerate a legacy native map, so a field migrated from
|
|
421
|
+
* {@link optionalFirestorePassthroughJsonField} keeps reading documents written before the switch. New
|
|
422
|
+
* writes are always strings, so a document converts itself the next time it is written.
|
|
423
|
+
*
|
|
424
|
+
* @param config - Filtering and storage configuration. Defaults to stripping `undefined` values at every depth.
|
|
425
|
+
* @returns A field mapping configuration for optional json values stored as a string.
|
|
426
|
+
*
|
|
427
|
+
* @dbxModelSnapshotField
|
|
428
|
+
* @dbxModelSnapshotFieldCategory object
|
|
429
|
+
* @dbxModelSnapshotFieldOptional true
|
|
430
|
+
* @dbxModelSnapshotFieldTags json, string, serialized, stringify, object, raw, optional, arbitrary, schema, factory
|
|
431
|
+
* @dbxModelSnapshotFieldRelated optional-firestore-passthrough-json-field, optional-firestore-field, firestore-sub-object
|
|
432
|
+
* @template T - Type of the model field. Stored as a json string.
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* fields: {
|
|
437
|
+
* // { model: 'm', text: { format: { schema: { enum: [['a']] } } } }
|
|
438
|
+
* // stores as the string '{"model":"m","text":{"format":{"schema":{"enum":[["a"]]}}}}'
|
|
439
|
+
* config: optionalFirestoreJsonStringField<MyVendorConfig>(),
|
|
440
|
+
* // store null rather than the string '{}' when nothing survives the filtering
|
|
441
|
+
* usage: optionalFirestoreJsonStringField<MyVendorUsage>({ filterEmptyValues: true, dontStoreIfEmpty: true })
|
|
442
|
+
* }
|
|
443
|
+
* ```
|
|
444
|
+
*
|
|
445
|
+
* @__NO_SIDE_EFFECTS__
|
|
446
|
+
*/
|
|
447
|
+
export declare function optionalFirestoreJsonStringField<T extends object>(config?: OptionalFirestoreJsonStringFieldConfig): FirestoreModelFieldMapFunctionsConfig<Maybe<T>, Maybe<string>>;
|
|
381
448
|
/**
|
|
382
449
|
* Configuration for a Firestore field with default model value but without conversion functions.
|
|
383
450
|
*
|
|
@@ -242,7 +242,7 @@ export declare const calendarEventItemFields: {
|
|
|
242
242
|
st: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<CalendarEventStatus>, Maybe<CalendarEventStatus>>;
|
|
243
243
|
q: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<number>, Maybe<number>>;
|
|
244
244
|
ca: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string[]>, Maybe<string[]>>;
|
|
245
|
-
x: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<Readonly<Record<string, string>>>, Maybe<
|
|
245
|
+
x: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<Readonly<Record<string, string>>>, Maybe<string>>;
|
|
246
246
|
cat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, number>;
|
|
247
247
|
uat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, number>;
|
|
248
248
|
};
|
|
@@ -70,6 +70,12 @@ export type CalendarType = string;
|
|
|
70
70
|
*
|
|
71
71
|
* Keys are stored WITHOUT the "X-" prefix and are prefixed at emit time, which is what makes it impossible
|
|
72
72
|
* for a stored key to shadow a standard property like SUMMARY.
|
|
73
|
+
*
|
|
74
|
+
* Persisted as a json STRING rather than a native Firestore map, for consistency with the other
|
|
75
|
+
* unmodelled-json fields in the workspace. Nothing here forces that — a flat map of strings is a shape
|
|
76
|
+
* Firestore stores perfectly well — so the reason is uniformity plus room for the type to widen, not a
|
|
77
|
+
* limit this type can currently reach. Nothing queries into it, which is the condition that makes string
|
|
78
|
+
* storage free.
|
|
73
79
|
*/
|
|
74
80
|
export type CalendarExtensionData = Readonly<Record<string, string>>;
|
|
75
81
|
/**
|
|
@@ -162,6 +162,12 @@ export declare const formSpaceFileSubObject: import("../..").FirestoreSubObjectF
|
|
|
162
162
|
*
|
|
163
163
|
* PASS-THROUGH: the framework never interprets it. The type's handler is what gives it meaning, and an app
|
|
164
164
|
* narrows this generic to its own interface at the point it reads the space.
|
|
165
|
+
*
|
|
166
|
+
* Persisted as a json STRING rather than a native Firestore map, because this is genuinely arbitrary json
|
|
167
|
+
* and a map cannot hold all of it: Firestore forbids an array directly inside an array, which a form
|
|
168
|
+
* reaches the moment a field holds a grid, a matrix, or a repeated group of multi-selects. That write
|
|
169
|
+
* FAILS rather than degrading, so the shape a form could submit would otherwise be bounded by the storage
|
|
170
|
+
* rather than by the type. Nothing queries into `d`, which is what makes string storage free here.
|
|
165
171
|
*/
|
|
166
172
|
export type FormSpaceData = Record<string, unknown>;
|
|
167
173
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Type } from 'arktype';
|
|
2
|
+
import { type Maybe } from '@dereekb/util';
|
|
2
3
|
import { type InferredTargetModelParams } from '../../common/model/model/model.param';
|
|
3
4
|
import { type FirebaseFunctionTypeConfigMap, type ModelFirebaseCreateFunction, type ModelFirebaseCrudFunction, type ModelFirebaseCrudFunctionConfigMap, type ModelFirebaseFunctionMap } from '../../client';
|
|
4
5
|
import { type UserExternalConnectionTypes } from './userexternalconnection';
|
|
@@ -41,8 +42,38 @@ export interface ReadUserExternalConnectionAuthorizeStateParams extends Inferred
|
|
|
41
42
|
* The provider type to begin connecting to.
|
|
42
43
|
*/
|
|
43
44
|
readonly providerType: UserExternalConnectionProviderType;
|
|
45
|
+
/**
|
|
46
|
+
* Which handoff the state begins. Defaults to `connect`.
|
|
47
|
+
*
|
|
48
|
+
* - `connect` — attach the provider as a DATA connection, with the data scopes.
|
|
49
|
+
* - `link` — make the provider a LOGIN METHOD for the already-signed-in caller, with the sign-in
|
|
50
|
+
* scopes. A separate round trip because the two scope sets are not guaranteed to be the same,
|
|
51
|
+
* so a data grant cannot be assumed to cover an identity read.
|
|
52
|
+
*
|
|
53
|
+
* Absent means `connect`, so a client minting a state before `link` existed still gets one.
|
|
54
|
+
*/
|
|
55
|
+
readonly mode?: Maybe<'connect' | 'link'>;
|
|
44
56
|
}
|
|
45
57
|
export declare const readUserExternalConnectionAuthorizeStateParamsType: Type<ReadUserExternalConnectionAuthorizeStateParams>;
|
|
58
|
+
/**
|
|
59
|
+
* Parameters for removing a provider as a LOGIN METHOD for the current user.
|
|
60
|
+
*
|
|
61
|
+
* Distinct from {@link DisconnectUserExternalConnectionParams}, and strictly larger: a disconnect
|
|
62
|
+
* drops the data connection and leaves the login link in place, whereas an unlink removes the login
|
|
63
|
+
* link AND the data connection and its credentials. "Stop using my Discord token" and "Discord is no
|
|
64
|
+
* longer how I log in" are different requests, so they are different calls.
|
|
65
|
+
*
|
|
66
|
+
* If no target model is provided, the current user's connection document is assumed.
|
|
67
|
+
*
|
|
68
|
+
* @dbxModelApiParams
|
|
69
|
+
*/
|
|
70
|
+
export interface UnlinkUserExternalConnectionLoginParams extends InferredTargetModelParams {
|
|
71
|
+
/**
|
|
72
|
+
* The provider type to unlink.
|
|
73
|
+
*/
|
|
74
|
+
readonly providerType: UserExternalConnectionProviderType;
|
|
75
|
+
}
|
|
76
|
+
export declare const unlinkUserExternalConnectionLoginParamsType: Type<UnlinkUserExternalConnectionLoginParams>;
|
|
46
77
|
/**
|
|
47
78
|
* The opaque, short-lived `state` to carry through a provider's OAuth handoff.
|
|
48
79
|
*
|
|
@@ -91,9 +122,17 @@ export type UserExternalConnectionModelCrudFunctionsConfig = {
|
|
|
91
122
|
* Disconnects the current user from the given provider.
|
|
92
123
|
*
|
|
93
124
|
* Removes the provider's credentials and its entry in one transaction, and recomputes the
|
|
94
|
-
* connected-provider array from the result.
|
|
125
|
+
* connected-provider array from the result. The provider's LOGIN LINK is retained — a data
|
|
126
|
+
* connection ending says nothing about whether the provider is still a way to sign in.
|
|
95
127
|
*/
|
|
96
128
|
disconnect: DisconnectUserExternalConnectionParams;
|
|
129
|
+
/**
|
|
130
|
+
* Removes the given provider as a login method for the current user.
|
|
131
|
+
*
|
|
132
|
+
* Removes the login link, the entry, and the credentials in one transaction. Refused when it
|
|
133
|
+
* would leave the account with no way to sign back in.
|
|
134
|
+
*/
|
|
135
|
+
unlink: UnlinkUserExternalConnectionLoginParams;
|
|
97
136
|
};
|
|
98
137
|
};
|
|
99
138
|
};
|
|
@@ -111,6 +150,7 @@ export declare abstract class UserExternalConnectionFunctions implements ModelFi
|
|
|
111
150
|
};
|
|
112
151
|
updateUserExternalConnection: {
|
|
113
152
|
disconnect: ModelFirebaseCrudFunction<DisconnectUserExternalConnectionParams>;
|
|
153
|
+
unlink: ModelFirebaseCrudFunction<UnlinkUserExternalConnectionLoginParams>;
|
|
114
154
|
};
|
|
115
155
|
};
|
|
116
156
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { type Maybe } from '@dereekb/util';
|
|
1
|
+
import { type EmailAddress, type Maybe } from '@dereekb/util';
|
|
2
2
|
import { type GrantedReadRole, type GrantedUpdateRole } from '@dereekb/model';
|
|
3
3
|
import { AbstractFirestoreDocument, type CollectionReference, type FirestoreCollection, type FirestoreContext } from '../../common';
|
|
4
4
|
import { type UserRelated, type UserRelatedById } from '../user';
|
|
5
|
-
import { type UserExternalConnectionCapability, type UserExternalConnectionExternalAccountId, type UserExternalConnectionProviderType } from './userexternalconnection.id';
|
|
5
|
+
import { type UserExternalConnectionCapability, type UserExternalConnectionExternalAccountId, type UserExternalConnectionExternalAccountKey, type UserExternalConnectionProviderType } from './userexternalconnection.id';
|
|
6
6
|
/**
|
|
7
7
|
* Provides access to the {@link UserExternalConnection} collection.
|
|
8
8
|
*
|
|
@@ -99,6 +99,69 @@ export interface UserExternalConnectionEntry {
|
|
|
99
99
|
* Map of provider type to the user's connection state for that provider.
|
|
100
100
|
*/
|
|
101
101
|
export type UserExternalConnectionEntryMap = Record<UserExternalConnectionProviderType, UserExternalConnectionEntry>;
|
|
102
|
+
/**
|
|
103
|
+
* A provider that is a LOGIN METHOD for this account.
|
|
104
|
+
*
|
|
105
|
+
* Separate from {@link UserExternalConnectionEntry} rather than a flag on it, because the two describe
|
|
106
|
+
* different kinds of fact and have different lifecycles. Every field on an entry is DERIVED from the
|
|
107
|
+
* credentials stored beside it — take the credentials away and the entry has nothing left to say — so
|
|
108
|
+
* an entry is a statement about a grant. A login link is a statement about the ACCOUNT: "this Discord
|
|
109
|
+
* user is how this person signs in". It survives the credentials expiring, being revoked by the
|
|
110
|
+
* provider, and the user disconnecting the data connection, and it is removed only by an explicit
|
|
111
|
+
* unlink.
|
|
112
|
+
*
|
|
113
|
+
* Folding it into the entry would mean a disconnect had to choose between destroying the sign-in
|
|
114
|
+
* binding and retaining a `disconnected` entry that lies about the credentials. Two maps make the
|
|
115
|
+
* choice unnecessary.
|
|
116
|
+
*
|
|
117
|
+
* Written ONLY by an identity-scoped OAuth round trip (the sign-in or `link` direction), never by the
|
|
118
|
+
* data connect flow: the scopes a data connection is granted are not guaranteed to cover what an
|
|
119
|
+
* identity read needs.
|
|
120
|
+
*
|
|
121
|
+
* @dbxModelSubObject
|
|
122
|
+
*/
|
|
123
|
+
export interface UserExternalConnectionLogin {
|
|
124
|
+
/**
|
|
125
|
+
* Identifier of the linked account within the provider. REQUIRED: this IS the identity.
|
|
126
|
+
*
|
|
127
|
+
* @dbxModelVariable externalAccountId
|
|
128
|
+
*/
|
|
129
|
+
ea: UserExternalConnectionExternalAccountId;
|
|
130
|
+
/**
|
|
131
|
+
* Human-readable label for the linked account (e.g. the provider-side username).
|
|
132
|
+
*
|
|
133
|
+
* @dbxModelVariable label
|
|
134
|
+
*/
|
|
135
|
+
l?: Maybe<string>;
|
|
136
|
+
/**
|
|
137
|
+
* The email the provider reported at link time, when it reported one.
|
|
138
|
+
*
|
|
139
|
+
* @dbxModelVariable email
|
|
140
|
+
*/
|
|
141
|
+
em?: Maybe<EmailAddress>;
|
|
142
|
+
/**
|
|
143
|
+
* Whether the PROVIDER considered that email verified at link time.
|
|
144
|
+
*
|
|
145
|
+
* @dbxModelVariable emailVerified
|
|
146
|
+
*/
|
|
147
|
+
emv?: Maybe<boolean>;
|
|
148
|
+
/**
|
|
149
|
+
* Date the provider was FIRST linked. Preserved across relinks.
|
|
150
|
+
*
|
|
151
|
+
* @dbxModelVariable linkedAt
|
|
152
|
+
*/
|
|
153
|
+
lat: Date;
|
|
154
|
+
/**
|
|
155
|
+
* Date this link was last updated at.
|
|
156
|
+
*
|
|
157
|
+
* @dbxModelVariable updatedAt
|
|
158
|
+
*/
|
|
159
|
+
uat: Date;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Map of provider type to the login link the user holds for that provider.
|
|
163
|
+
*/
|
|
164
|
+
export type UserExternalConnectionLoginMap = Record<UserExternalConnectionProviderType, UserExternalConnectionLogin>;
|
|
102
165
|
/**
|
|
103
166
|
* The client-readable half of a user's third-party OAuth connection state.
|
|
104
167
|
*
|
|
@@ -136,6 +199,37 @@ export interface UserExternalConnection extends UserRelated, UserRelatedById {
|
|
|
136
199
|
* @dbxModelVariable connectedProviderTypes
|
|
137
200
|
*/
|
|
138
201
|
c: UserExternalConnectionProviderType[];
|
|
202
|
+
/**
|
|
203
|
+
* Per-provider LOGIN LINKS, keyed by provider type.
|
|
204
|
+
*
|
|
205
|
+
* A provider present here is a way this account signs in. Independent of `e`: a provider can be
|
|
206
|
+
* linked without a data connection (the sign-in wrote the link and the app did not opt into
|
|
207
|
+
* `signInConnects`), connected without being linked (a connect-only provider), or both.
|
|
208
|
+
*
|
|
209
|
+
* @dbxModelVariable logins
|
|
210
|
+
*/
|
|
211
|
+
li: UserExternalConnectionLoginMap;
|
|
212
|
+
/**
|
|
213
|
+
* DERIVED from `e` UNION `li`: the `<providerType>:<externalAccountId>` key of every entry that
|
|
214
|
+
* names an external account, plus every login link's.
|
|
215
|
+
*
|
|
216
|
+
* The `c` array's sibling, and it exists for the same reason: Firestore cannot query across map
|
|
217
|
+
* keys, so `e.<provider>.ea` is unreachable. `c` answers "who is connected to X?"; this answers
|
|
218
|
+
* "who IS X?" — the lookup a sign-in performs to resolve a third-party identity to a Firebase uid.
|
|
219
|
+
*
|
|
220
|
+
* Both maps contribute because either one alone loses the answer. Sourcing it from `e` only meant
|
|
221
|
+
* disconnecting a data connection silently destroyed the sign-in binding and the next sign-in minted
|
|
222
|
+
* a second Firebase user; sourcing it from `li` only would lose a connect-established account that
|
|
223
|
+
* was never a login method.
|
|
224
|
+
*
|
|
225
|
+
* Unlike `c`, membership is NOT filtered by status. Which Discord account a user is is a fact
|
|
226
|
+
* about their identity, not about whether their credentials currently work: a returning user whose
|
|
227
|
+
* token expired (`error`) must still resolve to the same uid, or a sign-in would mint them a
|
|
228
|
+
* second account. Recomputed on every write and never passed in by a caller.
|
|
229
|
+
*
|
|
230
|
+
* @dbxModelVariable externalAccountKeys
|
|
231
|
+
*/
|
|
232
|
+
ec?: Maybe<UserExternalConnectionExternalAccountKey[]>;
|
|
139
233
|
/**
|
|
140
234
|
* Date this document was last updated at.
|
|
141
235
|
*
|
|
@@ -147,12 +241,17 @@ export interface UserExternalConnection extends UserRelated, UserRelatedById {
|
|
|
147
241
|
* Roles for a UserExternalConnection. Users can read their own connection state; all writes go
|
|
148
242
|
* through the server.
|
|
149
243
|
*
|
|
150
|
-
* `connect` and `
|
|
151
|
-
* operations a client can reach, so an app can withhold
|
|
244
|
+
* `connect`, `disconnect` and `unlink` are called out separately from `update` because they are the
|
|
245
|
+
* only operations a client can reach, so an app can withhold any one of them (a user allowed to drop a
|
|
152
246
|
* connection but not to add another, or the reverse) without also withholding the server-driven
|
|
153
247
|
* writes that share `update`.
|
|
248
|
+
*
|
|
249
|
+
* `unlink` is distinct from `disconnect` because they remove different things: a disconnect drops the
|
|
250
|
+
* data connection and keeps the login link, an unlink removes the login link AND everything the
|
|
251
|
+
* disconnect would have. Removing a way to sign in is the more consequential of the two, so an app can
|
|
252
|
+
* grant one without the other.
|
|
154
253
|
*/
|
|
155
|
-
export type UserExternalConnectionRoles = GrantedReadRole | GrantedUpdateRole | 'connect' | 'disconnect';
|
|
254
|
+
export type UserExternalConnectionRoles = GrantedReadRole | GrantedUpdateRole | 'connect' | 'disconnect' | 'unlink';
|
|
156
255
|
export declare class UserExternalConnectionDocument extends AbstractFirestoreDocument<UserExternalConnection, UserExternalConnectionDocument, typeof userExternalConnectionIdentity> {
|
|
157
256
|
get modelIdentity(): import("../..").RootFirestoreModelIdentity<"userExternalConnection", "uec">;
|
|
158
257
|
}
|
|
@@ -169,6 +268,17 @@ export declare const userExternalConnectionEntryFields: {
|
|
|
169
268
|
uat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, string>;
|
|
170
269
|
er: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<UserExternalConnectionErrorCode>, Maybe<UserExternalConnectionErrorCode>>;
|
|
171
270
|
};
|
|
271
|
+
/**
|
|
272
|
+
* Field conversions for a {@link UserExternalConnectionLogin}.
|
|
273
|
+
*/
|
|
274
|
+
export declare const userExternalConnectionLoginFields: {
|
|
275
|
+
ea: import("../..").FirestoreModelFieldMapFunctionsConfig<string, string>;
|
|
276
|
+
l: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
|
|
277
|
+
em: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<string>, Maybe<string>>;
|
|
278
|
+
emv: import("../..").FirestoreModelFieldMapFunctionsConfig<Maybe<boolean>, Maybe<boolean>>;
|
|
279
|
+
lat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, string>;
|
|
280
|
+
uat: import("../..").FirestoreModelFieldMapFunctionsConfig<Date, string>;
|
|
281
|
+
};
|
|
172
282
|
export declare const userExternalConnectionConverter: import("../..").SnapshotConverterFunctions<UserExternalConnection, Partial<import("@dereekb/util").ReplaceType<UserExternalConnection, import("@dereekb/util").MaybeMap<object>, any>>>;
|
|
173
283
|
/**
|
|
174
284
|
* Copies the document id into `uid` on write, so the stored uid can never drift from the document id.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes the UserExternalConnection server surfaces.
|
|
3
|
+
*
|
|
4
|
+
* Declared HERE rather than beside the `HttpsError` factories in `@dereekb/firebase-server/model`
|
|
5
|
+
* because both sides need them: the server throws them, and the client branches on them — a login
|
|
6
|
+
* page deciding what to say about a refused sign-in, or a client treating a raced
|
|
7
|
+
* `..._ALREADY_EXISTS` as success. A code the browser cannot import is a code the browser has to
|
|
8
|
+
* hard-code.
|
|
9
|
+
*/
|
|
10
|
+
export declare const USER_EXTERNAL_CONNECTION_PROVIDER_NOT_CONNECTED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_PROVIDER_NOT_CONNECTED";
|
|
11
|
+
export declare const USER_EXTERNAL_CONNECTION_PROVIDER_NOT_ALLOWED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_PROVIDER_NOT_ALLOWED";
|
|
12
|
+
export declare const USER_EXTERNAL_CONNECTION_ALREADY_EXISTS_ERROR_CODE = "USER_EXTERNAL_CONNECTION_ALREADY_EXISTS";
|
|
13
|
+
export declare const USER_EXTERNAL_CONNECTION_CREDENTIALS_EXPIRED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_CREDENTIALS_EXPIRED";
|
|
14
|
+
export declare const USER_EXTERNAL_CONNECTION_EXTERNAL_ACCOUNT_IN_USE_ERROR_CODE = "USER_EXTERNAL_CONNECTION_EXTERNAL_ACCOUNT_IN_USE";
|
|
15
|
+
export declare const USER_EXTERNAL_CONNECTION_SIGN_IN_NOT_ENABLED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_SIGN_IN_NOT_ENABLED";
|
|
16
|
+
export declare const USER_EXTERNAL_CONNECTION_SIGN_IN_DENIED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_SIGN_IN_DENIED";
|
|
17
|
+
export declare const USER_EXTERNAL_CONNECTION_SIGN_IN_EMAIL_CONFLICT_ERROR_CODE = "USER_EXTERNAL_CONNECTION_SIGN_IN_EMAIL_CONFLICT";
|
|
18
|
+
export declare const USER_EXTERNAL_CONNECTION_SIGN_IN_USER_MISSING_ERROR_CODE = "USER_EXTERNAL_CONNECTION_SIGN_IN_USER_MISSING";
|
|
19
|
+
export declare const USER_EXTERNAL_CONNECTION_SIGN_IN_IDENTITY_UNAVAILABLE_ERROR_CODE = "USER_EXTERNAL_CONNECTION_SIGN_IN_IDENTITY_UNAVAILABLE";
|
|
20
|
+
/**
|
|
21
|
+
* Refuses an unlink that would leave the account with no way back in.
|
|
22
|
+
*/
|
|
23
|
+
export declare const USER_EXTERNAL_CONNECTION_UNLINK_LAST_LOGIN_METHOD_ERROR_CODE = "USER_EXTERNAL_CONNECTION_UNLINK_LAST_LOGIN_METHOD";
|
|
24
|
+
/**
|
|
25
|
+
* Refuses a `link` round trip for a provider the app has not enabled for sign-in.
|
|
26
|
+
*
|
|
27
|
+
* Distinct from `..._SIGN_IN_NOT_ENABLED`: nothing is signing in — an already-authenticated user asked
|
|
28
|
+
* to make the provider a login method, and the same `policy.signIn` opt-in governs both.
|
|
29
|
+
*/
|
|
30
|
+
export declare const USER_EXTERNAL_CONNECTION_LINK_NOT_ENABLED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_LINK_NOT_ENABLED";
|
|
31
|
+
/**
|
|
32
|
+
* The only error codes a failed SIGN-IN reports back to the browser.
|
|
33
|
+
*
|
|
34
|
+
* An ALLOWLIST rather than a filter: a failed sign-in redirects to a URL the user can read, so
|
|
35
|
+
* anything that reaches it is public. Passing whatever code an internal failure happened to carry
|
|
36
|
+
* would leak the shape of that failure, and passing a message would leak its text — so a code absent
|
|
37
|
+
* from this set is reported as nothing at all.
|
|
38
|
+
*
|
|
39
|
+
* Shared with the client so a login page's copy map and the server's allowlist cannot drift.
|
|
40
|
+
*/
|
|
41
|
+
export declare const USER_EXTERNAL_CONNECTION_SIGN_IN_REPORTABLE_ERROR_CODES: ReadonlySet<string>;
|
|
@@ -49,3 +49,37 @@ export type UserExternalConnectionCapability = string;
|
|
|
49
49
|
* Identifier for the connected account within the third-party provider.
|
|
50
50
|
*/
|
|
51
51
|
export type UserExternalConnectionExternalAccountId = string;
|
|
52
|
+
/**
|
|
53
|
+
* The delimiter joining a provider type to an external account id in a
|
|
54
|
+
* {@link UserExternalConnectionExternalAccountKey}.
|
|
55
|
+
*
|
|
56
|
+
* A colon is safe on both sides: {@link UserExternalConnectionProviderType} must already be a valid
|
|
57
|
+
* Firestore map key (no dots or slashes), and the key is only ever a stored/queried string VALUE,
|
|
58
|
+
* never a document id or field path.
|
|
59
|
+
*/
|
|
60
|
+
export declare const USER_EXTERNAL_CONNECTION_EXTERNAL_ACCOUNT_KEY_DELIMITER = ":";
|
|
61
|
+
/**
|
|
62
|
+
* A `<providerType>:<externalAccountId>` pair identifying one third-party account globally.
|
|
63
|
+
*
|
|
64
|
+
* The provider type is part of the key because an external account id is only unique WITHIN a
|
|
65
|
+
* provider — a Discord snowflake and a Zoom user id could collide as bare strings.
|
|
66
|
+
*/
|
|
67
|
+
export type UserExternalConnectionExternalAccountKey = string;
|
|
68
|
+
export interface UserExternalConnectionExternalAccountKeyInput {
|
|
69
|
+
readonly providerType: UserExternalConnectionProviderType;
|
|
70
|
+
readonly externalAccountId: UserExternalConnectionExternalAccountId;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Builds the {@link UserExternalConnectionExternalAccountKey} for a provider/account pair.
|
|
74
|
+
*
|
|
75
|
+
* The SOLE producer of the key format: the derivation that stores it and the query that reads it
|
|
76
|
+
* both go through here, so the two can never disagree about the delimiter.
|
|
77
|
+
*
|
|
78
|
+
* @param input - The provider type and external account id to join.
|
|
79
|
+
* @param input.providerType - The provider the account belongs to.
|
|
80
|
+
* @param input.externalAccountId - The provider's stable id for the account.
|
|
81
|
+
* @returns The external account key.
|
|
82
|
+
*
|
|
83
|
+
* @__NO_SIDE_EFFECTS__
|
|
84
|
+
*/
|
|
85
|
+
export declare function userExternalConnectionExternalAccountKey(input: UserExternalConnectionExternalAccountKeyInput): UserExternalConnectionExternalAccountKey;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type FirestoreQueryConstraint } from '../../common';
|
|
2
|
-
import { type UserExternalConnectionProviderType } from './userexternalconnection.id';
|
|
2
|
+
import { type UserExternalConnectionExternalAccountKeyInput, type UserExternalConnectionProviderType } from './userexternalconnection.id';
|
|
3
3
|
/**
|
|
4
4
|
* Query for the UserExternalConnection documents that are currently connected to the given provider.
|
|
5
5
|
*
|
|
@@ -17,3 +17,24 @@ import { type UserExternalConnectionProviderType } from './userexternalconnectio
|
|
|
17
17
|
* @dbxModelFirebaseIndexScope COLLECTION
|
|
18
18
|
*/
|
|
19
19
|
export declare function userExternalConnectionsWithConnectedProviderQuery(providerType: UserExternalConnectionProviderType): FirestoreQueryConstraint[];
|
|
20
|
+
/**
|
|
21
|
+
* Query for the UserExternalConnection document holding the given third-party account.
|
|
22
|
+
*
|
|
23
|
+
* The sign-in counterpart of {@link userExternalConnectionsWithConnectedProviderQuery}: that one
|
|
24
|
+
* asks "which users are connected to this provider?", this one asks "which user IS this account?".
|
|
25
|
+
* Both exist because a per-user document makes `e.<provider>.ea` unqueryable.
|
|
26
|
+
*
|
|
27
|
+
* Matches at ANY entry status — see the `ec` field docs. Expect at most one result when the
|
|
28
|
+
* provider's policy declares the connection unique, but the caller must still handle more than one:
|
|
29
|
+
* uniqueness is enforced at write time and a provider may only have started enforcing it recently.
|
|
30
|
+
*
|
|
31
|
+
* @param input - The provider type and external account id to search for.
|
|
32
|
+
* @param input.providerType - The provider the account belongs to.
|
|
33
|
+
* @param input.externalAccountId - The provider's stable id for the account.
|
|
34
|
+
* @returns Firestore query constraints matching the user holding that external account.
|
|
35
|
+
*
|
|
36
|
+
* @dbxModelFirebaseIndex
|
|
37
|
+
* @dbxModelFirebaseIndexModel UserExternalConnection
|
|
38
|
+
* @dbxModelFirebaseIndexScope COLLECTION
|
|
39
|
+
*/
|
|
40
|
+
export declare function userExternalConnectionsWithExternalAccountQuery(input: UserExternalConnectionExternalAccountKeyInput): FirestoreQueryConstraint[];
|