@dereekb/firebase-server 14.3.0 → 14.5.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 (43) hide show
  1. package/calcom/package.json +11 -10
  2. package/discord/package.json +14 -10
  3. package/index.esm.js +1295 -317
  4. package/mailgun/package.json +9 -9
  5. package/mcp/index.esm.js +483 -83
  6. package/mcp/package.json +12 -11
  7. package/mcp/src/lib/mcp.config.d.ts +73 -3
  8. package/mcp/src/lib/service/index.d.ts +1 -0
  9. package/mcp/src/lib/service/mcp.server.factory.d.ts +3 -2
  10. package/mcp/src/lib/service/mcp.tool-generator.d.ts +9 -0
  11. package/mcp/src/lib/service/tools/mcp.tool.cli-token.d.ts +62 -0
  12. package/model/package.json +13 -10
  13. package/oidc/index.esm.js +4552 -1902
  14. package/oidc/package.json +12 -10
  15. package/oidc/src/lib/controller/index.d.ts +3 -0
  16. package/oidc/src/lib/controller/oidc.cli-token.config.d.ts +323 -0
  17. package/oidc/src/lib/controller/oidc.cli-token.controller.d.ts +45 -0
  18. package/oidc/src/lib/controller/oidc.cli-token.service.d.ts +138 -0
  19. package/oidc/src/lib/controller/oidc.interaction.controller.d.ts +38 -0
  20. package/oidc/src/lib/index.d.ts +1 -0
  21. package/oidc/src/lib/middleware/oauth-auth.module.d.ts +11 -0
  22. package/oidc/src/lib/oidc.config.d.ts +9 -0
  23. package/oidc/src/lib/oidc.module.d.ts +2 -39
  24. package/oidc/src/lib/oidc.resource-server.d.ts +170 -0
  25. package/oidc/src/lib/service/index.d.ts +2 -0
  26. package/oidc/src/lib/service/oidc.config.service.d.ts +8 -3
  27. package/oidc/src/lib/service/oidc.download-signer.d.ts +17 -0
  28. package/oidc/src/lib/service/oidc.jwt-signing.service.d.ts +112 -0
  29. package/oidc/src/lib/service/oidc.jwt-verify.d.ts +37 -0
  30. package/oidc/src/lib/service/oidc.service.d.ts +35 -4
  31. package/package.json +14 -12
  32. package/src/lib/nest/controller/download/download.api.config.d.ts +203 -0
  33. package/src/lib/nest/controller/download/download.api.controller.d.ts +25 -0
  34. package/src/lib/nest/controller/download/download.api.module.d.ts +47 -0
  35. package/src/lib/nest/controller/download/download.api.service.d.ts +111 -0
  36. package/src/lib/nest/controller/download/index.d.ts +4 -0
  37. package/src/lib/nest/controller/index.d.ts +2 -0
  38. package/src/lib/nest/controller/request.ip.d.ts +60 -0
  39. package/test/index.esm.js +169 -43
  40. package/test/package.json +12 -11
  41. package/test/src/lib/oidc/oidc.test.flow.d.ts +170 -11
  42. package/twilio/package.json +8 -8
  43. package/zoho/package.json +14 -10
@@ -1,6 +1,6 @@
1
1
  import { type ModuleMetadata } from '@nestjs/common';
2
2
  import { ConfigService } from '@nestjs/config';
3
- import { OidcModuleConfig, type OidcProviderConfig, type OidcResourceServerInfo } from './oidc.config';
3
+ import { OidcModuleConfig } from './oidc.config';
4
4
  import { type FirestoreContext } from '@dereekb/firebase';
5
5
  import { FirebaseServerEnvService } from '@dereekb/firebase-server';
6
6
  import { OidcServerFirestoreCollections } from './model/model';
@@ -88,43 +88,6 @@ export declare function oidcModuleConfigFactory(configService: ConfigService, en
88
88
  * @returns The discovery URL, or `undefined` if `appMcpUrl` is not set.
89
89
  */
90
90
  export declare function deriveResourceMetadataUrlFromEnv(envService: FirebaseServerEnvService): string | undefined;
91
- /**
92
- * Input for {@link buildFirebaseServerMcpResourceServer}.
93
- */
94
- export interface BuildFirebaseServerMcpResourceServerInput {
95
- /**
96
- * The Firebase server environment service. The MCP URL is read from `envService.appMcpUrl`.
97
- */
98
- readonly envService: FirebaseServerEnvService;
99
- /**
100
- * The OIDC provider config whose scopes back the resource server's `scope` value.
101
- */
102
- readonly providerConfig: OidcProviderConfig;
103
- }
104
- /**
105
- * Builds a single-entry {@link OidcResourceServerInfo} map for the MCP endpoint
106
- * declared on `envService.appMcpUrl`, with `scope` set to every scope declared
107
- * on `providerConfig.claims` (so any scope the provider issues is valid on the
108
- * resource server) and `audience` set to the MCP URL.
109
- *
110
- * Returns `undefined` when no `appMcpUrl` is configured.
111
- *
112
- * Used internally by {@link oidcModuleMetadata} when {@link OidcModuleConfig.configureMcpResourceServer}
113
- * is enabled, and exported for apps that need to combine the MCP resource server
114
- * with additional app-specific entries:
115
- *
116
- * @example
117
- * ```ts
118
- * resourceServers: {
119
- * ...(buildFirebaseServerMcpResourceServer({ envService, providerConfig: APP_PROVIDER_CONFIG }) ?? {}),
120
- * 'https://api.example.com/extras': {
121
- * scope: 'openid profile',
122
- * audience: 'https://api.example.com/extras'
123
- * }
124
- * }
125
- * ```
126
- */
127
- export declare function buildFirebaseServerMcpResourceServer(input: BuildFirebaseServerMcpResourceServerInput): Record<string, OidcResourceServerInfo> | undefined;
128
91
  /**
129
92
  * Factory that creates {@link OidcServerFirestoreCollections} using the provided Firestore context
130
93
  * and JWKS encryption config from {@link OidcModuleConfig}.
@@ -138,7 +101,7 @@ export declare function oidcFirestoreCollectionsFactory(firestoreContext: Firest
138
101
  * Subset of {@link OidcModuleConfig} that consumers may override via
139
102
  * `oidcModuleMetadata`'s `config` or `configFactory`.
140
103
  */
141
- export type OidcModuleMetadataOverrides = Partial<Pick<OidcModuleConfig, 'issuer' | 'suppressBodyParserWarning' | 'renderError' | 'protectedPaths' | 'appOAuthInteractionPath' | 'appOAuthLoginUrlPart' | 'appOAuthConsentUrlPart' | 'tokenEndpointAuthMethods' | 'registrationEnabled' | 'trustProxy' | 'trustProxyInNonProduction' | 'tokenLifetimes' | 'maxRequestedLoginDuration' | 'minRequestedLoginDuration' | 'defaultRequestedLoginDuration' | 'resourceServers' | 'resourceMetadataUrl' | 'configureMcpResourceServer' | 'cors'>>;
104
+ export type OidcModuleMetadataOverrides = Partial<Pick<OidcModuleConfig, 'issuer' | 'suppressBodyParserWarning' | 'renderError' | 'protectedPaths' | 'unprotectedPaths' | 'appOAuthInteractionPath' | 'appOAuthLoginUrlPart' | 'appOAuthConsentUrlPart' | 'tokenEndpointAuthMethods' | 'registrationEnabled' | 'trustProxy' | 'trustProxyInNonProduction' | 'tokenLifetimes' | 'maxRequestedLoginDuration' | 'minRequestedLoginDuration' | 'defaultRequestedLoginDuration' | 'resourceServers' | 'resourceMetadataUrl' | 'configureMcpResourceServer' | 'cors'>>;
142
105
  export interface ProvideAppOidcModuleMetadataConfig extends Pick<ModuleMetadata, 'imports' | 'exports' | 'providers'> {
143
106
  /**
144
107
  * Module that exports the required dependencies for this module.
@@ -0,0 +1,170 @@
1
+ import { type BuildIssuerProfilesConfig, type BuildIssuerProfilesOidcIssuerInput } from '@dereekb/oauth-resource';
2
+ import { type FirebaseServerEnvService } from '@dereekb/firebase-server';
3
+ import { type Maybe, type Seconds, type WebsiteUrlWithPrefix } from '@dereekb/util';
4
+ import { type OidcModuleConfig, type OidcProviderConfig, type OidcResourceServerInfo } from './oidc.config';
5
+ export interface BuildOidcResourceServerConfig {
6
+ /**
7
+ * The RFC 8707 resource indicator — the value a client sends as `resource=` on `/authorize` and
8
+ * `/token`, and the key this entry is registered under. Usually the resource's full URL, e.g.
9
+ * `https://db.example.com/mcp`.
10
+ */
11
+ readonly url: WebsiteUrlWithPrefix;
12
+ /**
13
+ * Space-delimited scopes valid on this resource server. Issued access tokens are filtered to the
14
+ * intersection of the client-requested scopes and this allow-list, so keep it to what the
15
+ * resource actually understands rather than every scope the provider issues.
16
+ */
17
+ readonly scope: string;
18
+ /**
19
+ * `aud` claim placed on tokens bound to this resource. Defaults to {@link url}.
20
+ *
21
+ * An OFF-BOX resource server usually wants its ORIGIN here (`https://db.example.com`) rather
22
+ * than the `/mcp` resource-indicator URL, so one audience covers every route it serves.
23
+ */
24
+ readonly audience?: Maybe<string>;
25
+ /**
26
+ * Access-token format. Defaults to oidc-provider's `'opaque'`.
27
+ *
28
+ * **Set `'jwt'` for any resource server that is not this API process.** An opaque access token is
29
+ * a database key: it can only be validated by the issuer's own adapter store, so a remote service
30
+ * fundamentally cannot verify it. A `'jwt'` token is an RS256 JWT signed with the provider's
31
+ * active JWKS key (no extra provider config needed — `getResourceServerConfig` falls back to
32
+ * `clientDefaults.id_token_signed_response_alg`, i.e. `RS256`), which any service can verify via
33
+ * `@dereekb/oauth-resource` against the published `/.well-known/jwks.json`.
34
+ *
35
+ * The trade-off: a JWT access token is NOT persisted by oidc-provider, so it has no adapter
36
+ * record and **cannot be revoked before `exp`**. Revoking the Grant stops refresh and future
37
+ * issuance but leaves outstanding JWTs valid — keep {@link accessTokenTTL} short.
38
+ */
39
+ readonly accessTokenFormat?: Maybe<'opaque' | 'jwt'>;
40
+ /**
41
+ * Access-token TTL for this resource, in seconds. Falls back to the provider's `ttl.AccessToken`.
42
+ */
43
+ readonly accessTokenTTL?: Maybe<Seconds>;
44
+ }
45
+ /**
46
+ * Builds a single-entry {@link OidcResourceServerInfo} map for an RFC 8707 resource indicator.
47
+ *
48
+ * This is the supported way to declare a resource server the provider issues tokens for —
49
+ * including one that runs OFF-BOX (a Docker'd sidecar, a worker, a standalone MCP host), which is
50
+ * what {@link BuildOidcResourceServerConfig.accessTokenFormat} `'jwt'` exists for.
51
+ *
52
+ * @param config - The resource indicator, its scopes, and the token shape to issue for it.
53
+ * @returns A single-entry map keyed by the resource indicator.
54
+ *
55
+ * @example
56
+ * ```ts
57
+ * const DB_ORIGIN = 'https://db.example.com';
58
+ *
59
+ * oidcModuleMetadata({
60
+ * dependencyModule: MyOidcDependencyModule,
61
+ * config: {
62
+ * resourceServers: buildOidcResourceServer({
63
+ * url: `${DB_ORIGIN}/mcp`,
64
+ * scope: 'openid profile email offline_access',
65
+ * audience: DB_ORIGIN,
66
+ * accessTokenFormat: 'jwt',
67
+ * accessTokenTTL: 3600
68
+ * })
69
+ * }
70
+ * })
71
+ * ```
72
+ */
73
+ export declare function buildOidcResourceServer(config: BuildOidcResourceServerConfig): Record<string, OidcResourceServerInfo>;
74
+ /**
75
+ * Input for {@link buildFirebaseServerMcpResourceServer}.
76
+ */
77
+ export interface BuildFirebaseServerMcpResourceServerInput {
78
+ /**
79
+ * The Firebase server environment service. The MCP URL is read from `envService.appMcpUrl`.
80
+ */
81
+ readonly envService: FirebaseServerEnvService;
82
+ /**
83
+ * The OIDC provider config whose scopes back the resource server's `scope` value.
84
+ */
85
+ readonly providerConfig: OidcProviderConfig;
86
+ /**
87
+ * Access-token format. Defaults to oidc-provider's `'opaque'`.
88
+ *
89
+ * See {@link BuildOidcResourceServerConfig.accessTokenFormat}.
90
+ */
91
+ readonly accessTokenFormat?: Maybe<'opaque' | 'jwt'>;
92
+ /**
93
+ * Access-token TTL for the MCP resource, in seconds.
94
+ */
95
+ readonly accessTokenTTL?: Maybe<Seconds>;
96
+ }
97
+ /**
98
+ * Builds a single-entry {@link OidcResourceServerInfo} map for the MCP endpoint
99
+ * declared on `envService.appMcpUrl`, with `scope` set to every scope declared
100
+ * on `providerConfig.claims` (so any scope the provider issues is valid on the
101
+ * resource server) and `audience` set to the MCP URL.
102
+ *
103
+ * Returns `undefined` when no `appMcpUrl` is configured.
104
+ *
105
+ * Used internally by `oidcModuleMetadata` when `OidcModuleConfig.configureMcpResourceServer`
106
+ * is enabled.
107
+ *
108
+ * @param input - The environment service and provider config backing the entry.
109
+ * @returns The single-entry map, or `undefined` when no MCP URL is configured.
110
+ *
111
+ * @deprecated Prefer {@link buildOidcResourceServer}, which takes the URL, scope, audience, and
112
+ * token format explicitly instead of deriving a fixed shape from the environment.
113
+ */
114
+ export declare function buildFirebaseServerMcpResourceServer(input: BuildFirebaseServerMcpResourceServerInput): Record<string, OidcResourceServerInfo> | undefined;
115
+ export interface FirebaseServerIssuerProfilesConfig {
116
+ /**
117
+ * The provider's own module config. Its `issuer` becomes the trusted OIDC issuer, and every
118
+ * registered `resourceServers` entry contributes its key and `audience` to the accepted
119
+ * audience list.
120
+ */
121
+ readonly oidcModuleConfig: Pick<OidcModuleConfig, 'issuer' | 'resourceServers'>;
122
+ /**
123
+ * Firebase project ids whose ID tokens the resource server also trusts.
124
+ */
125
+ readonly firebaseProjectIds?: Maybe<readonly string[]>;
126
+ /**
127
+ * Extra audiences to accept beyond the ones derived from `resourceServers` — typically the
128
+ * resource server's own origin when it is not itself a registered resource indicator.
129
+ */
130
+ readonly audiences?: Maybe<readonly string[]>;
131
+ /**
132
+ * Overrides how the provider's verification keys are resolved, bypassing discovery. The API
133
+ * verifying its OWN tokens in-process passes a local key set here rather than making an HTTP
134
+ * call back to itself.
135
+ */
136
+ readonly getKey?: Maybe<Required<Exclude<BuildIssuerProfilesOidcIssuerInput, string>>['getKey']>;
137
+ /**
138
+ * `fetch` used for OIDC discovery; injected in specs.
139
+ */
140
+ readonly fetch?: Maybe<typeof fetch>;
141
+ }
142
+ /**
143
+ * Emits the `@dereekb/oauth-resource` {@link BuildIssuerProfilesConfig} for THIS provider, so an
144
+ * API and the satellite services it issues tokens for cannot drift on issuer / audience strings —
145
+ * both sides read the same `OidcModuleConfig`.
146
+ *
147
+ * The type dependency points `firebase-server → oauth-resource` and never the reverse: the light
148
+ * verify package must stay installable in a plain Express service with no Firebase, Nest, or
149
+ * oidc-provider in its install graph.
150
+ *
151
+ * @param config - The provider's module config plus any extra trusted projects / audiences.
152
+ * @returns The issuer-profile config to hand to `buildIssuerProfiles()`.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * // inside the API, to hand its satellites a config they cannot get wrong
157
+ * const profilesConfig = firebaseServerIssuerProfiles({ oidcModuleConfig, firebaseProjectIds: [projectId] });
158
+ * const profiles = buildIssuerProfiles(profilesConfig);
159
+ * ```
160
+ */
161
+ export declare function firebaseServerIssuerProfiles(config: FirebaseServerIssuerProfilesConfig): BuildIssuerProfilesConfig;
162
+ /**
163
+ * Reads every audience a registered resource-server map can put on an access token: each entry's
164
+ * key (the resource indicator, which oidc-provider uses as the audience by default) plus its
165
+ * explicit `audience` when one is configured.
166
+ *
167
+ * @param resourceServers - The provider's registered resource servers, if any.
168
+ * @returns The unique audience values.
169
+ */
170
+ export declare function oidcResourceServerAudiences(resourceServers: Maybe<Record<string, OidcResourceServerInfo>>): string[];
@@ -11,3 +11,5 @@ export * from './oidc.client.service';
11
11
  export * from './oidc.interaction.service';
12
12
  export * from './oidc.grant';
13
13
  export * from './oidc.interaction-policy';
14
+ export * from './oidc.jwt-signing.service';
15
+ export * from './oidc.download-signer';
@@ -90,9 +90,14 @@ export declare class OidcProviderConfigService {
90
90
  * provider does support those scopes, for the clients holding the profile. This narrower list is
91
91
  * for metadata a client treats as a request template — notably an MCP protected-resource
92
92
  * document's `scopes_supported`, which dynamic-registration clients (the Claude Code CLI) copy
93
- * verbatim onto `/authorize`. An assignment-only scope advertised there ends the flow in
94
- * `access_denied`: the consent unlock gate judges the REQUEST, so unlike an admin-only scope
95
- * there is no deselect-at-consent way through.
93
+ * verbatim onto `/authorize`. Defaulting such a document to this narrower list keeps an
94
+ * unassigned client from requesting a scope it can never be granted.
95
+ *
96
+ * A resource MAY still advertise an assignment-only scope deliberately (demo-api does for
97
+ * `token.cli`, so an MCP connector can ask for it at all). That is not fatal for the clients
98
+ * lacking the profile: the consent URL builder withholds the scope from them, so it lands in the
99
+ * submit's `rejected` set and the flow completes without it — the same treatment an admin-only
100
+ * scope gets for a non-admin.
96
101
  */
97
102
  readonly clientRequestableScopesSupported: string[];
98
103
  /**
@@ -0,0 +1,17 @@
1
+ import { type DownloadTokenSigner } from '@dereekb/firebase-server';
2
+ import { type OidcJwtSigningService } from './oidc.jwt-signing.service';
3
+ /**
4
+ * Adapts {@link OidcJwtSigningService} to the `@dereekb/firebase-server` {@link DownloadTokenSigner}
5
+ * interface, so the signed asset-download endpoint can use the OIDC provider's own JWKS without
6
+ * `@dereekb/firebase-server` taking a dependency on this package.
7
+ *
8
+ * Using the provider's JWKS is the point: it is already shared across function instances and already
9
+ * rotates, so a download capability token needs no new secret to distribute. The `typ` + `aud`
10
+ * discriminators the download module passes in are what keep such a token from ever being accepted
11
+ * as an OAuth access token (and vice versa) even though both are signed by the same keys.
12
+ *
13
+ * @param signingService - The provider's JWT signing service.
14
+ * @returns A signer to register under the `DOWNLOAD_TOKEN_SIGNER` injection token.
15
+ * @__NO_SIDE_EFFECTS__
16
+ */
17
+ export declare function oidcDownloadTokenSigner(signingService: OidcJwtSigningService): DownloadTokenSigner;
@@ -0,0 +1,112 @@
1
+ import { type Maybe, type Milliseconds, type Seconds } from '@dereekb/util';
2
+ import { type JWTPayload } from 'jose';
3
+ import { OidcModuleConfig } from '../oidc.config';
4
+ import { JwksService } from './oidc.jwks.service';
5
+ /**
6
+ * Default lifetime of a minted JWT, in seconds.
7
+ */
8
+ export declare const DEFAULT_OIDC_SIGNED_JWT_EXPIRES_IN_SECONDS: Seconds;
9
+ /**
10
+ * How long the active signing key is cached before it is re-read from the JWKS store.
11
+ *
12
+ * A rotation is picked up within this window; rotated keys stay in the public JWKS for 30 days, so
13
+ * tokens signed with the previous key keep verifying in the meantime.
14
+ */
15
+ export declare const DEFAULT_OIDC_JWT_SIGNING_KEY_CACHE_MS: Milliseconds;
16
+ /**
17
+ * Header `typ` for a minted JWT: an RFC 9068 JWT access token, matching the format oidc-provider
18
+ * uses for the `accessTokenFormat: 'jwt'` resource-server tokens.
19
+ */
20
+ export declare const DEFAULT_OIDC_SIGNED_JWT_TYP = "at+jwt";
21
+ export interface OidcSignJwtInput {
22
+ /**
23
+ * `aud` claim(s) — the resource the token is for, e.g. an off-box service's origin.
24
+ */
25
+ readonly audience: string | readonly string[];
26
+ /**
27
+ * `sub` claim — the first-party identity of the caller, e.g. `demo-api`.
28
+ */
29
+ readonly subject: string;
30
+ /**
31
+ * Lifetime in seconds. Defaults to {@link DEFAULT_OIDC_SIGNED_JWT_EXPIRES_IN_SECONDS}.
32
+ */
33
+ readonly expiresIn?: Maybe<Seconds>;
34
+ /**
35
+ * Extra claims merged onto the payload, e.g. `{ client_id, scope }`.
36
+ */
37
+ readonly claims?: Maybe<Record<string, unknown>>;
38
+ /**
39
+ * Header `typ`. Defaults to {@link DEFAULT_OIDC_SIGNED_JWT_TYP}.
40
+ */
41
+ readonly typ?: Maybe<string>;
42
+ }
43
+ export interface OidcVerifyJwtInput {
44
+ /**
45
+ * The compact JWT to verify.
46
+ */
47
+ readonly token: string;
48
+ /**
49
+ * Required `aud` claim value.
50
+ */
51
+ readonly audience: string | readonly string[];
52
+ /**
53
+ * Required header `typ`. Pass the SAME discriminator the token was minted with — a first-party JWT
54
+ * minted for a non-OAuth purpose (e.g. an asset-download capability) is signed by the same JWKS as
55
+ * the provider's `at+jwt` access tokens, so `typ` + `aud` are what keep the two from being
56
+ * interchangeable.
57
+ */
58
+ readonly typ?: Maybe<string>;
59
+ }
60
+ export interface OidcSignedJwt {
61
+ readonly token: string;
62
+ readonly expiresAt: Date;
63
+ /**
64
+ * `kid` of the JWKS key that signed the token.
65
+ */
66
+ readonly kid: string;
67
+ }
68
+ /**
69
+ * Signs first-party JWTs with the OIDC provider's active JWKS signing key.
70
+ *
71
+ * The result is an RS256 token with `iss` = this provider's issuer, so any resource server that
72
+ * already trusts the provider's JWKS for OAuth access tokens verifies it with no new secret or
73
+ * trust anchor: "the API mints a token for the satellite service" is literally "sign with the
74
+ * active key, `aud` = the satellite's origin". The counterpart on the resource server is
75
+ * `verifyBearerJwt` from `@dereekb/oauth-resource`, whose `oidc` issuer profile discovers this
76
+ * provider's `jwks_uri` from its own discovery document.
77
+ *
78
+ * Registered and exported by `oidcModuleMetadata`, so a downstream app injects it with no extra
79
+ * wiring. Pair it with `createCachedTokenProvider` from `@dereekb/util/oidc` to hold a minted
80
+ * token until it nears expiry instead of re-signing per call.
81
+ */
82
+ export declare class OidcJwtSigningService {
83
+ private readonly _jwks;
84
+ private readonly _config;
85
+ private _cached;
86
+ private _verifyKey;
87
+ constructor(_jwks: JwksService, _config: OidcModuleConfig);
88
+ /**
89
+ * Mints a signed JWT.
90
+ *
91
+ * @param input - Audience, subject, lifetime, and extra claims.
92
+ * @returns The compact token, its expiry, and the signing key id.
93
+ */
94
+ signJwt(input: OidcSignJwtInput): Promise<OidcSignedJwt>;
95
+ /**
96
+ * Verifies a JWT that THIS provider signed, against its own JWKS.
97
+ *
98
+ * Enforces `iss` (this provider), `aud`, and — when supplied — the header `typ`. Returns
99
+ * `undefined` rather than throwing when the token does not verify, so a caller serving an
100
+ * unauthenticated route can answer with one generic error for every failure mode.
101
+ *
102
+ * The counterpart to {@link signJwt}: `verifyAccessToken` deliberately does NOT accept a token
103
+ * minted with a non-`at+jwt` `typ` and a non-OAuth audience, and this method is how a purpose-built
104
+ * token proves it is the RIGHT kind.
105
+ *
106
+ * @param input - The token, its required audience, and its required `typ`.
107
+ * @returns The verified payload, or `undefined` when verification fails.
108
+ */
109
+ verifyJwt(input: OidcVerifyJwtInput): Promise<Maybe<JWTPayload>>;
110
+ private _getVerifyKey;
111
+ private _loadSigningKey;
112
+ }
@@ -0,0 +1,37 @@
1
+ import { type IssuerProfiles } from '@dereekb/oauth-resource';
2
+ import { type JWTVerifyGetKey } from 'jose';
3
+ import { type OidcModuleConfig } from '../oidc.config';
4
+ import { type JwksService } from './oidc.jwks.service';
5
+ /**
6
+ * Builds a jose {@link JWTVerifyGetKey} backed by the provider's OWN published JWKS, read directly
7
+ * from {@link JwksService} rather than over HTTP.
8
+ *
9
+ * An in-process verifier must not `createRemoteJWKSet` against its own `/.well-known/jwks.json`:
10
+ * that would make every bearer-authenticated request depend on the process being able to reach
11
+ * itself over the network (which it may not be, behind a load balancer, in a Functions emulator,
12
+ * or under supertest).
13
+ *
14
+ * The key set is cached and re-read only when a token presents a `kid` the cache does not hold —
15
+ * so a freshly rotated key is picked up on its first use instead of after a fixed TTL.
16
+ *
17
+ * @param jwksService - The provider's JWKS service.
18
+ * @returns The key resolver.
19
+ */
20
+ export declare function jwksServiceVerifyGetKey(jwksService: JwksService): JWTVerifyGetKey;
21
+ /**
22
+ * Builds the {@link IssuerProfiles} the provider uses to verify its OWN JWT-format access tokens
23
+ * in-process.
24
+ *
25
+ * A JWT access token is issued for a specific `resource=`, so it carries THAT resource server's
26
+ * audience — not the API's own identity. The accepted audiences are therefore every registered
27
+ * `resourceServers` key and audience, plus the issuer itself. The trust decision that implies is
28
+ * deliberate and narrow: a resource server the user consented to may present the token it was
29
+ * given back to the API that issued it. Scope enforcement is unchanged and still does the real
30
+ * authorization work (e.g. the admin-only `session.firestore` consent gate and the endpoint's own
31
+ * admin predicate).
32
+ *
33
+ * @param config - The provider's module config.
34
+ * @param jwksService - The provider's JWKS service, used for local key resolution.
35
+ * @returns The issuer profiles, holding exactly this provider.
36
+ */
37
+ export declare function oidcProviderIssuerProfiles(config: OidcModuleConfig, jwksService: JwksService): IssuerProfiles;
@@ -23,6 +23,11 @@ export interface ResolveLoginDurationTier {
23
23
  */
24
24
  readonly hasServiceScope: boolean;
25
25
  }
26
+ /**
27
+ * Claims a JWT access token carries that the {@link OidcAuthData} shape sets explicitly, and that
28
+ * are therefore NOT app account claims.
29
+ */
30
+ export declare const OIDC_JWT_ACCESS_TOKEN_RESERVED_CLAIMS: readonly string[];
26
31
  /**
27
32
  * Core OIDC service that wraps the oidc-provider instance and exposes
28
33
  * typed methods for interaction handling, provider initialization, and JWKS management.
@@ -36,6 +41,7 @@ export declare class OidcService {
36
41
  private readonly encryptionService;
37
42
  private readonly _logger;
38
43
  private readonly _getProvider;
44
+ private readonly _getIssuerProfiles;
39
45
  constructor(config: OidcModuleConfig, providerConfigService: OidcProviderConfigService, jwksService: JwksService, accountService: OidcAccountService, collections: OidcServerFirestoreCollections, encryptionService: OidcEncryptionService);
40
46
  /**
41
47
  * Returns the oidc-provider instance, initializing it on first access.
@@ -67,15 +73,40 @@ export declare class OidcService {
67
73
  dbx_max_session_ttl?: number;
68
74
  } | undefined, tier: ResolveLoginDurationTier): number;
69
75
  /**
70
- * Verifies an opaque access token and returns the {@link OidcAuthData}.
76
+ * Verifies an access token and returns the {@link OidcAuthData}.
77
+ *
78
+ * Two formats are accepted, because oidc-provider issues two:
79
+ *
80
+ * - **opaque** (the default) — a database key. Looked up through the provider's `AccessToken`
81
+ * model, which reads the Firestore adapter record written at issuance.
82
+ * - **JWT** (a resource server registered with `accessTokenFormat: 'jwt'`) — verified by
83
+ * signature against this provider's own JWKS via `@dereekb/oauth-resource`. A JWT access token
84
+ * is deliberately NOT persisted by oidc-provider (`lib/models/formats/jwt.js` returns no
85
+ * payload, so `base_model.js` skips the `adapter.upsert`), which means `AccessToken.find()`
86
+ * always misses for one and the store lookup alone would 401 every token the provider just
87
+ * issued.
71
88
  *
72
- * Uses the provider's `AccessToken` model to look up the token and extract
73
- * the account ID, scope, and client ID.
89
+ * The same absence of an adapter record is why a JWT access token **cannot be revoked before
90
+ * `exp`**: {@link revokeGrant} deletes the Grant and every persisted token referencing it, which
91
+ * stops refresh and future issuance, but an outstanding JWT stays verifiable until it expires.
92
+ * Keep `accessTokenTTL` short on any resource server that opts into the JWT format.
74
93
  *
75
- * @param rawToken - The opaque access token string.
94
+ * @param rawToken - The access token string, opaque or JWT.
76
95
  * @returns The auth context, or `undefined` if the token is invalid or expired.
77
96
  */
78
97
  verifyAccessToken(rawToken: string): Promise<OidcAuthData | undefined>;
98
+ /**
99
+ * Verifies a JWT-format access token issued by THIS provider for one of its registered resource
100
+ * servers, and builds the same {@link OidcAuthData} shape the opaque path produces.
101
+ *
102
+ * `sub` is the `accountId` oidc-provider stamped at issuance, which IS the Firebase uid (the
103
+ * opaque path reads the identical value off `accessToken.accountId`), so no mapping table is
104
+ * involved.
105
+ *
106
+ * @param rawToken - The raw token, which may not be a JWT at all.
107
+ * @returns The auth context, or `undefined` when the token is not a verifiable JWT from this provider.
108
+ */
109
+ private _verifyJwtAccessToken;
79
110
  /**
80
111
  * Revokes a Grant entry and every grantable token entry that references it.
81
112
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dereekb/firebase-server",
3
- "version": "14.3.0",
3
+ "version": "14.5.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "exports": {
@@ -58,17 +58,18 @@
58
58
  },
59
59
  "peerDependencies": {
60
60
  "@cantoo/pdf-lib": ">=2.6.5 <2.11.0",
61
- "@dereekb/analytics": "14.3.0",
62
- "@dereekb/calcom": "14.3.0",
63
- "@dereekb/date": "14.3.0",
64
- "@dereekb/dbx-core": "14.3.0",
65
- "@dereekb/discord": "14.3.0",
66
- "@dereekb/firebase": "14.3.0",
67
- "@dereekb/model": "14.3.0",
68
- "@dereekb/nestjs": "14.3.0",
69
- "@dereekb/rxjs": "14.3.0",
70
- "@dereekb/util": "14.3.0",
71
- "@dereekb/zoho": "14.3.0",
61
+ "@dereekb/analytics": "14.5.0",
62
+ "@dereekb/calcom": "14.5.0",
63
+ "@dereekb/date": "14.5.0",
64
+ "@dereekb/dbx-core": "14.5.0",
65
+ "@dereekb/discord": "14.5.0",
66
+ "@dereekb/firebase": "14.5.0",
67
+ "@dereekb/model": "14.5.0",
68
+ "@dereekb/nestjs": "14.5.0",
69
+ "@dereekb/oauth-resource": "14.5.0",
70
+ "@dereekb/rxjs": "14.5.0",
71
+ "@dereekb/util": "14.5.0",
72
+ "@dereekb/zoho": "14.5.0",
72
73
  "@google-cloud/firestore": "^7.11.6",
73
74
  "@google-cloud/storage": "^7.22.0",
74
75
  "@modelcontextprotocol/node": "2.0.0",
@@ -85,6 +86,7 @@
85
86
  "firebase-admin": "^13.10.0",
86
87
  "firebase-functions": "^7.3.2",
87
88
  "firebase-functions-test": "3.5.0",
89
+ "jose": "^6.2.12",
88
90
  "jsonwebtoken": "^9.0.3",
89
91
  "make-error": "^1.3.6",
90
92
  "nanoid": "^6.0.1",