@dereekb/firebase-server 13.33.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.
@@ -1,4 +1,4 @@
1
- import { type SystemStateFirestoreCollection } from '@dereekb/firebase';
1
+ import { type FirestoreDocument, type SystemState, type SystemStateFirestoreCollectionLike, type SystemStateStoredData } from '@dereekb/firebase';
2
2
  import { type ZohoAccountsAccessTokenCacheService } from '@dereekb/zoho/nestjs';
3
3
  /**
4
4
  * Creates a {@link ZohoAccountsAccessTokenCacheService} backed by Firestore {@link SystemState} documents.
@@ -7,14 +7,21 @@ import { type ZohoAccountsAccessTokenCacheService } from '@dereekb/zoho/nestjs';
7
7
  * Tokens are stored in a single {@link SystemState} document (type {@link ZOHO_ACCESS_TOKEN_SYSTEM_STATE_TYPE})
8
8
  * and token updates/clears use Firestore transactions for concurrency safety.
9
9
  *
10
+ * The access token is a credential, so pass a SERVER-ONLY collection — a
11
+ * `systemStatePrivateFirestoreCollection()` from `@dereekb/firebase-server/model`, whose converter
12
+ * map registers {@link zohoAccessTokenSystemStateDataConverterFactory} and encrypts the token at
13
+ * rest. Passing an app's client-shared `systemStateCollection` still type-checks (for backwards
14
+ * compatibility) but requires the converter to be registered in the client-shared map, which drags
15
+ * `@dereekb/firebase-server` into browser builds.
16
+ *
10
17
  * @param systemStateCollection - The Firestore collection for system state documents.
11
18
  * @returns A cache service backed by Firestore system state documents.
12
19
  *
13
20
  * @example
14
21
  * ```ts
15
- * const cacheService = firebaseZohoAccountsAccessTokenCacheService(systemStateCollection);
22
+ * const cacheService = firebaseZohoAccountsAccessTokenCacheService(systemStatePrivateCollection);
16
23
  * const cache = cacheService.loadZohoAccessTokenCache('my-zoho-service');
17
24
  * const token = await cache.loadCachedToken();
18
25
  * ```
19
26
  */
20
- export declare function firebaseZohoAccountsAccessTokenCacheService(systemStateCollection: SystemStateFirestoreCollection): ZohoAccountsAccessTokenCacheService;
27
+ export declare function firebaseZohoAccountsAccessTokenCacheService<D extends FirestoreDocument<SystemState<SystemStateStoredData>>>(systemStateCollection: SystemStateFirestoreCollectionLike<SystemStateStoredData, D>): ZohoAccountsAccessTokenCacheService;
@@ -0,0 +1,51 @@
1
+ import { type ConfigService } from '@nestjs/config';
2
+ import { type SystemStateStoredDataConverterMap } from '@dereekb/firebase';
3
+ import { type FirebaseServerEnvService } from '@dereekb/firebase-server';
4
+ import { type AES256GCMEncryptionSecret } from '@dereekb/nestjs';
5
+ import { type ZohoAccessTokenSystemStateDataConverterConfig } from './zoho.accounts.firebase.system';
6
+ /**
7
+ * Environment variable name for the Zoho access token cache encryption secret
8
+ * (hex-encoded AES-256 key).
9
+ *
10
+ * There is NO key rotation — `firestoreEncryptedField` resolves and validates the key once at
11
+ * converter construction and closes over it. Unlike the `uecp` and `oidcJwksKey` secrets, however,
12
+ * rotating this one is SURVIVABLE: the Zoho converter supplies an `onDecodeFailure` handler, so
13
+ * every entry written under the old key simply degrades to a cache miss and the next Zoho call
14
+ * re-mints a token. Rotation costs one extra token request per service key, not an outage.
15
+ */
16
+ export declare const ZOHO_ACCESS_TOKEN_ENCRYPTION_SECRET_ENV_KEY = "ZOHO_ACCESS_TOKEN_ENCRYPTION_SECRET";
17
+ /**
18
+ * Deterministic secret used when running in a testing environment and no real secret is configured,
19
+ * so specs never need a live credential.
20
+ *
21
+ * Deliberately distinct from the OIDC JWKS and UserExternalConnection testing secrets so a leaked
22
+ * emulator blob is attributable. ("Zoho Access Token Cache Test Key", hex-encoded.)
23
+ */
24
+ export declare const TESTING_ZOHO_ACCESS_TOKEN_ENCRYPTION_SECRET: AES256GCMEncryptionSecret;
25
+ /**
26
+ * Reads the Zoho access token encryption secret from the environment.
27
+ *
28
+ * @param configService - The Nest config service used to read the encryption secret.
29
+ * @param envService - Used to detect a testing environment for the secret fallback.
30
+ * @returns The validated encryption secret.
31
+ * @throws {Error} When the configured secret is invalid outside a testing environment.
32
+ */
33
+ export declare function zohoAccessTokenEncryptionSecretFactory(configService: ConfigService, envService: FirebaseServerEnvService): AES256GCMEncryptionSecret;
34
+ /**
35
+ * Builds the converter map entry for the Zoho access token cache, for use in a SERVER-ONLY
36
+ * SystemState converter map.
37
+ *
38
+ * @param config - The encryption configuration.
39
+ * @returns A partial converter map containing only the Zoho access token entry.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * const collections = systemStatePrivateFirestoreCollection({
44
+ * firestoreContext,
45
+ * converters: {
46
+ * ...zohoAccessTokenSystemStatePrivateConverterEntry({ encryptionSecret })
47
+ * }
48
+ * });
49
+ * ```
50
+ */
51
+ export declare function zohoAccessTokenSystemStatePrivateConverterEntry(config: ZohoAccessTokenSystemStateDataConverterConfig): SystemStateStoredDataConverterMap;
@@ -1,5 +1,6 @@
1
1
  import { type ZohoAccessToken, type ZohoServiceAccessTokenKey } from '@dereekb/zoho';
2
- import { type FirestoreDocumentAccessor, type SystemState, type SystemStateDocument, type SystemStateStoredData, type SystemStateStoredDataFieldConverterConfig } from '@dereekb/firebase';
2
+ import { type FirestoreDocument, type FirestoreDocumentAccessor, type FirestoreModelFieldMapFunctionsConfig, type SystemState, type SystemStateDocument, type SystemStateStoredData, type SystemStateStoredDataFieldConverterConfig } from '@dereekb/firebase';
3
+ import { type AES256GCMEncryptionSecretSource } from '@dereekb/nestjs';
3
4
  import { type Configurable } from '@dereekb/util';
4
5
  /**
5
6
  * {@link SystemState} type identifier for storing Zoho access tokens in Firestore.
@@ -15,7 +16,35 @@ export interface ZohoAccessTokenSystemStateEmbeddedToken extends Configurable<Zo
15
16
  */
16
17
  key: ZohoServiceAccessTokenKey;
17
18
  }
18
- export declare const zohoAccessTokenSystemStateEmbeddedTokenConverter: import("@dereekb/firebase").FirestoreSubObjectFieldMapFunctionsConfig<ZohoAccessTokenSystemStateEmbeddedToken, Partial<import("@dereekb/util").ReplaceType<ZohoAccessTokenSystemStateEmbeddedToken, import("@dereekb/util").MaybeMap<object>, any>>>;
19
+ /**
20
+ * Configuration for the encrypted Zoho access token converters.
21
+ */
22
+ export interface ZohoAccessTokenSystemStateDataConverterConfig {
23
+ /**
24
+ * Encryption secret source for the `accessToken` field.
25
+ */
26
+ readonly encryptionSecret: AES256GCMEncryptionSecretSource;
27
+ }
28
+ /**
29
+ * Creates the embedded-token converter, encrypting the `accessToken` at rest.
30
+ *
31
+ * This is a factory rather than a module-level const because `firestoreEncryptedField` resolves and
32
+ * validates the encryption key eagerly at construction — the secret must be known at runtime.
33
+ *
34
+ * ONLY `accessToken` is encrypted, deliberately. `firestoreEncryptedField` round-trips through
35
+ * `JSON.stringify`/`JSON.parse`, so anything placed inside the encrypted blob loses its type — and
36
+ * `expiresAt` is a `Date`. Encrypting the token string alone keeps every date outside the blob,
37
+ * which is what lets {@link zohoAccessTokenSystemStateDataConverterFactory} keep filtering expired
38
+ * entries without having to decrypt them first. Do not "improve" this by encrypting the whole
39
+ * token object or the `tokens` array.
40
+ *
41
+ * Accepted trade-off: `key`, `scope`, `apiDomain`, `expiresIn` and `expiresAt` remain plaintext at
42
+ * rest. None of them is a credential.
43
+ *
44
+ * @param config - The encryption configuration.
45
+ * @returns The embedded token field converter.
46
+ */
47
+ export declare function zohoAccessTokenSystemStateEmbeddedTokenConverterFactory(config: ZohoAccessTokenSystemStateDataConverterConfig): FirestoreModelFieldMapFunctionsConfig<ZohoAccessTokenSystemStateEmbeddedToken, any>;
19
48
  /**
20
49
  * Data shape stored within a {@link SystemState} document for caching multiple Zoho access tokens.
21
50
  *
@@ -33,13 +62,17 @@ export interface ZohoAccessTokenSystemStateData extends SystemStateStoredData {
33
62
  lat: Date;
34
63
  }
35
64
  /**
36
- * Firestore field converter for {@link ZohoAccessTokenSystemStateData}.
65
+ * Creates the {@link ZohoAccessTokenSystemStateData} converter, encrypting each token's
66
+ * `accessToken` at rest.
67
+ *
68
+ * Register the result in a SERVER-ONLY converter map under {@link ZOHO_ACCESS_TOKEN_SYSTEM_STATE_TYPE} —
69
+ * see `systemStatePrivateFirestoreCollection()` in `@dereekb/firebase-server/model`. It must never be
70
+ * registered in an app's client-shared `SystemStateStoredDataConverterMap`.
37
71
  *
38
- * Automatically filters out expired tokens on read and enforces uniqueness by service key.
39
- * Must be registered in the app's {@link SystemStateStoredDataConverterMap} under
40
- * the {@link ZOHO_ACCESS_TOKEN_SYSTEM_STATE_TYPE} key.
72
+ * @param config - The encryption configuration.
73
+ * @returns The stored-data field converter.
41
74
  */
42
- export declare const zohoAccessTokenSystemStateDataConverter: SystemStateStoredDataFieldConverterConfig<ZohoAccessTokenSystemStateData>;
75
+ export declare function zohoAccessTokenSystemStateDataConverterFactory(config: ZohoAccessTokenSystemStateDataConverterConfig): SystemStateStoredDataFieldConverterConfig<ZohoAccessTokenSystemStateData>;
43
76
  /**
44
77
  * Loads the {@link SystemStateDocument} that stores {@link ZohoAccessTokenSystemStateData},
45
78
  * using {@link ZOHO_ACCESS_TOKEN_SYSTEM_STATE_TYPE} as the document ID.
@@ -53,4 +86,14 @@ export declare const zohoAccessTokenSystemStateDataConverter: SystemStateStoredD
53
86
  * const data = await doc.snapshotData();
54
87
  * ```
55
88
  */
56
- export declare function loadZohoAccessTokenSystemState(accessor: FirestoreDocumentAccessor<SystemState<SystemStateStoredData>, SystemStateDocument<SystemStateStoredData>>): SystemStateDocument<ZohoAccessTokenSystemStateData>;
89
+ export declare function loadZohoAccessTokenSystemState<D extends FirestoreDocument<SystemState<SystemStateStoredData>>>(accessor: FirestoreDocumentAccessor<SystemState<SystemStateStoredData>, D>): SystemStateDocument<ZohoAccessTokenSystemStateData>;
90
+ /**
91
+ * @deprecated stores the access token in PLAINTEXT. Use
92
+ * {@link zohoAccessTokenSystemStateEmbeddedTokenConverterFactory} instead, which encrypts it at rest.
93
+ */
94
+ export declare const zohoAccessTokenSystemStateEmbeddedTokenConverter: import("@dereekb/firebase").FirestoreSubObjectFieldMapFunctionsConfig<ZohoAccessTokenSystemStateEmbeddedToken, Partial<import("@dereekb/util").ReplaceType<ZohoAccessTokenSystemStateEmbeddedToken, import("@dereekb/util").MaybeMap<object>, any>>>;
95
+ /**
96
+ * @deprecated stores access tokens in PLAINTEXT. Use {@link zohoAccessTokenSystemStateDataConverterFactory}
97
+ * instead, and register it on a server-only SystemStatePrivate collection.
98
+ */
99
+ export declare const zohoAccessTokenSystemStateDataConverter: SystemStateStoredDataFieldConverterConfig<ZohoAccessTokenSystemStateData>;