@dereekb/firebase-server 13.11.18 → 13.12.1

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 (67) hide show
  1. package/index.cjs.js +218 -31
  2. package/index.esm.js +217 -34
  3. package/mailgun/package.json +9 -9
  4. package/mcp/index.cjs.default.js +1 -0
  5. package/mcp/index.cjs.js +3646 -0
  6. package/mcp/index.cjs.mjs +2 -0
  7. package/mcp/index.d.ts +1 -0
  8. package/mcp/index.esm.js +3609 -0
  9. package/mcp/package.json +33 -0
  10. package/mcp/src/index.d.ts +1 -0
  11. package/mcp/src/lib/controller/index.d.ts +2 -0
  12. package/mcp/src/lib/controller/mcp.controller.d.ts +19 -0
  13. package/mcp/src/lib/controller/mcp.wellknown.controller.d.ts +25 -0
  14. package/mcp/src/lib/index.d.ts +5 -0
  15. package/mcp/src/lib/mcp.config.d.ts +100 -0
  16. package/mcp/src/lib/mcp.module.d.ts +49 -0
  17. package/mcp/src/lib/service/index.d.ts +8 -0
  18. package/mcp/src/lib/service/mcp.manifest.d.ts +150 -0
  19. package/mcp/src/lib/service/mcp.response-formatter.d.ts +36 -0
  20. package/mcp/src/lib/service/mcp.server.factory.d.ts +124 -0
  21. package/mcp/src/lib/service/mcp.tool-generator.d.ts +235 -0
  22. package/mcp/src/lib/service/mcp.visibility.d.ts +99 -0
  23. package/mcp/src/lib/service/tools/mcp.tool.model-decode.d.ts +87 -0
  24. package/mcp/src/lib/service/tools/mcp.tool.model-get.d.ts +81 -0
  25. package/mcp/src/lib/service/tools/mcp.tool.model-info.d.ts +84 -0
  26. package/mcp/src/lib/service/tools/mcp.tool.whoami.d.ts +72 -0
  27. package/mcp/src/lib/transport/index.d.ts +1 -0
  28. package/mcp/src/lib/transport/streamable-http.transport.d.ts +21 -0
  29. package/model/index.cjs.js +639 -266
  30. package/model/index.esm.js +640 -270
  31. package/model/package.json +9 -9
  32. package/model/src/lib/storagefile/extension/compress.pdf.d.ts +29 -1
  33. package/model/src/lib/storagefile/index.d.ts +1 -0
  34. package/model/src/lib/storagefile/storagefile.action.server.d.ts +29 -2
  35. package/model/src/lib/storagefile/storagefile.mcp.d.ts +32 -0
  36. package/model/src/lib/storagefile/storagefile.module.d.ts +20 -3
  37. package/oidc/index.cjs.js +322 -147
  38. package/oidc/index.esm.js +321 -148
  39. package/oidc/package.json +10 -10
  40. package/oidc/src/lib/controller/oidc.wellknown.controller.d.ts +6 -15
  41. package/oidc/src/lib/middleware/oauth-auth.middleware.d.ts +22 -2
  42. package/oidc/src/lib/middleware/oauth-auth.module.d.ts +57 -0
  43. package/oidc/src/lib/oidc.config.d.ts +73 -0
  44. package/oidc/src/lib/oidc.module.d.ts +44 -2
  45. package/package.json +22 -10
  46. package/src/lib/auth/auth.service.d.ts +16 -1
  47. package/src/lib/auth/auth.service.error.util.d.ts +24 -5
  48. package/src/lib/env/env.config.d.ts +16 -0
  49. package/src/lib/env/env.service.d.ts +9 -0
  50. package/src/lib/nest/controller/model/model.api.get.service.d.ts +35 -1
  51. package/src/lib/nest/env/env.service.d.ts +1 -0
  52. package/src/lib/nest/model/api.details.d.ts +116 -5
  53. package/src/lib/nest/model/crud.assert.function.d.ts +1 -1
  54. package/src/lib/nest/model/index.d.ts +1 -0
  55. package/src/lib/nest/model/invoke.model.function.d.ts +89 -0
  56. package/test/package.json +11 -11
  57. package/twilio/LICENSE +21 -0
  58. package/twilio/index.cjs.default.js +1 -0
  59. package/twilio/index.cjs.js +404 -0
  60. package/twilio/index.cjs.mjs +2 -0
  61. package/twilio/index.d.ts +1 -0
  62. package/twilio/index.esm.js +398 -0
  63. package/twilio/package.json +25 -0
  64. package/twilio/src/index.d.ts +1 -0
  65. package/twilio/src/lib/index.d.ts +1 -0
  66. package/twilio/src/lib/notification.send.service.twilio.d.ts +148 -0
  67. package/zoho/package.json +9 -9
@@ -1,14 +1,16 @@
1
1
  import { JwksService } from '../service/oidc.jwks.service';
2
- import { OidcModuleConfig } from '../oidc.config';
3
2
  import { OidcProviderConfigService, type OidcDiscoveryMetadata } from '../service/oidc.config.service';
4
3
  /**
5
- * Controller for OAuth/OIDC discovery and metadata endpoints.
4
+ * Controller for OIDC discovery and JWKS endpoints under `/.well-known`.
5
+ *
6
+ * The protected-resource discovery doc (`/.well-known/oauth-protected-resource`)
7
+ * is owned by the protected resource itself (the MCP module's
8
+ * `McpWellKnownController`), not the authorization server, per RFC 9728.
6
9
  */
7
10
  export declare class OidcWellKnownController {
8
- private readonly config;
9
11
  private readonly providerConfigService;
10
12
  private readonly jwksService;
11
- constructor(config: OidcModuleConfig, providerConfigService: OidcProviderConfigService, jwksService: JwksService);
13
+ constructor(providerConfigService: OidcProviderConfigService, jwksService: JwksService);
12
14
  /**
13
15
  * OpenID Connect Discovery endpoint (RFC 8414 / OpenID Connect Discovery 1.0).
14
16
  *
@@ -28,15 +30,4 @@ export declare class OidcWellKnownController {
28
30
  getJwks(): Promise<{
29
31
  keys: import("..").JsonWebKeyWithKid[];
30
32
  }>;
31
- /**
32
- * OAuth Protected Resource discovery endpoint (RFC 8707).
33
- *
34
- * Returns the authorization server(s) that protect this resource,
35
- * allowing clients to discover which authorization server to use.
36
- *
37
- * @returns The protected resource metadata with authorization server URLs.
38
- */
39
- getProtectedResource(): {
40
- authorization_servers: string[];
41
- };
42
33
  }
@@ -2,6 +2,7 @@ import { type NestMiddleware } from '@nestjs/common';
2
2
  import { type Response, type NextFunction } from 'express';
3
3
  import { OidcService } from '../service/oidc.service';
4
4
  import { type OidcAuthenticatedRequest } from '../service/oidc.auth';
5
+ import { OidcAuthMiddlewareConfig } from './oauth-auth.module';
5
6
  /**
6
7
  * NestJS middleware that verifies OAuth bearer tokens issued by our OIDC provider.
7
8
  *
@@ -9,13 +10,32 @@ import { type OidcAuthenticatedRequest } from '../service/oidc.auth';
9
10
  * verifies it via the provider's AccessToken model, and attaches the
10
11
  * auth context to the request as {@link OidcAuthData}.
11
12
  *
13
+ * On 401, emits an RFC 9728 `WWW-Authenticate: Bearer ...` header — including
14
+ * the `resource_metadata` discovery URL when `OidcAuthMiddlewareConfig.resourceMetadataUrl`
15
+ * is configured — so OAuth-aware clients (Claude, mcp-inspector, etc.) can
16
+ * locate the discovery doc explicitly.
17
+ *
12
18
  * Applied to routes via {@link ConfigureOidcAuthMiddlewareModule}.
13
19
  *
20
+ * @example
21
+ * Wired automatically when `protectedPaths` is set on `oidcModuleMetadata`:
22
+ * ```ts
23
+ * oidcModuleMetadata({
24
+ * dependencyModule: MyOidcDependencyModule,
25
+ * config: {
26
+ * protectedPaths: ['/api/model', '/mcp'],
27
+ * resourceMetadataUrl: 'https://api.example.com/.well-known/oauth-protected-resource'
28
+ * }
29
+ * })
30
+ * ```
31
+ *
14
32
  * @throws {UnauthorizedException} When the Authorization header is missing, malformed, or the token is invalid/expired.
15
33
  */
16
34
  export declare class OidcAuthBearerTokenMiddleware implements NestMiddleware {
17
35
  private readonly oidcService;
36
+ private readonly config;
18
37
  private readonly logger;
19
- constructor(oidcService: OidcService);
20
- use(req: OidcAuthenticatedRequest, _res: Response, next: NextFunction): Promise<void>;
38
+ constructor(oidcService: OidcService, config: OidcAuthMiddlewareConfig);
39
+ use(req: OidcAuthenticatedRequest, res: Response, next: NextFunction): Promise<void>;
40
+ private _setWwwAuthenticate;
21
41
  }
@@ -6,6 +6,23 @@ import { type SlashPath } from '@dereekb/util';
6
6
  * Works in reverse of `FirebaseAppCheckMiddlewareConfig`: instead of protecting
7
7
  * all routes and ignoring some, this only protects explicitly specified paths.
8
8
  * Routes under the global API prefix (protected by AppCheck) are excluded.
9
+ *
10
+ * @example
11
+ * Manual configuration without going through `oidcModuleMetadata`:
12
+ * ```ts
13
+ * @Module({
14
+ * providers: [
15
+ * {
16
+ * provide: OidcAuthMiddlewareConfig,
17
+ * useValue: {
18
+ * protectedPaths: ['/api/model', '/mcp'],
19
+ * resourceMetadataUrl: 'https://api.example.com/.well-known/oauth-protected-resource'
20
+ * } satisfies OidcAuthMiddlewareConfig
21
+ * }
22
+ * ]
23
+ * })
24
+ * export class MyApiModule {}
25
+ * ```
9
26
  */
10
27
  export declare abstract class OidcAuthMiddlewareConfig {
11
28
  /**
@@ -16,7 +33,30 @@ export declare abstract class OidcAuthMiddlewareConfig {
16
33
  * since those are protected by AppCheck.
17
34
  */
18
35
  readonly protectedPaths: SlashPath[];
36
+ /**
37
+ * Absolute URL of the OAuth 2.0 Protected Resource Metadata document
38
+ * (RFC 9728). When set, included as the `resource_metadata` parameter
39
+ * of the `WWW-Authenticate: Bearer` header on 401 responses so OAuth
40
+ * clients can locate the discovery doc explicitly rather than relying on
41
+ * origin-rooted path-walkback (which fails when the function isn't
42
+ * mounted at `/` — e.g. behind the Firebase Functions emulator URL prefix).
43
+ */
44
+ readonly resourceMetadataUrl?: string;
19
45
  }
46
+ /**
47
+ * Builds the `WWW-Authenticate: Bearer ...` challenge string emitted on 401
48
+ * responses to OAuth-protected routes.
49
+ *
50
+ * Per RFC 6750 §3 / RFC 7235, auth-params are comma-separated (optionally
51
+ * with surrounding whitespace). When `resourceMetadataUrl` is provided, it's
52
+ * included as the RFC 9728 `resource_metadata` hint so clients can locate the
53
+ * discovery doc directly instead of relying on origin-rooted path-walkback.
54
+ *
55
+ * @param error - The RFC 6750 `error` token (e.g. `invalid_token`, `invalid_request`).
56
+ * @param resourceMetadataUrl - Optional protected-resource metadata URL.
57
+ * @returns Header value, e.g. `Bearer resource_metadata="…", error="invalid_token"`.
58
+ */
59
+ export declare function buildBearerChallenge(error: string, resourceMetadataUrl?: string): string;
20
60
  /**
21
61
  * Applies OAuth bearer token verification as global Express middleware on
22
62
  * the given NestJS application.
@@ -39,5 +79,22 @@ export declare abstract class OidcAuthMiddlewareConfig {
39
79
  * }
40
80
  * };
41
81
  * ```
82
+ *
83
+ * Pair with `oidcModuleMetadata` (passing `resourceMetadataUrl` on `config`) so
84
+ * the 401 emits an RFC 9728 `WWW-Authenticate: Bearer resource_metadata="..."`
85
+ * header — required when the resource server isn't mounted at the origin root
86
+ * (e.g. behind the Firebase Functions emulator URL prefix), since RFC 9728's
87
+ * default path-walkback lands at a 404 in that case.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * oidcModuleMetadata({
92
+ * dependencyModule: MyOidcDependencyModule,
93
+ * config: {
94
+ * protectedPaths: ['/api/model', '/mcp'],
95
+ * resourceMetadataUrl: 'http://localhost:9902/dereekb-components/us-central1/api/.well-known/oauth-protected-resource'
96
+ * }
97
+ * })
98
+ * ```
42
99
  */
43
100
  export declare function applyOidcAuthMiddleware(nestApp: INestApplication): void;
@@ -9,6 +9,37 @@ import { type JwksKeyConverterConfig } from './model';
9
9
  * Matches the `renderError` option from the oidc-provider `Configuration` type.
10
10
  */
11
11
  export type OidcRenderErrorFunction = Configuration['renderError'];
12
+ /**
13
+ * Per-resource configuration returned by `features.resourceIndicators.getResourceServerInfo`
14
+ * to oidc-provider when a client requests an access token bound to a specific resource
15
+ * (RFC 8707).
16
+ *
17
+ * Shape mirrors oidc-provider's `ResourceServer` documented at
18
+ * `features.resourceIndicators.getResourceServerInfo` in
19
+ * `node_modules/oidc-provider/lib/helpers/defaults.js`.
20
+ */
21
+ export interface OidcResourceServerInfo {
22
+ /**
23
+ * Space-delimited scopes valid on this resource server. Issued access tokens
24
+ * are filtered to the intersection of the client-requested scopes and this
25
+ * allow-list.
26
+ */
27
+ readonly scope: string;
28
+ /**
29
+ * `aud` claim placed on tokens bound to this resource. Defaults to the
30
+ * resource indicator URL when omitted.
31
+ */
32
+ readonly audience?: string;
33
+ /**
34
+ * Access-token TTL override for this resource (seconds). Falls back to the
35
+ * provider's `ttl.AccessToken` when omitted.
36
+ */
37
+ readonly accessTokenTTL?: number;
38
+ /**
39
+ * Access-token format. Defaults to `'opaque'`.
40
+ */
41
+ readonly accessTokenFormat?: 'opaque' | 'jwt';
42
+ }
12
43
  /**
13
44
  * OIDC provider-level configuration for scopes, grant types, response types,
14
45
  * and claim mappings. These values drive both the oidc-provider instance and the
@@ -193,6 +224,48 @@ export declare abstract class OidcModuleConfig {
193
224
  * since those are typically protected by AppCheck.
194
225
  */
195
226
  readonly protectedPaths?: SlashPath[];
227
+ /**
228
+ * Map of recognized OAuth resource indicator URLs (RFC 8707) to their
229
+ * {@link OidcResourceServerInfo}. When non-empty, oidc-provider's
230
+ * `features.resourceIndicators.getResourceServerInfo` is wired to look up
231
+ * each requested `resource` parameter against this map and reject unknown
232
+ * resources with `invalid_target`.
233
+ *
234
+ * Required for OAuth-aware MCP clients (Claude, mcp-inspector) — they pass
235
+ * the MCP URL from the protected-resource discovery doc as the `resource`
236
+ * parameter on `/authorize` and `/token`, and oidc-provider rejects every
237
+ * such request with `invalid_target` unless that URL is recognized here.
238
+ *
239
+ * @example
240
+ * ```ts
241
+ * resourceServers: {
242
+ * 'http://localhost:9902/dereekb-components/us-central1/api/mcp': {
243
+ * scope: 'openid profile email offline_access demo model.create model.read model.update model.delete model.query model.invoke',
244
+ * audience: 'http://localhost:9902/dereekb-components/us-central1/api/mcp'
245
+ * }
246
+ * }
247
+ * ```
248
+ */
249
+ readonly resourceServers?: Readonly<Record<string, OidcResourceServerInfo>>;
250
+ /**
251
+ * Absolute URL of the OAuth 2.0 Protected Resource Metadata document
252
+ * (RFC 9728) for the resources guarded by {@link protectedPaths}.
253
+ *
254
+ * When set, the OIDC bearer middleware emits
255
+ * `WWW-Authenticate: Bearer resource_metadata="<url>", error="invalid_token"`
256
+ * on 401 responses, so OAuth-aware clients (Claude, mcp-inspector) can
257
+ * locate the discovery doc without relying on RFC 9728's origin-rooted
258
+ * path-walkback rule — which only works when the resource server is
259
+ * actually mounted at the origin root.
260
+ *
261
+ * Typically derived from `appMcpUrl` (or the equivalent protected-resource
262
+ * URL) — e.g. for `appMcpUrl = https://api.example.com/mcp`, the value is
263
+ * `https://api.example.com/.well-known/oauth-protected-resource`.
264
+ *
265
+ * @example
266
+ * resourceMetadataUrl: 'http://localhost:9902/dereekb-components/us-central1/api/.well-known/oauth-protected-resource'
267
+ */
268
+ readonly resourceMetadataUrl?: string;
196
269
  /**
197
270
  * Supported token endpoint authentication methods.
198
271
  *
@@ -74,6 +74,20 @@ export declare const FIREBASE_SERVER_OIDC_ROUTES_FOR_GLOBAL_ROUTE_EXCLUDE: strin
74
74
  * @throws {Error} When `appUrl` is missing, the resolved issuer lacks an HTTP prefix, or the encryption secret is invalid.
75
75
  */
76
76
  export declare function oidcModuleConfigFactory(configService: ConfigService, envService: FirebaseServerEnvService): OidcModuleConfig;
77
+ /**
78
+ * Derives the RFC 9728 protected-resource metadata URL from `envService.appMcpUrl`.
79
+ *
80
+ * For `appMcpUrl = http://localhost:9902/dereekb-components/us-central1/api/mcp`, this
81
+ * returns `http://localhost:9902/dereekb-components/us-central1/api/.well-known/oauth-protected-resource` —
82
+ * i.e. the discovery doc colocated with the MCP endpoint, regardless of any URL prefix
83
+ * imposed by the runtime (Firebase Functions emulator, Cloud Run path, etc.).
84
+ *
85
+ * Returns `undefined` when no `appMcpUrl` is configured.
86
+ *
87
+ * @param envService - The Firebase server environment service.
88
+ * @returns The discovery URL, or `undefined` if `appMcpUrl` is not set.
89
+ */
90
+ export declare function deriveResourceMetadataUrlFromEnv(envService: FirebaseServerEnvService): string | undefined;
77
91
  /**
78
92
  * Factory that creates {@link OidcServerFirestoreCollections} using the provided Firestore context
79
93
  * and JWKS encryption config from {@link OidcModuleConfig}.
@@ -83,6 +97,11 @@ export declare function oidcModuleConfigFactory(configService: ConfigService, en
83
97
  * @returns The configured OidcServerFirestoreCollections.
84
98
  */
85
99
  export declare function oidcFirestoreCollectionsFactory(firestoreContext: FirestoreContext, oidcModuleConfig: OidcModuleConfig): OidcServerFirestoreCollections;
100
+ /**
101
+ * Subset of {@link OidcModuleConfig} that consumers may override via
102
+ * `oidcModuleMetadata`'s `config` or `configFactory`.
103
+ */
104
+ export type OidcModuleMetadataOverrides = Partial<Pick<OidcModuleConfig, 'issuer' | 'suppressBodyParserWarning' | 'renderError' | 'protectedPaths' | 'appOAuthInteractionPath' | 'appOAuthLoginUrlPart' | 'appOAuthConsentUrlPart' | 'tokenEndpointAuthMethods' | 'registrationEnabled' | 'trustProxy' | 'trustProxyInNonProduction' | 'tokenLifetimes' | 'maxRequestedLoginDuration' | 'minRequestedLoginDuration' | 'defaultRequestedLoginDuration' | 'resourceServers' | 'resourceMetadataUrl'>>;
86
105
  export interface ProvideAppOidcModuleMetadataConfig extends Pick<ModuleMetadata, 'imports' | 'exports' | 'providers'> {
87
106
  /**
88
107
  * Module that exports the required dependencies for this module.
@@ -90,14 +109,37 @@ export interface ProvideAppOidcModuleMetadataConfig extends Pick<ModuleMetadata,
90
109
  */
91
110
  readonly dependencyModule: Required<ModuleMetadata>['imports']['0'];
92
111
  /**
93
- * Optional overrides to merge into the {@link OidcModuleConfig} produced by the factory.
112
+ * Optional static overrides to merge into the {@link OidcModuleConfig} produced by the factory.
94
113
  *
95
114
  * The `issuer` override is honored verbatim and takes precedence over the factory-derived issuer
96
115
  * (which is normally built from `envService.appApiUrl` origin ?? `envService.appUrl`). Pass an
97
116
  * explicit `issuer` when the consumer wants a canonical issuer URL that does not match either
98
117
  * environment URL — e.g., when serving OIDC behind a non-`/oidc` path or under a vanity host.
118
+ *
119
+ * For values that must be derived from runtime services (e.g. `resourceServers` keyed on
120
+ * `envService.appMcpUrl`), use {@link configFactory} instead. When both are supplied,
121
+ * `configFactory`'s result is merged on top of `config`.
122
+ */
123
+ readonly config?: OidcModuleMetadataOverrides;
124
+ /**
125
+ * Optional dynamic overrides that depend on runtime services. Called inside the
126
+ * `OidcModuleConfig` factory with the resolved {@link FirebaseServerEnvService} and
127
+ * {@link ConfigService}. Merged on top of {@link config} (so `configFactory` values win).
128
+ *
129
+ * Use this when an override field (typically `resourceServers` or `resourceMetadataUrl`)
130
+ * must be derived from `envService.appMcpUrl` rather than from a value that's stable
131
+ * at module-import time.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * configFactory: (envService) => ({
136
+ * resourceServers: envService.appMcpUrl
137
+ * ? { [envService.appMcpUrl]: { scope: ALL_SCOPES, audience: envService.appMcpUrl } }
138
+ * : undefined
139
+ * })
140
+ * ```
99
141
  */
100
- readonly config?: Partial<Pick<OidcModuleConfig, 'issuer' | 'suppressBodyParserWarning' | 'renderError' | 'protectedPaths' | 'appOAuthInteractionPath' | 'appOAuthLoginUrlPart' | 'appOAuthConsentUrlPart' | 'tokenEndpointAuthMethods' | 'registrationEnabled' | 'trustProxy' | 'trustProxyInNonProduction' | 'tokenLifetimes' | 'maxRequestedLoginDuration' | 'minRequestedLoginDuration' | 'defaultRequestedLoginDuration'>>;
142
+ readonly configFactory?: (envService: FirebaseServerEnvService, configService: ConfigService) => OidcModuleMetadataOverrides;
101
143
  }
102
144
  /**
103
145
  * Convenience function used to generate ModuleMetadata for an app's OidcModule.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server",
3
- "version": "13.11.18",
3
+ "version": "13.12.1",
4
4
  "sideEffects": false,
5
5
  "exports": {
6
6
  "./test": {
@@ -15,6 +15,12 @@
15
15
  "import": "./mailgun/index.cjs.mjs",
16
16
  "default": "./mailgun/index.cjs.js"
17
17
  },
18
+ "./twilio": {
19
+ "module": "./twilio/index.esm.js",
20
+ "types": "./twilio/index.d.ts",
21
+ "import": "./twilio/index.cjs.mjs",
22
+ "default": "./twilio/index.cjs.js"
23
+ },
18
24
  "./zoho": {
19
25
  "module": "./zoho/index.esm.js",
20
26
  "types": "./zoho/index.d.ts",
@@ -33,6 +39,12 @@
33
39
  "import": "./oidc/index.cjs.mjs",
34
40
  "default": "./oidc/index.cjs.js"
35
41
  },
42
+ "./mcp": {
43
+ "module": "./mcp/index.esm.js",
44
+ "types": "./mcp/index.d.ts",
45
+ "import": "./mcp/index.cjs.mjs",
46
+ "default": "./mcp/index.cjs.js"
47
+ },
36
48
  "./package.json": "./package.json",
37
49
  ".": {
38
50
  "module": "./index.esm.js",
@@ -44,15 +56,15 @@
44
56
  "main": "./index.cjs.js",
45
57
  "types": "./src/index.d.ts",
46
58
  "peerDependencies": {
47
- "@dereekb/analytics": "13.11.18",
48
- "@dereekb/date": "13.11.18",
49
- "@dereekb/dbx-core": "13.11.18",
50
- "@dereekb/firebase": "13.11.18",
51
- "@dereekb/model": "13.11.18",
52
- "@dereekb/nestjs": "13.11.18",
53
- "@dereekb/rxjs": "13.11.18",
54
- "@dereekb/util": "13.11.18",
55
- "@dereekb/zoho": "13.11.18",
59
+ "@dereekb/analytics": "13.12.1",
60
+ "@dereekb/date": "13.12.1",
61
+ "@dereekb/dbx-core": "13.12.1",
62
+ "@dereekb/firebase": "13.12.1",
63
+ "@dereekb/model": "13.12.1",
64
+ "@dereekb/nestjs": "13.12.1",
65
+ "@dereekb/rxjs": "13.12.1",
66
+ "@dereekb/util": "13.12.1",
67
+ "@dereekb/zoho": "13.12.1",
56
68
  "@google-cloud/firestore": "^7.11.6",
57
69
  "@google-cloud/storage": "^7.19.0",
58
70
  "@nestjs/common": "^11.1.19",
@@ -180,6 +180,13 @@ export declare abstract class AbstractFirebaseServerAuthUserContext<S extends Fi
180
180
  */
181
181
  protected _generateResetPasswordKey(): string;
182
182
  beginResetPassword(): Promise<FirebaseServerAuthResetUserPasswordClaims>;
183
+ /**
184
+ * Returns the TTL (in milliseconds) for newly-generated reset codes. Subclasses may override
185
+ * to customize expiry.
186
+ *
187
+ * @returns The TTL in milliseconds for newly-generated reset codes.
188
+ */
189
+ protected _resetCodeExpiresInMs(): Milliseconds;
183
190
  loadResetPasswordClaims<T extends FirebaseServerAuthResetUserPasswordClaims = FirebaseServerAuthResetUserPasswordClaims>(): Promise<Maybe<T>>;
184
191
  setPassword(password: PasswordString): Promise<admin.auth.UserRecord>;
185
192
  updateUser(template: admin.auth.UpdateRequest): Promise<admin.auth.UserRecord>;
@@ -753,11 +760,19 @@ export interface FirebaseServerUserPasswordResetService<D = unknown, U extends F
753
760
  completePasswordReset(uid: FirebaseAuthUserId, input: FirebaseServerAuthCompletePasswordResetInput): Promise<admin.auth.UserRecord>;
754
761
  }
755
762
  /**
756
- * Default throttle duration (1 hour) between reset content sends to prevent spam.
763
+ * Default throttle duration (60 seconds) between reset content re-sends to prevent spam while
764
+ * remaining responsive for legitimate users who didn't receive the first email.
757
765
  *
758
766
  * Used by {@link AbstractFirebaseServerUserPasswordResetService.sendResetContent} to rate-limit delivery.
759
767
  */
760
768
  export declare const DEFAULT_RESET_COM_THROTTLE_TIME: number;
769
+ /**
770
+ * Default lifetime (15 minutes) of a reset code before it expires.
771
+ *
772
+ * Used by {@link AbstractFirebaseServerAuthUserContext.beginResetPassword} to set
773
+ * `resetExpiresAt` in the user's claims.
774
+ */
775
+ export declare const DEFAULT_RESET_CODE_EXPIRES_IN: number;
761
776
  /**
762
777
  * Base implementation of {@link FirebaseServerUserPasswordResetService} that handles reset initiation,
763
778
  * claims management, throttled reset content delivery, and reset completion.
@@ -1,18 +1,32 @@
1
+ import { type Maybe } from '@dereekb/util';
1
2
  /**
2
3
  * Wraps an async function that uses {@link FirebaseServerUserPasswordResetService} methods,
3
4
  * catching password-reset-specific errors and re-throwing them as appropriate {@link HttpsError} instances
4
5
  * suitable for returning to clients.
5
6
  *
6
- * Error mapping:
7
+ * Default mapping (security-hardened — enumeration-safe, for end-user callers):
7
8
  * - {@link FirebaseServerAuthPasswordResetInvalidCodeError} → permission-denied (403)
8
- * - {@link FirebaseServerAuthPasswordResetNoResetConfigError} → bad-request (400)
9
- * - {@link FirebaseServerAuthPasswordResetThrottleError} → unavailable (503)
10
- * - {@link FirebaseServerAuthPasswordResetSendOnceError} → bad-request (400)
9
+ * - {@link FirebaseServerAuthPasswordResetNoResetConfigError} → permission-denied (403), same opaque
10
+ * response as InvalidCode so callers cannot distinguish "wrong code" from "no reset active".
11
+ * - {@link FirebaseServerAuthPasswordResetSendOnceError} → permission-denied (403), same opaque
12
+ * response so callers cannot probe whether a reset was already initiated.
13
+ * - {@link FirebaseServerAuthPasswordResetThrottleError} → unavailable (503) with a generic message
14
+ * (no `lastSentAt` is leaked).
15
+ *
16
+ * Admin mapping (`isAdmin: true`) — preserves distinct error codes and statuses so an admin
17
+ * resetting on behalf of another user can see the actual reason. Used when the call originates
18
+ * from an authenticated admin, where enumeration is not a concern:
19
+ * - {@link FirebaseServerAuthPasswordResetInvalidCodeError} → permission-denied (403)
20
+ * - {@link FirebaseServerAuthPasswordResetNoResetConfigError} → bad-request (400), distinct message
21
+ * - {@link FirebaseServerAuthPasswordResetSendOnceError} → bad-request (400), distinct message
22
+ * - {@link FirebaseServerAuthPasswordResetThrottleError} → unavailable (503), same as default
11
23
  *
12
24
  * @param fn - The async function to execute.
25
+ * @param isAdmin - When `true`, returns distinct error codes/messages instead of the
26
+ * enumeration-safe opaque response. Pass the caller's admin status here.
13
27
  * @returns The result of the function.
14
28
  */
15
- export declare function catchAndThrowPasswordResetServerErrors<T>(fn: () => Promise<T>): Promise<T>;
29
+ export declare function catchAndThrowPasswordResetServerErrors<T>(fn: () => Promise<T>, isAdmin?: Maybe<boolean>): Promise<T>;
16
30
  /**
17
31
  * Creates a permission-denied (403) server error for an invalid or expired password reset code.
18
32
  *
@@ -22,6 +36,11 @@ export declare function authServicePasswordResetInvalidCodeError(): import("fire
22
36
  /**
23
37
  * Creates a bad-request (400) server error when no active password reset exists for the user.
24
38
  *
39
+ * Distinct from {@link authServicePasswordResetInvalidCodeError}. Use only when the caller is
40
+ * trusted (e.g., an admin) and revealing "no reset active" is not an enumeration risk; for
41
+ * end-user callers, {@link catchAndThrowPasswordResetServerErrors} maps this case to the same
42
+ * opaque response as `InvalidCodeError`.
43
+ *
25
44
  * @returns An {@link HttpsError} with code `invalid-argument`.
26
45
  */
27
46
  export declare function authServicePasswordResetNoConfigError(): import("firebase-functions/https").HttpsError;
@@ -13,6 +13,22 @@ export interface FirebaseServerEnvironmentConfig extends ServerEnvironmentConfig
13
13
  * it from `appUrl + globalApiRoutePrefix`.
14
14
  */
15
15
  readonly appApiUrl?: Maybe<WebsiteUrlWithPrefix>;
16
+ /**
17
+ * The MCP endpoint URL (e.g., 'https://api.example.com/mcp').
18
+ *
19
+ * When set, the firebase-server MCP module advertises this URL as the protected
20
+ * resource in `/.well-known/oauth-protected-resource`, and the OIDC bearer
21
+ * middleware uses its origin to construct the `WWW-Authenticate`
22
+ * `resource_metadata` hint on 401 responses.
23
+ *
24
+ * Set explicitly when the MCP endpoint lives on a different origin than the
25
+ * frontend `appUrl` (e.g., dev: behind a different port than the SPA proxy;
26
+ * prod: a dedicated `api.*` subdomain that bypasses Firebase Hosting).
27
+ *
28
+ * When omitted, the MCP module falls back to deriving the URL from
29
+ * `appApiUrl` (or `appUrl`).
30
+ */
31
+ readonly appMcpUrl?: Maybe<WebsiteUrlWithPrefix>;
16
32
  /**
17
33
  * The webhook URL. When not set explicitly, `buildNestServerRootModule()` computes
18
34
  * it from `appUrl + /webhook`.
@@ -45,6 +45,15 @@ export declare abstract class FirebaseServerEnvService {
45
45
  * The full API URL (e.g., 'https://app.example.com/api').
46
46
  */
47
47
  abstract readonly appApiUrl: Maybe<WebsiteUrl>;
48
+ /**
49
+ * The full MCP endpoint URL (e.g., 'https://api.example.com/mcp').
50
+ *
51
+ * When set, advertised verbatim in `/.well-known/oauth-protected-resource` and
52
+ * used to derive the OIDC bearer middleware's `WWW-Authenticate`
53
+ * `resource_metadata` hint. When omitted, modules fall back to deriving the
54
+ * URL from `appApiUrl` / `appUrl`.
55
+ */
56
+ abstract readonly appMcpUrl: Maybe<WebsiteUrl>;
48
57
  /**
49
58
  * The full webhook URL (e.g., 'https://app.example.com/webhook').
50
59
  */
@@ -1,5 +1,5 @@
1
1
  import { type Maybe } from '@dereekb/util';
2
- import { type FirestoreModelKey, type FirestoreModelType } from '@dereekb/firebase';
2
+ import { type FirestoreModelIdentity, type FirestoreModelKey, type FirestoreModelType } from '@dereekb/firebase';
3
3
  import { type INestApplicationContext } from '@nestjs/common';
4
4
  import { ModelApiDispatchConfig } from './model.api.dispatch';
5
5
  import { type FirebaseServerAuthData } from '../auth.context.server';
@@ -29,6 +29,24 @@ export interface ModelAccessReadError {
29
29
  readonly message: string;
30
30
  readonly code?: string;
31
31
  }
32
+ /**
33
+ * Shape of a single failed-key entry from `useMultipleModels({ throwOnFirstError: false })`.
34
+ * Exposed so the mapper below stays testable without a live nest context.
35
+ */
36
+ export interface ModelAccessUseMultipleModelsFailureEntry {
37
+ readonly key: FirestoreModelKey;
38
+ readonly error: unknown;
39
+ }
40
+ /**
41
+ * Maps a single `useMultipleModels` failure entry into the public {@link ModelAccessReadError}
42
+ * shape. Unwraps the typical Firebase error shapes via {@link firebaseServerErrorInfo} so
43
+ * permission-denied / not-found errors surface a real message + code instead of the generic
44
+ * `"Unknown error"` fallback the inline mapping used previously.
45
+ *
46
+ * @param entry - A failed-key entry from the underlying multi-read.
47
+ * @returns A `{ key, message, code? }` triple safe to return to API/MCP callers.
48
+ */
49
+ export declare function modelAccessReadErrorFromUseMultipleModelsFailure(entry: ModelAccessUseMultipleModelsFailureEntry): ModelAccessReadError;
32
50
  /**
33
51
  * Service for direct document reads using the `useModel()` permission-checking pattern.
34
52
  *
@@ -38,7 +56,23 @@ export interface ModelAccessReadError {
38
56
  */
39
57
  export declare class ModelApiGetService {
40
58
  private readonly _nestContext;
59
+ private _identityByModelType;
41
60
  constructor(config: ModelApiDispatchConfig, nestApplication: INestApplicationContext);
61
+ /**
62
+ * Returns the registered {@link FirestoreModelIdentity} for the given `modelType` string, or
63
+ * `undefined` when no model of that type is registered.
64
+ *
65
+ * Identities are read from `firebaseModelsService` and cached on first successful call. The
66
+ * lookup uses the provided `auth` to build a real model context — `getFirestoreCollection(ctx)`
67
+ * is context-dependent (calls `ctx.collection(...)` or similar), so a synthetic empty context
68
+ * is not sufficient.
69
+ *
70
+ * @param modelType - The Firestore model type string (e.g., 'guestbook', 'profile').
71
+ * @param auth - The request's auth data; used to build a context for the (one-time) lookup.
72
+ * @returns The matching identity or `undefined`.
73
+ */
74
+ getModelIdentity(modelType: FirestoreModelType, auth: Maybe<FirebaseServerAuthData>): Maybe<FirestoreModelIdentity>;
75
+ private _buildIdentityMap;
42
76
  /**
43
77
  * Reads a single document by model type and key with permission checking.
44
78
  *
@@ -16,6 +16,7 @@ export declare class DefaultFirebaseServerEnvService extends ServerEnvironmentSe
16
16
  */
17
17
  get developmentSchedulerEnabled(): boolean;
18
18
  get appUrlDetails(): Maybe<WebsiteUrlDetails>;
19
+ get appMcpUrl(): Maybe<WebsiteUrl>;
19
20
  get appWebhookUrl(): Maybe<WebsiteUrl>;
20
21
  get isApiEnabled(): boolean;
21
22
  get isWebhooksEnabled(): boolean;