@dereekb/firebase-server 13.32.0 → 13.34.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 (79) 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 +42 -9
  28. package/index.esm.js +42 -9
  29. package/mailgun/package.json +9 -9
  30. package/mcp/package.json +11 -11
  31. package/model/index.cjs.js +4243 -855
  32. package/model/index.esm.js +4183 -862
  33. package/model/package.json +10 -9
  34. package/model/src/lib/index.d.ts +2 -0
  35. package/model/src/lib/system/index.d.ts +2 -0
  36. package/model/src/lib/system/system.private.d.ts +63 -0
  37. package/model/src/lib/system/system.private.module.d.ts +70 -0
  38. package/model/src/lib/userexternalconnection/index.d.ts +8 -0
  39. package/model/src/lib/userexternalconnection/oauth/index.d.ts +7 -0
  40. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.config.d.ts +110 -0
  41. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.controller.d.ts +68 -0
  42. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.error.d.ts +29 -0
  43. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.refresh.d.ts +24 -0
  44. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.registry.d.ts +54 -0
  45. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.service.d.ts +216 -0
  46. package/model/src/lib/userexternalconnection/oauth/userexternalconnection.oauth.state.d.ts +114 -0
  47. package/model/src/lib/userexternalconnection/userexternalconnection.accessor.service.d.ts +111 -0
  48. package/model/src/lib/userexternalconnection/userexternalconnection.action.server.d.ts +172 -0
  49. package/model/src/lib/userexternalconnection/userexternalconnection.error.d.ts +44 -0
  50. package/model/src/lib/userexternalconnection/userexternalconnection.module.d.ts +129 -0
  51. package/model/src/lib/userexternalconnection/userexternalconnection.private.d.ts +192 -0
  52. package/model/src/lib/userexternalconnection/userexternalconnection.reader.service.d.ts +157 -0
  53. package/model/src/lib/userexternalconnection/userexternalconnection.refresh.service.d.ts +46 -0
  54. package/oidc/index.cjs.js +104 -78
  55. package/oidc/index.esm.js +104 -78
  56. package/oidc/package.json +10 -10
  57. package/package.json +24 -10
  58. package/src/lib/env/env.config.d.ts +15 -0
  59. package/src/lib/env/env.service.d.ts +7 -0
  60. package/src/lib/firestore/snapshot/snapshot.field.encrypt.d.ts +21 -2
  61. package/src/lib/nest/env/env.service.d.ts +1 -0
  62. package/test/index.cjs.js +14 -2
  63. package/test/index.esm.js +15 -3
  64. package/test/package.json +11 -11
  65. package/test/src/lib/firebase/firebase.admin.auth.d.ts +1 -1
  66. package/twilio/package.json +8 -8
  67. package/zoho/README.md +38 -0
  68. package/zoho/index.cjs.js +1418 -64
  69. package/zoho/index.esm.js +1401 -67
  70. package/zoho/package.json +13 -9
  71. package/zoho/src/lib/index.d.ts +6 -0
  72. package/zoho/src/lib/zoho.accounts.firebase.d.ts +10 -3
  73. package/zoho/src/lib/zoho.accounts.firebase.module.d.ts +51 -0
  74. package/zoho/src/lib/zoho.accounts.firebase.system.d.ts +51 -8
  75. package/zoho/src/lib/zoho.oauth.connection.cache.d.ts +45 -0
  76. package/zoho/src/lib/zoho.oauth.connection.config.d.ts +80 -0
  77. package/zoho/src/lib/zoho.oauth.connection.controller.d.ts +18 -0
  78. package/zoho/src/lib/zoho.oauth.connection.module.d.ts +43 -0
  79. package/zoho/src/lib/zoho.oauth.connection.service.d.ts +107 -0
@@ -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;
@@ -0,0 +1,192 @@
1
+ import { type ISO8601DateString, type Maybe } from '@dereekb/util';
2
+ import { AbstractFirestoreDocument, type CollectionReference, type FirebaseAuthUserId, type FirestoreCollection, type FirestoreContext, type UserExternalConnectionCapability, type UserExternalConnectionExternalAccountId, type UserExternalConnectionGrantSummary, type UserExternalConnectionProviderType, type UserRelated, type UserRelatedById } from '@dereekb/firebase';
3
+ import { type AES256GCMEncryptionSecretSource } from '@dereekb/nestjs';
4
+ /**
5
+ * Provides access to the {@link UserExternalConnectionPrivate} collection.
6
+ *
7
+ * Provided ONLY by the UserExternalConnectionModule. This is deliberately never declared on an
8
+ * app's shared `FirestoreCollections` class: `provideAppFirestoreCollections()` hard-codes a
9
+ * `(context: FirestoreContext) => T` factory, which cannot carry the encryption secret this
10
+ * collection's converter needs — so the private collection structurally cannot leak into the
11
+ * client-shared collection set.
12
+ */
13
+ export declare abstract class UserExternalConnectionServerFirestoreCollections {
14
+ abstract readonly userExternalConnectionPrivateCollection: UserExternalConnectionPrivateFirestoreCollection;
15
+ }
16
+ /**
17
+ * Firestore model identity for {@link UserExternalConnectionPrivate} documents.
18
+ *
19
+ * This is a server-side only model. It has no provisions for client-side access, and deliberately
20
+ * has NO `firestore.rules` match block — the root catch-all `allow read, write: if false;` is what
21
+ * denies every client, and a block here would only create an opportunity to grant access by accident.
22
+ */
23
+ export declare const userExternalConnectionPrivateIdentity: import("@dereekb/firebase").RootFirestoreModelIdentity<"userExternalConnectionPrivate", "uecp">;
24
+ /**
25
+ * Credentials granted by a third-party provider for a single user.
26
+ *
27
+ * NOTE: every timestamp here is an {@link ISO8601DateString}, NOT a `Date`. The whole credentials
28
+ * map is JSON round-tripped through `encryptValue`/`decryptValue`, so a `Date` would silently come
29
+ * back as a string.
30
+ */
31
+ export interface UserExternalConnectionCredentials {
32
+ readonly accessToken: string;
33
+ readonly refreshToken?: Maybe<string>;
34
+ readonly tokenType?: Maybe<string>;
35
+ readonly issuedAt: ISO8601DateString;
36
+ readonly expiresAt?: Maybe<ISO8601DateString>;
37
+ readonly scopes?: Maybe<UserExternalConnectionCapability[]>;
38
+ readonly externalAccountId?: Maybe<UserExternalConnectionExternalAccountId>;
39
+ readonly label?: Maybe<string>;
40
+ /**
41
+ * Any additional provider-specific values that must be retained to use the credentials.
42
+ */
43
+ readonly extra?: Maybe<Record<string, Maybe<string | number | boolean>>>;
44
+ }
45
+ /**
46
+ * Map of provider type to the credentials stored for that provider.
47
+ */
48
+ export type UserExternalConnectionCredentialsMap = Record<UserExternalConnectionProviderType, UserExternalConnectionCredentials>;
49
+ /**
50
+ * The server-only half of a user's third-party OAuth connection state.
51
+ *
52
+ * Shares its document id (the user's uid) with the client-readable `UserExternalConnection` in
53
+ * `@dereekb/firebase`, and is only ever written in the same transaction as it. See
54
+ * `writeUserExternalConnectionPairInTransactionFactory`.
55
+ */
56
+ export interface UserExternalConnectionPrivate extends UserRelated, UserRelatedById {
57
+ /**
58
+ * Per-provider credentials. Stored as ONE encrypted string covering the whole map.
59
+ *
60
+ * A single encrypted field rather than a map of encrypted values: there is no server-side need to
61
+ * query inside credentials, and one field keeps the whole blob atomic with the paired write.
62
+ */
63
+ cr: UserExternalConnectionCredentialsMap;
64
+ /**
65
+ * Date this document was last updated at.
66
+ */
67
+ uat: Date;
68
+ }
69
+ export declare class UserExternalConnectionPrivateDocument extends AbstractFirestoreDocument<UserExternalConnectionPrivate, UserExternalConnectionPrivateDocument, typeof userExternalConnectionPrivateIdentity> {
70
+ get modelIdentity(): import("@dereekb/firebase").RootFirestoreModelIdentity<"userExternalConnectionPrivate", "uecp">;
71
+ }
72
+ /**
73
+ * Configuration for creating a {@link UserExternalConnectionPrivate} snapshot converter.
74
+ */
75
+ export interface UserExternalConnectionPrivateConverterConfig {
76
+ /**
77
+ * Encryption secret source for the credentials field.
78
+ */
79
+ readonly encryptionSecret: AES256GCMEncryptionSecretSource;
80
+ }
81
+ /**
82
+ * Creates a snapshot converter for {@link UserExternalConnectionPrivate} documents.
83
+ *
84
+ * This is a factory rather than a module-level const because `firestoreEncryptedField` resolves and
85
+ * validates the encryption key eagerly at construction — the secret must be known at runtime.
86
+ *
87
+ * @param config - Encryption configuration for the credentials field.
88
+ * @returns Snapshot converter functions for UserExternalConnectionPrivate documents.
89
+ */
90
+ export declare function userExternalConnectionPrivateConverter(config: UserExternalConnectionPrivateConverterConfig): import("@dereekb/firebase").SnapshotConverterFunctions<UserExternalConnectionPrivate, Partial<import("@dereekb/util").ReplaceType<UserExternalConnectionPrivate, import("@dereekb/util").MaybeMap<object>, any>>>;
91
+ /**
92
+ * Copies the document id into `uid` on write, so the stored uid can never drift from the document id.
93
+ */
94
+ export declare const userExternalConnectionPrivateAccessorFactory: import("@dereekb/firebase").InterceptAccessorFactoryFunction<UserExternalConnectionPrivate, import("@dereekb/firebase").DocumentData>;
95
+ /**
96
+ * Returns the Firestore {@link CollectionReference} for {@link UserExternalConnectionPrivate} documents.
97
+ *
98
+ * @param context - The Firestore context to create the collection reference from.
99
+ * @returns The typed collection reference.
100
+ */
101
+ export declare function userExternalConnectionPrivateCollectionReference(context: FirestoreContext): CollectionReference<UserExternalConnectionPrivate>;
102
+ export type UserExternalConnectionPrivateFirestoreCollection = FirestoreCollection<UserExternalConnectionPrivate, UserExternalConnectionPrivateDocument>;
103
+ /**
104
+ * Configuration for creating a {@link UserExternalConnectionPrivateFirestoreCollection}.
105
+ */
106
+ export interface UserExternalConnectionPrivateFirestoreCollectionConfig extends UserExternalConnectionPrivateConverterConfig {
107
+ readonly firestoreContext: FirestoreContext;
108
+ }
109
+ /**
110
+ * Creates a {@link UserExternalConnectionPrivateFirestoreCollection} with encrypted credentials support.
111
+ *
112
+ * @param config - Configuration including the Firestore context and encryption settings.
113
+ * @returns The configured collection.
114
+ */
115
+ export declare function userExternalConnectionPrivateFirestoreCollection(config: UserExternalConnectionPrivateFirestoreCollectionConfig): UserExternalConnectionPrivateFirestoreCollection;
116
+ /**
117
+ * Projects the client-readable facts out of a set of credentials.
118
+ *
119
+ * This is the ONLY bridge between the private and public halves of the pair. Because the summary
120
+ * entry is derived from this projection, the public document cannot describe scopes, an account, or
121
+ * an expiration that the credentials do not actually carry.
122
+ *
123
+ * @param credentials - The credentials to project.
124
+ * @returns The grant summary the public entry is derived from.
125
+ */
126
+ export declare function userExternalConnectionGrantSummaryFromCredentials(credentials: UserExternalConnectionCredentials): UserExternalConnectionGrantSummary;
127
+ /**
128
+ * Input for {@link mergeRefreshedUserExternalConnectionCredentials}.
129
+ */
130
+ export interface MergeRefreshedUserExternalConnectionCredentialsInput {
131
+ /**
132
+ * The credentials that were stored before the refresh.
133
+ */
134
+ readonly previous: UserExternalConnectionCredentials;
135
+ /**
136
+ * The credentials a refresh produced.
137
+ */
138
+ readonly refreshed: UserExternalConnectionCredentials;
139
+ }
140
+ /**
141
+ * Merges refreshed credentials over the stored ones, retaining anything the refresh response omitted.
142
+ *
143
+ * Required because the paired write replaces a provider's credentials WHOLESALE (see
144
+ * {@link applyUserExternalConnectionCredentials}) — so persisting a refresh response verbatim erases
145
+ * every field the provider did not resend, while leaving the entry `connected`. Two of those are
146
+ * load-bearing:
147
+ *
148
+ * - `refreshToken`, which Zoho (and any provider that issues one only on first consent) omits on
149
+ * refresh. Losing it yields a connection that can never be refreshed again and does not look broken.
150
+ * - `extra`, which carries the values needed to USE the credentials — Zoho's `apiDomain` and the
151
+ * `accountsServer` datacenter a later refresh must be sent to. Dropping `accountsServer` breaks the
152
+ * refresh AFTER this one, which is considerably harder to attribute.
153
+ *
154
+ * `extra` is merged key-by-key rather than replaced, so a refresh that resends only `apiDomain` still
155
+ * updates it without discarding the rest. Keys the refresh left UNDEFINED are dropped before merging,
156
+ * because an object literal built with an absent optional field carries the key with an undefined value
157
+ * and a plain spread would use it to erase the stored one. A refresh that means to CLEAR a key can
158
+ * still say so with an explicit null.
159
+ *
160
+ * The generalization of `AbstractUserExternalConnectionOAuthService.credentialsRetainingStoredRefreshToken`,
161
+ * which does the same thing for the authorization-code exchange.
162
+ *
163
+ * @param input - The stored credentials and the refreshed ones.
164
+ * @returns The credentials to persist.
165
+ */
166
+ export declare function mergeRefreshedUserExternalConnectionCredentials(input: MergeRefreshedUserExternalConnectionCredentialsInput): UserExternalConnectionCredentials;
167
+ /**
168
+ * Input for {@link applyUserExternalConnectionCredentials}.
169
+ */
170
+ export interface ApplyUserExternalConnectionCredentialsInput {
171
+ /**
172
+ * The currently stored private document, when one exists.
173
+ */
174
+ readonly current?: Maybe<UserExternalConnectionPrivate>;
175
+ readonly uid: FirebaseAuthUserId;
176
+ readonly providerType: UserExternalConnectionProviderType;
177
+ /**
178
+ * The next credentials for this provider, or null to remove the provider's key entirely.
179
+ */
180
+ readonly credentials: Maybe<UserExternalConnectionCredentials>;
181
+ readonly now: Date;
182
+ }
183
+ /**
184
+ * Applies a single provider's credentials and returns the COMPLETE next private document.
185
+ *
186
+ * The counterpart of `applyUserExternalConnectionEntry()` — both are always called with the same
187
+ * provider and instant inside one transaction.
188
+ *
189
+ * @param input - The current document plus the credentials to apply.
190
+ * @returns The next UserExternalConnectionPrivate value to write.
191
+ */
192
+ export declare function applyUserExternalConnectionCredentials(input: ApplyUserExternalConnectionCredentialsInput): UserExternalConnectionPrivate;
@@ -0,0 +1,157 @@
1
+ import { type FactoryWithRequiredInput, type Maybe, type Milliseconds } from '@dereekb/util';
2
+ import { type FirebaseAuthUserId, type FirebaseAuthUserIdRef, type UserExternalConnectionErrorCode, type UserExternalConnectionProviderType } from '@dereekb/firebase';
3
+ import { type UserExternalConnectionAccessor, type UserExternalConnectionForProvider, type UserExternalConnectionReadParams } from './userexternalconnection.accessor.service';
4
+ import { type UserExternalConnectionCredentials } from './userexternalconnection.private';
5
+ import { type UserExternalConnectionCredentialsRefresher } from './userexternalconnection.refresh.service';
6
+ import { type UserExternalConnectionCredentialsAndFailureWriter } from './userexternalconnection.action.server';
7
+ /**
8
+ * How long before its stated expiration a set of credentials is treated as already expired.
9
+ *
10
+ * A token that expires mid-flight is indistinguishable from one that was never valid, so the check
11
+ * needs headroom. One minute matches the `tokenExpirationBuffer` default that the Cal.com and Zoho
12
+ * token factories already use, so a credential this reader hands out is one those factories would also
13
+ * consider live.
14
+ */
15
+ export declare const DEFAULT_USER_EXTERNAL_CONNECTION_CREDENTIALS_EXPIRATION_BUFFER: Milliseconds;
16
+ /**
17
+ * The error code recorded when a refresh attempt fails.
18
+ *
19
+ * `expired` rather than `revoked`: a failed refresh is nearly always `invalid_grant`, which does not
20
+ * distinguish a revoked grant from a refresh token that was already spent, and claiming the user
21
+ * revoked access when they did not would send the UI down the wrong path.
22
+ */
23
+ export declare const DEFAULT_USER_EXTERNAL_CONNECTION_REFRESH_FAILURE_ERROR_CODE: UserExternalConnectionErrorCode;
24
+ /**
25
+ * Configuration for {@link userExternalConnectionReader}.
26
+ */
27
+ export interface UserExternalConnectionReaderConfig {
28
+ /**
29
+ * The paired read surface this reader adds policy on top of.
30
+ */
31
+ readonly accessor: UserExternalConnectionAccessor;
32
+ /**
33
+ * Used to persist a refresh and to record a failure. Both go through the paired write.
34
+ *
35
+ * Narrowed to those two writes: a full `UserExternalConnectionServerActions` satisfies it, and
36
+ * a caller wiring a reader over something else does not have to implement writes it never reaches.
37
+ */
38
+ readonly actions: UserExternalConnectionCredentialsAndFailureWriter;
39
+ /**
40
+ * Optional. Without one, expired credentials cannot be renewed and
41
+ * {@link UserExternalConnectionReaderProviderInstance.readUsableUserExternalConnectionCredentials}
42
+ * throws instead.
43
+ */
44
+ readonly refresher?: Maybe<UserExternalConnectionCredentialsRefresher>;
45
+ /**
46
+ * How long before its stated expiration a credential is treated as expired. Defaults to
47
+ * {@link DEFAULT_USER_EXTERNAL_CONNECTION_CREDENTIALS_EXPIRATION_BUFFER}.
48
+ */
49
+ readonly expirationBuffer?: Maybe<Milliseconds>;
50
+ }
51
+ /**
52
+ * Why a provider rejected a user's credentials.
53
+ */
54
+ export interface UserExternalConnectionReportFailureInput {
55
+ /**
56
+ * Why the credentials were rejected. Defaults to `unauthorized`, the code for the 401 this is
57
+ * almost always called in response to.
58
+ */
59
+ readonly error?: Maybe<UserExternalConnectionErrorCode>;
60
+ }
61
+ /**
62
+ * Parameters for reporting that a provider rejected a user's credentials.
63
+ */
64
+ export interface UserExternalConnectionReportFailureParams extends UserExternalConnectionReadParams, UserExternalConnectionReportFailureInput {
65
+ }
66
+ /**
67
+ * Input identifying the user a {@link UserExternalConnectionReaderUserInstance} reads for.
68
+ */
69
+ export interface UserExternalConnectionReaderUserInput extends FirebaseAuthUserIdRef {
70
+ }
71
+ /**
72
+ * A {@link UserExternalConnectionReader} narrowed to ONE user and ONE provider.
73
+ *
74
+ * The reader's entire read surface, with `{ uid, providerType }` already applied — so a consumer acting
75
+ * as one user against one provider, the normal case for code making provider API calls, states that
76
+ * pair once instead of at every call and cannot accidentally mix two providers up.
77
+ */
78
+ export interface UserExternalConnectionReaderProviderInstance {
79
+ readonly uid: FirebaseAuthUserId;
80
+ readonly providerType: UserExternalConnectionProviderType;
81
+ /**
82
+ * Loads both halves of the pair, applying no policy. Never throws for an absent or unusable
83
+ * connection.
84
+ */
85
+ readUserExternalConnectionForProvider(): Promise<UserExternalConnectionForProvider>;
86
+ /**
87
+ * Loads the stored credentials as-is, applying no policy. They may be expired.
88
+ */
89
+ readUserExternalConnectionCredentials(): Promise<Maybe<UserExternalConnectionCredentials>>;
90
+ /**
91
+ * Returns credentials that are safe to attempt a provider call with, renewing them first if needed.
92
+ *
93
+ * @throws When the user is not connected to the provider, or the credentials have expired and could
94
+ * not be renewed.
95
+ */
96
+ readUsableUserExternalConnectionCredentials(): Promise<UserExternalConnectionCredentials>;
97
+ /**
98
+ * Records that the provider rejected these credentials, moving the entry to the `error` status.
99
+ *
100
+ * Exists so a caller that gets a 401 mid-call can report it without reaching for the write surface.
101
+ * Never throws — a failure to record a failure is logged and swallowed, because the caller is already
102
+ * handling an error and losing that error to this one would be worse.
103
+ *
104
+ * @param input - Why the credentials were rejected. Defaults to `unauthorized`.
105
+ */
106
+ reportUserExternalConnectionFailure(input?: Maybe<UserExternalConnectionReportFailureInput>): Promise<void>;
107
+ }
108
+ /**
109
+ * A {@link UserExternalConnectionReader} narrowed to one user, awaiting the provider to target.
110
+ *
111
+ * The second of the reader's two factory levels. Splitting them this way is what lets ONE reader — the
112
+ * app has a single provider-agnostic instance — be narrowed to a user once and then used against any
113
+ * number of that user's providers.
114
+ */
115
+ export type UserExternalConnectionReaderUserInstance = FactoryWithRequiredInput<UserExternalConnectionReaderProviderInstance, UserExternalConnectionProviderType>;
116
+ /**
117
+ * Reads a user's third-party credentials for a specific provider, and keeps them usable.
118
+ *
119
+ * The read surface a consumer should reach for. It wraps a {@link UserExternalConnectionAccessor} and
120
+ * adds the policy that would otherwise be re-implemented at every call site: whether the connection is
121
+ * usable, whether the credentials are near enough to expiring to renew, renewing them, persisting the
122
+ * result, and recording a failure.
123
+ *
124
+ * A consumer that has this does not need `UserExternalConnectionServerActions` at all, which is
125
+ * the point — that class is the write surface.
126
+ */
127
+ export declare abstract class UserExternalConnectionReader {
128
+ /**
129
+ * Narrows this reader to one user, returning a factory that narrows it further to one provider.
130
+ *
131
+ * The reader's only entry point. Every read and the failure report live on the narrowed instance, so
132
+ * a caller states the user and provider it is acting for once instead of threading that pair through
133
+ * each call:
134
+ *
135
+ * ```ts
136
+ * const connections = reader.readerForUser({ uid });
137
+ * const credentials = await connections(CALCOM).readUsableUserExternalConnectionCredentials();
138
+ * ```
139
+ *
140
+ * @param input - The user to read for.
141
+ * @returns A factory producing a reader for whichever of that user's providers is needed.
142
+ */
143
+ abstract readerForUser(input: UserExternalConnectionReaderUserInput): UserExternalConnectionReaderUserInstance;
144
+ }
145
+ /**
146
+ * Reference to a {@link UserExternalConnectionReader} instance.
147
+ */
148
+ export interface UserExternalConnectionReaderRef {
149
+ readonly userExternalConnectionReader: UserExternalConnectionReader;
150
+ }
151
+ /**
152
+ * Creates a {@link UserExternalConnectionReader}.
153
+ *
154
+ * @param config - The accessor and actions to build over, plus the optional refresher.
155
+ * @returns A concrete UserExternalConnectionReader implementation.
156
+ */
157
+ export declare function userExternalConnectionReader(config: UserExternalConnectionReaderConfig): UserExternalConnectionReader;
@@ -0,0 +1,46 @@
1
+ import { type Maybe } from '@dereekb/util';
2
+ import { type FirebaseAuthUserId, type UserExternalConnectionProviderType } from '@dereekb/firebase';
3
+ import { type UserExternalConnectionCredentials } from './userexternalconnection.private';
4
+ /**
5
+ * Input for {@link UserExternalConnectionCredentialsRefresher.refreshUserExternalConnectionCredentials}.
6
+ */
7
+ export interface UserExternalConnectionRefreshCredentialsInput {
8
+ readonly uid: FirebaseAuthUserId;
9
+ readonly providerType: UserExternalConnectionProviderType;
10
+ /**
11
+ * The credentials currently stored for this provider.
12
+ *
13
+ * Passed whole rather than as a bare refresh token because a refresh can need more than that:
14
+ * Zoho's refresh must be sent to the datacenter that issued the grant, which is only recorded on
15
+ * `extra`.
16
+ */
17
+ readonly credentials: UserExternalConnectionCredentials;
18
+ }
19
+ /**
20
+ * Exchanges a user's stored credentials for fresh ones.
21
+ *
22
+ * Declared here rather than beside the OAuth registry on purpose: {@link UserExternalConnectionReader}
23
+ * takes a refresher as OPTIONAL configuration, and if the reader could only be configured with a
24
+ * registry-backed one it would have to import the OAuth layer — whose provider services depend on the
25
+ * accessor the reader is built over. Keeping this an interface in the middle is what keeps that graph
26
+ * acyclic, and it also lets a caller supply a refresher that has nothing to do with OAuth.
27
+ *
28
+ * Implementations return the credentials the provider issued, and are NOT responsible for carrying
29
+ * forward values the provider's response omits — see `mergeRefreshedUserExternalConnectionCredentials`,
30
+ * which the reader applies to every result.
31
+ */
32
+ export declare abstract class UserExternalConnectionCredentialsRefresher {
33
+ /**
34
+ * Refreshes one provider's credentials.
35
+ *
36
+ * @param input - The acting user, the provider, and the credentials currently stored.
37
+ * @returns The refreshed credentials, or null when this provider has no refresh path at all.
38
+ */
39
+ abstract refreshUserExternalConnectionCredentials(input: UserExternalConnectionRefreshCredentialsInput): Promise<Maybe<UserExternalConnectionCredentials>>;
40
+ }
41
+ /**
42
+ * Reference to a {@link UserExternalConnectionCredentialsRefresher} instance.
43
+ */
44
+ export interface UserExternalConnectionCredentialsRefresherRef {
45
+ readonly userExternalConnectionCredentialsRefresher: UserExternalConnectionCredentialsRefresher;
46
+ }