@dereekb/firebase-server 13.32.0 → 13.33.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 (72) hide show
  1. package/calcom/index.cjs.default.js +1 -0
  2. package/calcom/index.cjs.js +1451 -0
  3. package/calcom/index.cjs.mjs +2 -0
  4. package/calcom/index.d.ts +1 -0
  5. package/calcom/index.esm.js +1437 -0
  6. package/calcom/package.json +29 -0
  7. package/calcom/src/index.d.ts +1 -0
  8. package/calcom/src/lib/calcom.oauth.connection.cache.d.ts +49 -0
  9. package/calcom/src/lib/calcom.oauth.connection.config.d.ts +75 -0
  10. package/calcom/src/lib/calcom.oauth.connection.context.d.ts +84 -0
  11. package/calcom/src/lib/calcom.oauth.connection.controller.d.ts +18 -0
  12. package/calcom/src/lib/calcom.oauth.connection.module.d.ts +55 -0
  13. package/calcom/src/lib/calcom.oauth.connection.service.d.ts +33 -0
  14. package/calcom/src/lib/index.d.ts +6 -0
  15. package/discord/index.cjs.default.js +1 -0
  16. package/discord/index.cjs.js +761 -0
  17. package/discord/index.cjs.mjs +2 -0
  18. package/discord/index.d.ts +1 -0
  19. package/discord/index.esm.js +753 -0
  20. package/discord/package.json +29 -0
  21. package/discord/src/index.d.ts +1 -0
  22. package/discord/src/lib/discord.oauth.connection.config.d.ts +69 -0
  23. package/discord/src/lib/discord.oauth.connection.controller.d.ts +18 -0
  24. package/discord/src/lib/discord.oauth.connection.module.d.ts +39 -0
  25. package/discord/src/lib/discord.oauth.connection.service.d.ts +43 -0
  26. package/discord/src/lib/index.d.ts +4 -0
  27. package/index.cjs.js +14 -3
  28. package/index.esm.js +14 -3
  29. package/mailgun/package.json +9 -9
  30. package/mcp/package.json +11 -11
  31. package/model/index.cjs.js +3986 -855
  32. package/model/index.esm.js +3934 -862
  33. package/model/package.json +10 -9
  34. package/model/src/lib/index.d.ts +1 -0
  35. package/model/src/lib/userexternalconnection/index.d.ts +8 -0
  36. package/model/src/lib/userexternalconnection/oauth/index.d.ts +7 -0
  37. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.config.d.ts +110 -0
  38. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.controller.d.ts +68 -0
  39. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.error.d.ts +29 -0
  40. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.refresh.d.ts +24 -0
  41. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.registry.d.ts +54 -0
  42. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.service.d.ts +216 -0
  43. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.state.d.ts +114 -0
  44. package/model/src/lib/userexternalconnection/userexternalconnection.accessor.service.d.ts +111 -0
  45. package/model/src/lib/userexternalconnection/userexternalconnection.action.server.d.ts +172 -0
  46. package/model/src/lib/userexternalconnection/userexternalconnection.error.d.ts +44 -0
  47. package/model/src/lib/userexternalconnection/userexternalconnection.module.d.ts +129 -0
  48. package/model/src/lib/userexternalconnection/userexternalconnection.private.d.ts +192 -0
  49. package/model/src/lib/userexternalconnection/userexternalconnection.reader.service.d.ts +157 -0
  50. package/model/src/lib/userexternalconnection/userexternalconnection.refresh.service.d.ts +46 -0
  51. package/oidc/index.cjs.js +104 -78
  52. package/oidc/index.esm.js +104 -78
  53. package/oidc/package.json +10 -10
  54. package/package.json +24 -10
  55. package/src/lib/env/env.config.d.ts +15 -0
  56. package/src/lib/env/env.service.d.ts +7 -0
  57. package/src/lib/nest/env/env.service.d.ts +1 -0
  58. package/test/index.cjs.js +14 -2
  59. package/test/index.esm.js +15 -3
  60. package/test/package.json +11 -11
  61. package/test/src/lib/firebase/firebase.admin.auth.d.ts +1 -1
  62. package/twilio/package.json +8 -8
  63. package/zoho/README.md +8 -0
  64. package/zoho/index.cjs.js +1223 -33
  65. package/zoho/index.esm.js +1212 -36
  66. package/zoho/package.json +12 -9
  67. package/zoho/src/lib/index.d.ts +5 -0
  68. package/zoho/src/lib/zoho.oauth.connection.cache.d.ts +45 -0
  69. package/zoho/src/lib/zoho.oauth.connection.config.d.ts +80 -0
  70. package/zoho/src/lib/zoho.oauth.connection.controller.d.ts +18 -0
  71. package/zoho/src/lib/zoho.oauth.connection.module.d.ts +43 -0
  72. package/zoho/src/lib/zoho.oauth.connection.service.d.ts +107 -0
@@ -0,0 +1,114 @@
1
+ import { type ConfigService } from '@nestjs/config';
2
+ import { type AES256GCMEncryptionSecret } from '@dereekb/nestjs';
3
+ import { type FirebaseServerEnvService } from '@dereekb/firebase-server';
4
+ import { type FirebaseAuthUserId, type UserExternalConnectionProviderType } from '@dereekb/firebase';
5
+ import { type Maybe, type Milliseconds } from '@dereekb/util';
6
+ /**
7
+ * Secret the external-connection OAuth `state` is encrypted with.
8
+ *
9
+ * Provider-agnostic on purpose: `state` is part of the OAuth 2.0 authorization-code flow itself
10
+ * (RFC 6749 4.1.1) and every provider echoes it back opaquely, so one secret serves the whole
11
+ * registry no matter how many providers are registered.
12
+ *
13
+ * Deliberately NOT the credentials secret. `USER_EXTERNAL_CONNECTION_ENCRYPTION_SECRET` is
14
+ * write-once — rotating it makes every stored `uecp` credential permanently undecryptable — whereas
15
+ * this one is freely rotatable, since a state lives for minutes and rotating it only invalidates
16
+ * handoffs that are mid-flight.
17
+ */
18
+ export declare const USER_EXTERNAL_CONNECTION_STATE_SECRET_CONFIG_KEY = "USER_EXTERNAL_CONNECTION_STATE_SECRET";
19
+ /**
20
+ * Deterministic secret used when running in a testing environment and no real secret is configured,
21
+ * so specs never need a live credential.
22
+ */
23
+ export declare const TESTING_USER_EXTERNAL_CONNECTION_STATE_SECRET: AES256GCMEncryptionSecret;
24
+ /**
25
+ * How long a minted `state` stays valid.
26
+ *
27
+ * Long enough for a user to work through a provider's consent screen, short enough that a leaked
28
+ * state is not reusable later.
29
+ */
30
+ export declare const DEFAULT_USER_EXTERNAL_CONNECTION_STATE_EXPIRATION: Milliseconds;
31
+ /**
32
+ * The payload carried inside an encrypted external-connection OAuth `state`.
33
+ */
34
+ export interface UserExternalConnectionStatePayload {
35
+ /**
36
+ * The user the handoff belongs to.
37
+ */
38
+ readonly uid: FirebaseAuthUserId;
39
+ /**
40
+ * The provider the handoff was started for.
41
+ *
42
+ * Verified on the way back, so a state minted to connect one provider cannot be replayed against
43
+ * another provider's callback.
44
+ */
45
+ readonly providerType: UserExternalConnectionProviderType;
46
+ /**
47
+ * Epoch milliseconds after which the state is rejected.
48
+ */
49
+ readonly exp: number;
50
+ }
51
+ /**
52
+ * Who a verified state belongs to.
53
+ */
54
+ export interface UserExternalConnectionStateActor {
55
+ readonly uid: FirebaseAuthUserId;
56
+ }
57
+ export interface MintUserExternalConnectionStateInput {
58
+ readonly uid: FirebaseAuthUserId;
59
+ readonly providerType: UserExternalConnectionProviderType;
60
+ }
61
+ export interface VerifyUserExternalConnectionStateInput {
62
+ readonly state: Maybe<string>;
63
+ /**
64
+ * The provider whose callback is verifying. A state minted for a different provider is rejected.
65
+ */
66
+ readonly providerType: UserExternalConnectionProviderType;
67
+ }
68
+ export interface UserExternalConnectionStateCoderConfig {
69
+ readonly secret: AES256GCMEncryptionSecret;
70
+ /**
71
+ * How long a minted state stays valid. Defaults to {@link DEFAULT_USER_EXTERNAL_CONNECTION_STATE_EXPIRATION}.
72
+ */
73
+ readonly expiresIn?: Maybe<Milliseconds>;
74
+ }
75
+ /**
76
+ * Mints and verifies the OAuth `state` for external-connection handoffs.
77
+ *
78
+ * Declared as an abstract class so it is its own injection token, matching
79
+ * `UserExternalConnectionModuleConfig`. One coder is shared by every registered provider.
80
+ */
81
+ export declare abstract class UserExternalConnectionStateCoder {
82
+ /**
83
+ * Mints a short-lived state for a user's connect handoff with a provider.
84
+ */
85
+ abstract readonly mintState: (input: MintUserExternalConnectionStateInput) => string;
86
+ /**
87
+ * Resolves the user a state belongs to, or null when it is absent, tampered with, expired, or was
88
+ * minted for a different provider.
89
+ */
90
+ abstract readonly verifyState: (input: VerifyUserExternalConnectionStateInput) => Maybe<UserExternalConnectionStateActor>;
91
+ }
92
+ /**
93
+ * Creates the coder that mints and verifies the OAuth `state` for external-connection handoffs.
94
+ *
95
+ * The state is what lets a provider's redirect back to us be attributed to a user: the authorize
96
+ * request is a top-level browser navigation and carries no credentials of its own. Tamper-evidence
97
+ * is the requirement; AES-256-GCM is used because its auth tag provides that and additionally keeps
98
+ * the uid opaque to the browser.
99
+ *
100
+ * @param config - The encryption secret and optional expiration.
101
+ * @returns The state coder.
102
+ *
103
+ * @__NO_SIDE_EFFECTS__
104
+ */
105
+ export declare function userExternalConnectionStateCoder(config: UserExternalConnectionStateCoderConfig): UserExternalConnectionStateCoder;
106
+ /**
107
+ * Builds the external-connection state coder from the environment.
108
+ *
109
+ * @param configService - The Nest config service used to read the state secret.
110
+ * @param envService - Used to detect a testing environment for the secret fallback.
111
+ * @returns The state coder.
112
+ * @throws {Error} When the configured secret is invalid outside a testing environment.
113
+ */
114
+ export declare function userExternalConnectionStateCoderFactory(configService: ConfigService, envService: FirebaseServerEnvService): UserExternalConnectionStateCoder;
@@ -0,0 +1,111 @@
1
+ import { type FactoryWithRequiredInput, type Maybe } from '@dereekb/util';
2
+ import { type FirebaseAuthUserId, type FirebaseAuthUserIdRef, type UserExternalConnectionEntry, type UserExternalConnectionFirestoreCollections, type UserExternalConnectionProviderType } from '@dereekb/firebase';
3
+ import { type UserExternalConnectionCredentials, type UserExternalConnectionServerFirestoreCollections } from './userexternalconnection.private';
4
+ /**
5
+ * Context required by {@link userExternalConnectionAccessor}.
6
+ *
7
+ * Carries both halves of the pair, the same as the server actions context. Unlike that context there
8
+ * is no `FirestoreContextReference` here: every read is a plain document read, so nothing in this
9
+ * file needs to start a transaction.
10
+ */
11
+ export interface UserExternalConnectionAccessorContext extends UserExternalConnectionFirestoreCollections, UserExternalConnectionServerFirestoreCollections {
12
+ }
13
+ /**
14
+ * Identifies the one provider entry a read is about.
15
+ */
16
+ export interface UserExternalConnectionReadParams {
17
+ readonly uid: FirebaseAuthUserId;
18
+ readonly providerType: UserExternalConnectionProviderType;
19
+ }
20
+ /**
21
+ * Both halves of a user's connection state for a single provider.
22
+ *
23
+ * The public entry and the private credentials are returned together because a caller deciding
24
+ * whether it can act as this user needs both: the credentials alone cannot say whether the
25
+ * connection is `connected` or `error`, and the entry alone cannot be used to call anything.
26
+ *
27
+ * Either side may be null. A user with no connection document at all reads as both null.
28
+ */
29
+ export interface UserExternalConnectionForProvider {
30
+ readonly uid: FirebaseAuthUserId;
31
+ readonly providerType: UserExternalConnectionProviderType;
32
+ /**
33
+ * The provider's entry on the client-readable document, when the user has one.
34
+ */
35
+ readonly entry: Maybe<UserExternalConnectionEntry>;
36
+ /**
37
+ * The provider's stored credentials in plaintext, when the user has any.
38
+ */
39
+ readonly credentials: Maybe<UserExternalConnectionCredentials>;
40
+ }
41
+ /**
42
+ * Input identifying the user a {@link UserExternalConnectionAccessorUserInstance} reads for.
43
+ */
44
+ export interface UserExternalConnectionAccessorUserInput extends FirebaseAuthUserIdRef {
45
+ }
46
+ /**
47
+ * A {@link UserExternalConnectionAccessor} narrowed to ONE user and ONE provider.
48
+ *
49
+ * The accessor's entire read surface with `{ uid, providerType }` already applied.
50
+ */
51
+ export interface UserExternalConnectionAccessorProviderInstance {
52
+ readonly uid: FirebaseAuthUserId;
53
+ readonly providerType: UserExternalConnectionProviderType;
54
+ /**
55
+ * Loads both halves of the pair.
56
+ */
57
+ readUserExternalConnectionForProvider(): Promise<UserExternalConnectionForProvider>;
58
+ /**
59
+ * Loads only the stored credentials.
60
+ */
61
+ readUserExternalConnectionCredentials(): Promise<Maybe<UserExternalConnectionCredentials>>;
62
+ }
63
+ /**
64
+ * A {@link UserExternalConnectionAccessor} narrowed to one user, awaiting the provider to target.
65
+ */
66
+ export type UserExternalConnectionAccessorUserInstance = FactoryWithRequiredInput<UserExternalConnectionAccessorProviderInstance, UserExternalConnectionProviderType>;
67
+ /**
68
+ * Server-only read surface for the UserExternalConnection document pair.
69
+ *
70
+ * Deliberately the whole read surface and nothing more: no expiration policy, no refresh, no
71
+ * assertions. That keeps this usable by the OAuth provider services themselves — they need to read
72
+ * the credentials they are about to replace, and a tier that could refresh would have to know about
73
+ * the provider registry those services are registered in.
74
+ *
75
+ * {@link UserExternalConnectionReader} wraps this and adds the policy.
76
+ */
77
+ export declare abstract class UserExternalConnectionAccessor {
78
+ /**
79
+ * Narrows this accessor to one user, returning a factory that narrows it further to one provider.
80
+ *
81
+ * The accessor's only entry point, and the same two levels
82
+ * {@link UserExternalConnectionReader.readerForUser} has, so a caller holding either states the user
83
+ * and provider it is reading for once:
84
+ *
85
+ * ```ts
86
+ * const credentials = await accessor.accessorForUser({ uid })(CALCOM).readUserExternalConnectionCredentials();
87
+ * ```
88
+ *
89
+ * @param input - The user to read for.
90
+ * @returns A factory producing an accessor for whichever of that user's providers is needed.
91
+ */
92
+ abstract accessorForUser(input: UserExternalConnectionAccessorUserInput): UserExternalConnectionAccessorUserInstance;
93
+ }
94
+ /**
95
+ * Reference to a {@link UserExternalConnectionAccessor} instance.
96
+ */
97
+ export interface UserExternalConnectionAccessorRef {
98
+ readonly userExternalConnectionAccessor: UserExternalConnectionAccessor;
99
+ }
100
+ /**
101
+ * Creates a {@link UserExternalConnectionAccessor} bound to the given context.
102
+ *
103
+ * Reads are NOT paired the way writes are. The pairing is a write invariant — the two documents are
104
+ * only ever written together, so a read of one cannot observe a state the other contradicts, and
105
+ * reading them in a transaction would buy nothing. Server paths load both halves; the client can only
106
+ * ever load the public one.
107
+ *
108
+ * @param context - The context carrying both halves of the pair.
109
+ * @returns A concrete UserExternalConnectionAccessor implementation.
110
+ */
111
+ export declare function userExternalConnectionAccessor(context: UserExternalConnectionAccessorContext): UserExternalConnectionAccessor;
@@ -0,0 +1,172 @@
1
+ import { type Maybe } from '@dereekb/util';
2
+ import { type FirebaseAuthUserId, type FirestoreContextReference, type UserExternalConnectionDocument, type UserExternalConnectionEntryStatus, type UserExternalConnectionErrorCode, type UserExternalConnectionFirestoreCollections, type UserExternalConnectionProviderType } from '@dereekb/firebase';
3
+ import { type UserExternalConnectionCredentials, type UserExternalConnectionServerFirestoreCollections } from './userexternalconnection.private';
4
+ /**
5
+ * Context required by {@link userExternalConnectionServerActions}.
6
+ *
7
+ * Carries BOTH halves of the pair. Nothing else in the workspace should hold the private collection.
8
+ */
9
+ export interface UserExternalConnectionServerActionsContext extends FirestoreContextReference, UserExternalConnectionFirestoreCollections, UserExternalConnectionServerFirestoreCollections {
10
+ }
11
+ /**
12
+ * Reference to a {@link UserExternalConnectionServerActions} instance.
13
+ */
14
+ export interface UserExternalConnectionServerActionsRef {
15
+ readonly userExternalConnectionActions: UserExternalConnectionServerActions;
16
+ }
17
+ /**
18
+ * Parameters for connecting a user to a provider.
19
+ *
20
+ * NOTE what is absent: there is no parameter for `status`, `scopes`, `externalAccountId`,
21
+ * `expiresAt`, `connectedAt`, `updatedAt`, or the connected-provider array. Every one of those is
22
+ * derived from `credentials`, so a caller has no way to submit a summary that contradicts the
23
+ * credentials it summarizes. `label` rides on the credentials because it is a fact about the grant.
24
+ */
25
+ export interface UserExternalConnectionConnectParams {
26
+ readonly uid: FirebaseAuthUserId;
27
+ readonly providerType: UserExternalConnectionProviderType;
28
+ readonly credentials: UserExternalConnectionCredentials;
29
+ /**
30
+ * Optional instant to apply the change at. Defaults to now.
31
+ */
32
+ readonly now?: Maybe<Date>;
33
+ }
34
+ /**
35
+ * Parameters for replacing a provider's credentials after a token refresh.
36
+ */
37
+ export type UserExternalConnectionRefreshCredentialsParams = UserExternalConnectionConnectParams;
38
+ /**
39
+ * Parameters for marking a provider's connection as errored.
40
+ *
41
+ * The stored credentials are retained so the connection can be repaired without a full reconnect.
42
+ */
43
+ export interface UserExternalConnectionMarkErrorParams {
44
+ readonly uid: FirebaseAuthUserId;
45
+ readonly providerType: UserExternalConnectionProviderType;
46
+ readonly error?: Maybe<UserExternalConnectionErrorCode>;
47
+ readonly now?: Maybe<Date>;
48
+ }
49
+ /**
50
+ * Parameters for disconnecting a user from a provider.
51
+ */
52
+ export interface UserExternalConnectionDisconnectParams {
53
+ readonly uid: FirebaseAuthUserId;
54
+ readonly providerType: UserExternalConnectionProviderType;
55
+ /**
56
+ * Whether to retain a `disconnected` history entry on the public document rather than removing the
57
+ * provider's key. The credentials are removed either way. Defaults to false.
58
+ */
59
+ readonly retainEntry?: Maybe<boolean>;
60
+ readonly now?: Maybe<Date>;
61
+ }
62
+ /**
63
+ * Parameters for creating a user's connection document.
64
+ */
65
+ export interface UserExternalConnectionCreateParams {
66
+ readonly uid: FirebaseAuthUserId;
67
+ /**
68
+ * Optional instant to stamp the new document with. Defaults to now.
69
+ */
70
+ readonly now?: Maybe<Date>;
71
+ }
72
+ /**
73
+ * Parameters for deleting a user's entire connection pair.
74
+ */
75
+ export interface UserExternalConnectionDeleteAllParams {
76
+ readonly uid: FirebaseAuthUserId;
77
+ }
78
+ /**
79
+ * Server-only actions for the UserExternalConnection document pair.
80
+ *
81
+ * This is the ENTIRE write surface, and ONLY the write surface. The two collections are never exposed
82
+ * for independent mutation, so there is no way for a caller to write one document without the other —
83
+ * and therefore no sync, reconciliation, or drift-detection process to maintain.
84
+ *
85
+ * Reading is `UserExternalConnectionAccessor` (raw) or `UserExternalConnectionReader` (the one a
86
+ * consumer wants). A read used to live here too, which meant every path that only needed to look at a
87
+ * user's credentials had to hold the write surface to do it.
88
+ */
89
+ export declare abstract class UserExternalConnectionServerActions {
90
+ abstract createUserExternalConnection(params: UserExternalConnectionCreateParams): Promise<UserExternalConnectionDocument>;
91
+ abstract connectUserExternalConnection(params: UserExternalConnectionConnectParams): Promise<UserExternalConnectionDocument>;
92
+ abstract refreshUserExternalConnectionCredentials(params: UserExternalConnectionRefreshCredentialsParams): Promise<UserExternalConnectionDocument>;
93
+ abstract markUserExternalConnectionError(params: UserExternalConnectionMarkErrorParams): Promise<UserExternalConnectionDocument>;
94
+ abstract disconnectUserExternalConnection(params: UserExternalConnectionDisconnectParams): Promise<UserExternalConnectionDocument>;
95
+ abstract deleteAllUserExternalConnectionsForUser(params: UserExternalConnectionDeleteAllParams): Promise<void>;
96
+ }
97
+ /**
98
+ * The single write a per-user token cache needs: persisting credentials the provider just issued.
99
+ *
100
+ * Narrower than {@link UserExternalConnectionServerActions} on purpose. A token cache has no business
101
+ * connecting, disconnecting, or deleting anything, and the resolved document is of no use to it — hence
102
+ * the unconstrained result, which also means a caller can satisfy this without producing a document it
103
+ * would only throw away.
104
+ */
105
+ export interface UserExternalConnectionCredentialsWriter {
106
+ refreshUserExternalConnectionCredentials(params: UserExternalConnectionRefreshCredentialsParams): Promise<unknown>;
107
+ }
108
+ /**
109
+ * The two writes a reader performs: persisting a refresh, and recording that a provider rejected the
110
+ * credentials.
111
+ *
112
+ * Also narrower than {@link UserExternalConnectionServerActions} on purpose — a read surface has no
113
+ * business creating or deleting a connection — and for the same reason unconstrained in its results.
114
+ */
115
+ export interface UserExternalConnectionCredentialsAndFailureWriter extends UserExternalConnectionCredentialsWriter {
116
+ markUserExternalConnectionError(params: UserExternalConnectionMarkErrorParams): Promise<unknown>;
117
+ }
118
+ /**
119
+ * Creates a {@link UserExternalConnectionServerActions} bound to the given context.
120
+ *
121
+ * @param context - The context carrying both halves of the connection pair.
122
+ * @returns A concrete UserExternalConnectionServerActions implementation.
123
+ */
124
+ export declare function userExternalConnectionServerActions(context: UserExternalConnectionServerActionsContext): UserExternalConnectionServerActions;
125
+ /**
126
+ * Creates a function that creates a user's connection document.
127
+ *
128
+ * Only the public half is written: the private half exists to hold credentials, and the paired write
129
+ * creates it on the first connect. Creation runs in a transaction so two concurrent calls cannot both
130
+ * see an absent document and both write one.
131
+ *
132
+ * @param context - The context carrying both halves of the pair.
133
+ * @returns A function that creates the document for a uid, throwing if it already exists.
134
+ */
135
+ export declare function createUserExternalConnectionFactory(context: UserExternalConnectionServerActionsContext): (params: UserExternalConnectionCreateParams) => Promise<UserExternalConnectionDocument>;
136
+ /**
137
+ * Parameters for the paired write.
138
+ */
139
+ export interface WriteUserExternalConnectionPairParams {
140
+ readonly uid: FirebaseAuthUserId;
141
+ readonly providerType: UserExternalConnectionProviderType;
142
+ /**
143
+ * The outcome the operation produced. Drives BOTH sides of the pair.
144
+ */
145
+ readonly outcome: UserExternalConnectionEntryStatus;
146
+ /**
147
+ * The credentials the outcome produced. Required for a `connected` outcome; ignored for a
148
+ * `disconnected` one (which always removes the stored credentials).
149
+ */
150
+ readonly credentials?: Maybe<UserExternalConnectionCredentials>;
151
+ readonly error?: Maybe<UserExternalConnectionErrorCode>;
152
+ readonly retainEntry?: Maybe<boolean>;
153
+ readonly now?: Maybe<Date>;
154
+ }
155
+ /**
156
+ * Creates the single function through which every mutation of the connection pair flows.
157
+ *
158
+ * Both documents are loaded from the same transaction, all reads happen before any write (a
159
+ * Firestore transaction requirement), and both are written with the COMPLETE next value derived from
160
+ * one input. A failure at any point leaves neither document changed.
161
+ *
162
+ * @param context - The context carrying both halves of the pair.
163
+ * @returns A function that applies one provider's outcome to both documents atomically.
164
+ */
165
+ export declare function writeUserExternalConnectionPairInTransactionFactory(context: UserExternalConnectionServerActionsContext): (params: WriteUserExternalConnectionPairParams) => Promise<UserExternalConnectionDocument>;
166
+ /**
167
+ * Creates a function that deletes a user's entire connection pair in one transaction.
168
+ *
169
+ * @param context - The context carrying both halves of the pair.
170
+ * @returns A function that removes both documents for a uid.
171
+ */
172
+ export declare function deleteAllUserExternalConnectionsForUserFactory(context: UserExternalConnectionServerActionsContext): (params: UserExternalConnectionDeleteAllParams) => Promise<void>;
@@ -0,0 +1,44 @@
1
+ import { type FirebaseAuthUserId, type UserExternalConnectionProviderType } from '@dereekb/firebase';
2
+ export declare const USER_EXTERNAL_CONNECTION_PROVIDER_NOT_CONNECTED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_PROVIDER_NOT_CONNECTED";
3
+ export declare const USER_EXTERNAL_CONNECTION_PROVIDER_NOT_ALLOWED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_PROVIDER_NOT_ALLOWED";
4
+ export declare const USER_EXTERNAL_CONNECTION_ALREADY_EXISTS_ERROR_CODE = "USER_EXTERNAL_CONNECTION_ALREADY_EXISTS";
5
+ export declare const USER_EXTERNAL_CONNECTION_CREDENTIALS_EXPIRED_ERROR_CODE = "USER_EXTERNAL_CONNECTION_CREDENTIALS_EXPIRED";
6
+ /**
7
+ * Creates an error indicating the user already has a connection document.
8
+ *
9
+ * There is exactly one per user, so creating a second is a caller mistake rather than a conflict to
10
+ * resolve. Clients that create on page load are expected to skip the call when the document is
11
+ * already loaded, and to treat this code as success when two of them race.
12
+ *
13
+ * @param uid - The user whose document already exists.
14
+ * @returns A precondition-conflict HttpsError.
15
+ */
16
+ export declare function userExternalConnectionAlreadyExistsError(uid: FirebaseAuthUserId): import("firebase-functions/https").HttpsError;
17
+ /**
18
+ * Creates an error indicating the user has no connection to the requested provider.
19
+ *
20
+ * @param providerType - The provider that was requested.
21
+ * @returns A precondition-conflict HttpsError.
22
+ */
23
+ export declare function userExternalConnectionProviderNotConnectedError(providerType: UserExternalConnectionProviderType): import("firebase-functions/https").HttpsError;
24
+ /**
25
+ * Creates an error indicating the user's credentials for a provider have expired and could not be
26
+ * renewed.
27
+ *
28
+ * Distinct from {@link userExternalConnectionProviderNotConnectedError}: the user IS connected and the
29
+ * credentials are still stored, they just cannot be used right now. Either no refresher was configured,
30
+ * the provider has no refresh path, or the refresh itself failed. The remedy for the last of those is a
31
+ * reconnect; for the first two it is a configuration change, so the code is deliberately the same and
32
+ * the distinction is left to the server logs.
33
+ *
34
+ * @param providerType - The provider whose credentials expired.
35
+ * @returns A precondition-conflict HttpsError.
36
+ */
37
+ export declare function userExternalConnectionCredentialsExpiredError(providerType: UserExternalConnectionProviderType): import("firebase-functions/https").HttpsError;
38
+ /**
39
+ * Creates an error indicating the requested provider is not one this app allows connecting to.
40
+ *
41
+ * @param providerType - The provider that was requested.
42
+ * @returns A precondition-conflict HttpsError.
43
+ */
44
+ export declare function userExternalConnectionProviderNotAllowedError(providerType: UserExternalConnectionProviderType): import("firebase-functions/https").HttpsError;
@@ -0,0 +1,129 @@
1
+ import { type InjectionToken, type ModuleMetadata, type Provider } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { type Maybe, type Milliseconds } from '@dereekb/util';
4
+ import { type FirestoreContext, type FirestoreContextReference, type UserExternalConnectionFirestoreCollections } from '@dereekb/firebase';
5
+ import { FirebaseServerEnvService } from '@dereekb/firebase-server';
6
+ import { type AES256GCMEncryptionSecret } from '@dereekb/nestjs';
7
+ import { type UserExternalConnectionPrivateConverterConfig, UserExternalConnectionServerFirestoreCollections } from './userexternalconnection.private';
8
+ import { type UserExternalConnectionServerActionsContext } from './userexternalconnection.action.server';
9
+ /**
10
+ * Environment variable name for the external-connection credentials encryption secret
11
+ * (hex-encoded AES-256 key).
12
+ *
13
+ * IMPORTANT: there is NO key rotation. `firestoreEncryptedField` resolves and validates the key once
14
+ * at converter construction and closes over it, so changing this value makes every existing
15
+ * `uecp` document permanently undecryptable. Treat it as write-once per environment.
16
+ */
17
+ export declare const USER_EXTERNAL_CONNECTION_ENCRYPTION_SECRET_ENV_KEY = "USER_EXTERNAL_CONNECTION_ENCRYPTION_SECRET";
18
+ /**
19
+ * Deterministic secret used when running in a testing environment and no real secret is configured,
20
+ * so specs never need a live credential.
21
+ *
22
+ * Deliberately distinct from the OIDC JWKS testing secret so a leaked emulator blob is attributable.
23
+ */
24
+ export declare const TESTING_USER_EXTERNAL_CONNECTION_ENCRYPTION_SECRET: AES256GCMEncryptionSecret;
25
+ /**
26
+ * Configuration for the UserExternalConnection server module.
27
+ */
28
+ export declare abstract class UserExternalConnectionModuleConfig {
29
+ abstract readonly userExternalConnectionPrivateConverterConfig: UserExternalConnectionPrivateConverterConfig;
30
+ }
31
+ /**
32
+ * Builds the {@link UserExternalConnectionModuleConfig} from the environment.
33
+ *
34
+ * @param configService - The Nest config service used to read the encryption secret.
35
+ * @param envService - Used to detect a testing environment for the secret fallback.
36
+ * @returns The module configuration.
37
+ * @throws {Error} When the configured secret is invalid outside a testing environment.
38
+ */
39
+ export declare function userExternalConnectionModuleConfigFactory(configService: ConfigService, envService: FirebaseServerEnvService): UserExternalConnectionModuleConfig;
40
+ /**
41
+ * Creates the {@link UserExternalConnectionServerFirestoreCollections}.
42
+ *
43
+ * This is a bespoke provider rather than a `provideAppFirestoreCollections()` entry because that
44
+ * helper hard-codes a `(context: FirestoreContext) => T` factory and cannot carry the encryption
45
+ * secret this collection needs.
46
+ *
47
+ * @param firestoreContext - The Firestore context.
48
+ * @param config - The module configuration carrying the encryption secret.
49
+ * @returns The server-only collections.
50
+ */
51
+ export declare function userExternalConnectionServerFirestoreCollectionsFactory(firestoreContext: FirestoreContext, config: UserExternalConnectionModuleConfig): UserExternalConnectionServerFirestoreCollections;
52
+ /**
53
+ * Assembles the {@link UserExternalConnectionServerActionsContext} from the app-supplied public
54
+ * collections and the module-owned private collection.
55
+ *
56
+ * @param appCollections - The app's collections, carrying the public UserExternalConnection collection.
57
+ * @param serverCollections - The module-owned private collection.
58
+ * @returns The assembled server actions context.
59
+ */
60
+ export declare function userExternalConnectionServerActionsContextFactory(appCollections: UserExternalConnectionFirestoreCollections & FirestoreContextReference, serverCollections: UserExternalConnectionServerFirestoreCollections): UserExternalConnectionServerActionsContext;
61
+ /**
62
+ * NestJS injection token for the assembled {@link UserExternalConnectionServerActionsContext}.
63
+ */
64
+ export declare const USER_EXTERNAL_CONNECTION_SERVER_ACTIONS_CONTEXT_TOKEN: InjectionToken;
65
+ export interface ProvideAppUserExternalConnectionModuleMetadataConfig extends Pick<ModuleMetadata, 'imports' | 'exports' | 'providers'> {
66
+ /**
67
+ * Module that exports the app's collections token. When provided, it is automatically included in
68
+ * the generated `imports` array.
69
+ */
70
+ readonly dependencyModule?: Maybe<Required<ModuleMetadata>['imports']['0']>;
71
+ /**
72
+ * Token that resolves the app's collections — anything implementing both
73
+ * `UserExternalConnectionFirestoreCollections` and `FirestoreContextReference`.
74
+ *
75
+ * Taking this as a token is what keeps the package from ever naming an app's collections class.
76
+ */
77
+ readonly appCollectionsToken: InjectionToken;
78
+ }
79
+ /**
80
+ * Convenience function used to generate ModuleMetadata for an app's UserExternalConnectionModule.
81
+ *
82
+ * By default this module exports:
83
+ * - UserExternalConnectionServerActions
84
+ * - UserExternalConnectionAccessor
85
+ * - UserExternalConnectionServerFirestoreCollections
86
+ * - UserExternalConnectionModuleConfig
87
+ * - UserExternalConnectionStateCoder
88
+ *
89
+ * NOTE what is absent: `UserExternalConnectionReader`. The reader can be configured with a refresher,
90
+ * and the only refresher an app normally wants is backed by the OAuth provider registry — which this
91
+ * module cannot see without importing the provider modules that import it. Provide the reader with
92
+ * `userExternalConnectionReaderProvider()` from wherever the registry is declared.
93
+ *
94
+ * @param config - The module configuration.
95
+ * @returns The assembled {@link ModuleMetadata}.
96
+ */
97
+ export declare function appUserExternalConnectionModuleMetadata(config: ProvideAppUserExternalConnectionModuleMetadataConfig): ModuleMetadata;
98
+ /**
99
+ * Configuration for {@link userExternalConnectionReaderProvider}.
100
+ */
101
+ export interface UserExternalConnectionReaderProviderConfig {
102
+ /**
103
+ * Whether to build the reader's refresher from the {@link UserExternalConnectionOAuthProviderRegistry}.
104
+ * Defaults to true.
105
+ *
106
+ * Set false for an app that registers no OAuth provider services — the registry token would not
107
+ * resolve, and a reader with no refresher is still useful for reading.
108
+ */
109
+ readonly refreshWithOAuthProviderRegistry?: Maybe<boolean>;
110
+ /**
111
+ * Overrides how long before its stated expiration a credential is treated as expired.
112
+ */
113
+ readonly expirationBuffer?: Maybe<Milliseconds>;
114
+ }
115
+ /**
116
+ * Creates the NestJS provider for the {@link UserExternalConnectionReader}.
117
+ *
118
+ * Declared by the app rather than by the UserExternalConnection module, for the same reason
119
+ * {@link userExternalConnectionOAuthProviderRegistryProvider} is: the reader refreshes through the
120
+ * registry, and the registry can only be assembled somewhere that is able to import the provider
121
+ * modules — each of which imports the UserExternalConnection module. Put this beside the registry
122
+ * provider.
123
+ *
124
+ * @param config - Optional configuration. By default the reader refreshes through the registry.
125
+ * @returns The NestJS provider.
126
+ *
127
+ * @__NO_SIDE_EFFECTS__
128
+ */
129
+ export declare function userExternalConnectionReaderProvider(config?: Maybe<UserExternalConnectionReaderProviderConfig>): Provider;