@dereekb/firebase-server 13.12.9 → 13.13.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.
- package/mailgun/package.json +9 -9
- package/mcp/package.json +11 -11
- package/model/index.cjs.js +209 -0
- package/model/index.esm.js +208 -2
- package/model/package.json +9 -9
- package/model/src/lib/storagefile/storagefile.action.server.d.ts +61 -1
- package/oidc/index.cjs.js +1025 -283
- package/oidc/index.esm.js +1016 -285
- package/oidc/package.json +10 -10
- package/oidc/src/lib/controller/oidc.interaction.controller.d.ts +4 -1
- package/oidc/src/lib/controller/oidc.provider.controller.d.ts +17 -0
- package/oidc/src/lib/oidc.config.d.ts +60 -0
- package/oidc/src/lib/service/analytics/index.d.ts +4 -0
- package/oidc/src/lib/service/analytics/oidc.analytics.config.d.ts +46 -0
- package/oidc/src/lib/service/analytics/oidc.analytics.handler.d.ts +116 -0
- package/oidc/src/lib/service/analytics/oidc.analytics.module.d.ts +35 -0
- package/oidc/src/lib/service/analytics/oidc.analytics.service.d.ts +23 -0
- package/oidc/src/lib/service/index.d.ts +1 -0
- package/oidc/src/lib/service/oidc.account.service.d.ts +15 -0
- package/oidc/src/lib/service/oidc.client.service.d.ts +4 -0
- package/oidc/src/lib/service/oidc.interaction.service.d.ts +2 -2
- package/oidc/src/lib/service/oidc.service.d.ts +23 -1
- package/oidc/src/lib/service/oidc.session-ttl.d.ts +114 -0
- package/package.json +10 -10
- package/test/index.cjs.js +11 -3
- package/test/index.esm.js +11 -3
- package/test/package.json +11 -11
- package/twilio/package.json +8 -8
- package/zoho/package.json +9 -9
package/oidc/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase-server/oidc",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.13.0",
|
|
4
4
|
"peerDependencies": {
|
|
5
|
-
"@dereekb/analytics": "13.
|
|
6
|
-
"@dereekb/date": "13.
|
|
7
|
-
"@dereekb/firebase": "13.
|
|
8
|
-
"@dereekb/firebase-server": "13.
|
|
9
|
-
"@dereekb/model": "13.
|
|
10
|
-
"@dereekb/nestjs": "13.
|
|
11
|
-
"@dereekb/rxjs": "13.
|
|
12
|
-
"@dereekb/util": "13.
|
|
13
|
-
"@dereekb/zoho": "13.
|
|
5
|
+
"@dereekb/analytics": "13.13.0",
|
|
6
|
+
"@dereekb/date": "13.13.0",
|
|
7
|
+
"@dereekb/firebase": "13.13.0",
|
|
8
|
+
"@dereekb/firebase-server": "13.13.0",
|
|
9
|
+
"@dereekb/model": "13.13.0",
|
|
10
|
+
"@dereekb/nestjs": "13.13.0",
|
|
11
|
+
"@dereekb/rxjs": "13.13.0",
|
|
12
|
+
"@dereekb/util": "13.13.0",
|
|
13
|
+
"@dereekb/zoho": "13.13.0",
|
|
14
14
|
"@nestjs/common": "^11.1.19",
|
|
15
15
|
"@nestjs/config": "^4.0.4",
|
|
16
16
|
"express": "^5.2.1",
|
|
@@ -4,6 +4,7 @@ import { type OAuthInteractionConsentRequest, type OAuthInteractionLoginRequest,
|
|
|
4
4
|
import { OidcAccountService } from '../service/oidc.account.service';
|
|
5
5
|
import { OidcInteractionService } from '../service/oidc.interaction.service';
|
|
6
6
|
import { OidcService } from '../service/oidc.service';
|
|
7
|
+
import { type OidcAnalyticsService } from '../service/analytics';
|
|
7
8
|
/**
|
|
8
9
|
* Controller for OIDC interaction endpoints (login/consent).
|
|
9
10
|
*
|
|
@@ -19,7 +20,9 @@ export declare class OidcInteractionController {
|
|
|
19
20
|
private readonly oidcProviderConfigService;
|
|
20
21
|
private readonly accountService;
|
|
21
22
|
private readonly oidcService;
|
|
22
|
-
|
|
23
|
+
private readonly _logger;
|
|
24
|
+
private readonly _analytics;
|
|
25
|
+
constructor(oidcInteractionService: OidcInteractionService, oidcProviderConfigService: OidcProviderConfigService, accountService: OidcAccountService, oidcService: OidcService, analytics?: OidcAnalyticsService);
|
|
23
26
|
/**
|
|
24
27
|
* GET /interaction/:uid.
|
|
25
28
|
*
|
|
@@ -29,5 +29,22 @@ export declare class OidcProviderController {
|
|
|
29
29
|
* @param res - Express response used to issue the 302 redirect to the configured `appLoginUrl`.
|
|
30
30
|
*/
|
|
31
31
|
redirectToClientLogin(req: Request, res: Response): void;
|
|
32
|
+
/**
|
|
33
|
+
* GET /oidc/session.
|
|
34
|
+
*
|
|
35
|
+
* Read route that verifies the presented bearer access token and returns its resolved session
|
|
36
|
+
* lifetime metadata: `{ sub, scope, expiresAt, rotationDisabled }`. `expiresAt` is the grant's
|
|
37
|
+
* expiry (unix seconds) and `rotationDisabled` flags a non-rotating (service) token. The values
|
|
38
|
+
* are sourced from the access token's baked-in `extra` claims (see `extraTokenClaims`), so this
|
|
39
|
+
* does not require decoding the opaque token client-side — cleaner than userinfo, which does not
|
|
40
|
+
* echo access-token `extra`.
|
|
41
|
+
*
|
|
42
|
+
* Declared ahead of the `@All('{*path}')` catch-all so it is matched here rather than proxied to
|
|
43
|
+
* the oidc-provider callback (mirrors `GET /oidc/login/client`).
|
|
44
|
+
*
|
|
45
|
+
* @param req - Inbound request; the `Authorization: Bearer <token>` header is read for the access token.
|
|
46
|
+
* @param res - Express response used to send the session JSON (401 when the token is missing/invalid).
|
|
47
|
+
*/
|
|
48
|
+
getSession(req: Request, res: Response): Promise<void>;
|
|
32
49
|
handleOidcRequest(req: Request, res: Response): Promise<void>;
|
|
33
50
|
}
|
|
@@ -86,6 +86,24 @@ export interface OidcProviderConfig<S extends OidcScope = OidcScope> {
|
|
|
86
86
|
* Supported OAuth 2.0 grant types (e.g., `['authorization_code', 'refresh_token']`).
|
|
87
87
|
*/
|
|
88
88
|
readonly grantTypes: string[];
|
|
89
|
+
/**
|
|
90
|
+
* Scopes that may only be granted to admin users. When a consent request includes any of these
|
|
91
|
+
* scopes and the resolving user is not an admin (per
|
|
92
|
+
* {@link OidcAccountServiceDelegate.isAdminUser}), the interaction is hard-rejected with
|
|
93
|
+
* `access_denied` rather than silently dropping the scope.
|
|
94
|
+
*
|
|
95
|
+
* Keeps the generic package app-agnostic: apps opt a scope (e.g. `token.service`) into admin-only
|
|
96
|
+
* behavior by listing it here, rather than the package hard-coding any scope name.
|
|
97
|
+
*/
|
|
98
|
+
readonly adminOnlyScopes?: readonly string[];
|
|
99
|
+
/**
|
|
100
|
+
* Scopes whose grants must never rotate their refresh token. When a grant's scope set intersects
|
|
101
|
+
* this list, {@link buildProviderConfiguration}'s `rotateRefreshToken` hook returns `false`,
|
|
102
|
+
* yielding a stable refresh token suitable for storing in a server environment variable.
|
|
103
|
+
*
|
|
104
|
+
* All other grants keep oidc-provider's default rotation behavior.
|
|
105
|
+
*/
|
|
106
|
+
readonly nonRotatingScopes?: readonly string[];
|
|
89
107
|
}
|
|
90
108
|
/**
|
|
91
109
|
* Returns the space-delimited list of every scope declared on `providerConfig.claims`.
|
|
@@ -142,6 +160,26 @@ export declare const DEFAULT_MAX_REQUESTED_LOGIN_DURATION_SECONDS: number;
|
|
|
142
160
|
* Default global floor for a client-requested login duration, in seconds. 1 hour.
|
|
143
161
|
*/
|
|
144
162
|
export declare const DEFAULT_MIN_REQUESTED_LOGIN_DURATION_SECONDS: number;
|
|
163
|
+
/**
|
|
164
|
+
* Default ceiling (seconds) on a login duration for a non-admin user. 45 days.
|
|
165
|
+
*
|
|
166
|
+
* Tier 1 of the tiered ceiling resolved by `resolveTieredServerMaxSeconds`.
|
|
167
|
+
*/
|
|
168
|
+
export declare const DEFAULT_MAX_NONADMIN_LOGIN_DURATION_SECONDS: number;
|
|
169
|
+
/**
|
|
170
|
+
* Default ceiling (seconds) on a normal (non-service-token) login duration for an admin user. 90 days.
|
|
171
|
+
*
|
|
172
|
+
* Tier 2 of the tiered ceiling resolved by `resolveTieredServerMaxSeconds`.
|
|
173
|
+
*/
|
|
174
|
+
export declare const DEFAULT_MAX_ADMIN_LOGIN_DURATION_SECONDS: number;
|
|
175
|
+
/**
|
|
176
|
+
* Default ceiling (seconds) on a login duration carrying an admin-only service-token scope. 365 days.
|
|
177
|
+
*
|
|
178
|
+
* Tier 3 of the tiered ceiling resolved by `resolveTieredServerMaxSeconds`. Also matches
|
|
179
|
+
* node-oidc-provider's absolute refresh-token rotation cap (`totalLifetime >= 365.25d`), so a
|
|
180
|
+
* service token issued at this ceiling never rotates.
|
|
181
|
+
*/
|
|
182
|
+
export declare const DEFAULT_MAX_SERVICE_TOKEN_LOGIN_DURATION_SECONDS: number;
|
|
145
183
|
/**
|
|
146
184
|
* Default token lifetimes: 15 min access tokens, 30-day refresh tokens, 30-day sessions/grants, 60 s auth codes.
|
|
147
185
|
*/
|
|
@@ -205,6 +243,28 @@ export declare abstract class OidcModuleConfig {
|
|
|
205
243
|
* auth-URL param. When undefined, falls back to {@link OidcTokenLifetimes.refreshToken}.
|
|
206
244
|
*/
|
|
207
245
|
readonly defaultRequestedLoginDuration?: number;
|
|
246
|
+
/**
|
|
247
|
+
* Tiered ceiling (seconds) on the requested login duration for a non-admin user.
|
|
248
|
+
*
|
|
249
|
+
* Used by `resolveTieredServerMaxSeconds` when the resolving user is not an admin; the resolved
|
|
250
|
+
* tier value is used as the `serverMaxSeconds` ceiling (it replaces the flat
|
|
251
|
+
* {@link maxRequestedLoginDuration}, since service-token tokens intentionally exceed it).
|
|
252
|
+
*
|
|
253
|
+
* Defaults to {@link DEFAULT_MAX_NONADMIN_LOGIN_DURATION_SECONDS} (45 days).
|
|
254
|
+
*/
|
|
255
|
+
readonly maxRequestedLoginDurationNonAdmin?: number;
|
|
256
|
+
/**
|
|
257
|
+
* Tiered ceiling (seconds) on a normal (non-service-token) requested login duration for an admin user.
|
|
258
|
+
*
|
|
259
|
+
* Defaults to {@link DEFAULT_MAX_ADMIN_LOGIN_DURATION_SECONDS} (90 days).
|
|
260
|
+
*/
|
|
261
|
+
readonly maxRequestedLoginDurationAdmin?: number;
|
|
262
|
+
/**
|
|
263
|
+
* Tiered ceiling (seconds) on a requested login duration carrying an admin-only service-token scope.
|
|
264
|
+
*
|
|
265
|
+
* Defaults to {@link DEFAULT_MAX_SERVICE_TOKEN_LOGIN_DURATION_SECONDS} (365 days).
|
|
266
|
+
*/
|
|
267
|
+
readonly maxRequestedLoginDurationServiceToken?: number;
|
|
208
268
|
/**
|
|
209
269
|
* JWKS service configuration (encryption secret, rotated key max age).
|
|
210
270
|
*/
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type InjectionToken } from '@nestjs/common';
|
|
2
|
+
import { type OidcAnalyticsEventType } from './oidc.analytics.handler';
|
|
3
|
+
/**
|
|
4
|
+
* Default prefix prepended to every resolved OIDC analytics event name forwarded
|
|
5
|
+
* to the downstream analytics pipeline (e.g. `'OIDC ' + 'Login'` → `'OIDC Login'`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_OIDC_ANALYTICS_EVENT_NAME_PREFIX = "OIDC ";
|
|
8
|
+
/**
|
|
9
|
+
* Default per-type downstream event name parts (the {@link DEFAULT_OIDC_ANALYTICS_EVENT_NAME_PREFIX}
|
|
10
|
+
* is prepended at send time).
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_OIDC_ANALYTICS_EVENT_NAMES: Record<OidcAnalyticsEventType, string>;
|
|
13
|
+
/**
|
|
14
|
+
* Configuration for {@link FirebaseServerOidcAnalyticsService}.
|
|
15
|
+
*
|
|
16
|
+
* Apps provide this via {@link FIREBASE_SERVER_OIDC_ANALYTICS_CONFIG} (typically through
|
|
17
|
+
* {@link appOidcAnalyticsModuleMetadata}) to tune how OIDC events are named, logged, and forwarded.
|
|
18
|
+
*/
|
|
19
|
+
export interface FirebaseServerOidcAnalyticsConfig {
|
|
20
|
+
/**
|
|
21
|
+
* The prefix prepended to every resolved event name.
|
|
22
|
+
* Defaults to {@link DEFAULT_OIDC_ANALYTICS_EVENT_NAME_PREFIX}.
|
|
23
|
+
*/
|
|
24
|
+
readonly eventNamePrefix?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Per-type overrides for the (unprefixed) event name part. Merged over
|
|
27
|
+
* {@link DEFAULT_OIDC_ANALYTICS_EVENT_NAMES}.
|
|
28
|
+
*/
|
|
29
|
+
readonly eventNames?: Partial<Record<OidcAnalyticsEventType, string>>;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to emit a per-event `Logger` line for each OIDC event.
|
|
32
|
+
*
|
|
33
|
+
* Defaults to `true` on non-production environments.
|
|
34
|
+
*/
|
|
35
|
+
readonly logEvents?: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* NestJS injection token for the optional {@link FirebaseServerOidcAnalyticsConfig} provider.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* @Module(appOidcAnalyticsModuleMetadata({ oidcAnalyticsConfig: { eventNamePrefix: 'OIDC ', logEvents: true } }))
|
|
43
|
+
* export class AppOidcAnalyticsModule {}
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare const FIREBASE_SERVER_OIDC_ANALYTICS_CONFIG: InjectionToken<FirebaseServerOidcAnalyticsConfig>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type InjectionToken, type Logger } from '@nestjs/common';
|
|
2
|
+
import { type FirebaseAuthUserId } from '@dereekb/firebase';
|
|
3
|
+
import { type Maybe } from '@dereekb/util';
|
|
4
|
+
/**
|
|
5
|
+
* The high-level OIDC/OAuth interaction-flow event types captured as analytics.
|
|
6
|
+
*
|
|
7
|
+
* Each value maps to a distinct downstream analytics event name (see
|
|
8
|
+
* {@link DEFAULT_OIDC_ANALYTICS_EVENT_NAMES}). Scoped to the OIDC interaction flow
|
|
9
|
+
* (`POST /interaction/:uid/login` and `/consent`) — the events that have no callModel
|
|
10
|
+
* representation. Client/grant lifecycle operations run through callModel functions and
|
|
11
|
+
* are captured by the callModel analytics layer (`OnCallModelAnalyticsService`) instead.
|
|
12
|
+
*/
|
|
13
|
+
export type OidcAnalyticsEventType = 'login' | 'consent';
|
|
14
|
+
/**
|
|
15
|
+
* Structured analytics event emitted once per high-level OIDC/OAuth moment.
|
|
16
|
+
*
|
|
17
|
+
* A single flat shape (mirroring the MCP `McpAnalyticsEvent`) keyed by {@link type};
|
|
18
|
+
* only the fields relevant to a given event type are populated. The {@link FirebaseServerOidcAnalyticsService}
|
|
19
|
+
* resolves a distinct downstream event name from {@link type} and forwards the populated
|
|
20
|
+
* fields as properties.
|
|
21
|
+
*
|
|
22
|
+
* Consumed by {@link OidcAnalyticsService} implementations registered under {@link OIDC_ANALYTICS_SERVICE}.
|
|
23
|
+
*/
|
|
24
|
+
export interface OidcAnalyticsEvent {
|
|
25
|
+
/**
|
|
26
|
+
* The high-level event identity.
|
|
27
|
+
*/
|
|
28
|
+
readonly type: OidcAnalyticsEventType;
|
|
29
|
+
/**
|
|
30
|
+
* Whether the underlying operation completed successfully. `false` for failed logins,
|
|
31
|
+
* the admin-only service-token rejection, and any other denied/errored outcome.
|
|
32
|
+
*/
|
|
33
|
+
readonly isSuccessful: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The Firebase Auth UID of the acting/owning user, when known. Always present for
|
|
36
|
+
* login/consent (the verified account id); best-effort for grant revocation (the grant
|
|
37
|
+
* owner) and typically absent for clientId-keyed client lifecycle events.
|
|
38
|
+
*/
|
|
39
|
+
readonly uid?: Maybe<FirebaseAuthUserId>;
|
|
40
|
+
/**
|
|
41
|
+
* The OAuth client id the user is authenticating against / consenting to, when known.
|
|
42
|
+
*/
|
|
43
|
+
readonly clientId?: Maybe<string>;
|
|
44
|
+
/**
|
|
45
|
+
* The OIDC scopes granted on a successful consent.
|
|
46
|
+
*/
|
|
47
|
+
readonly scopes?: Maybe<string[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Whether the event involved an admin-only service-token scope.
|
|
50
|
+
*/
|
|
51
|
+
readonly serviceToken?: Maybe<boolean>;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the acting user is an admin, when resolved (consent flow).
|
|
54
|
+
*/
|
|
55
|
+
readonly isAdmin?: Maybe<boolean>;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the user denied the consent prompt.
|
|
58
|
+
*/
|
|
59
|
+
readonly denied?: Maybe<boolean>;
|
|
60
|
+
/**
|
|
61
|
+
* A short machine-readable reason for a failed/denied outcome (e.g. `'service_token_non_admin'`,
|
|
62
|
+
* `'invalid_id_token'`).
|
|
63
|
+
*/
|
|
64
|
+
readonly reason?: Maybe<string>;
|
|
65
|
+
/**
|
|
66
|
+
* The thrown error, when the operation failed ({@link isSuccessful} is `false`).
|
|
67
|
+
*/
|
|
68
|
+
readonly error?: Maybe<unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* Wall-clock duration in milliseconds, when a handler boundary is available (controller handlers).
|
|
71
|
+
*/
|
|
72
|
+
readonly durationMs?: Maybe<number>;
|
|
73
|
+
/**
|
|
74
|
+
* Custom key-value properties. Reserved for future use.
|
|
75
|
+
*/
|
|
76
|
+
readonly properties?: Maybe<Record<string, any>>;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Abstract analytics service that apps implement to process OIDC analytics events.
|
|
80
|
+
*
|
|
81
|
+
* Analogous to the MCP `McpAnalyticsService` but scoped to the OIDC/OAuth transport.
|
|
82
|
+
* Apps extend this class and provide it via {@link OIDC_ANALYTICS_SERVICE}.
|
|
83
|
+
*/
|
|
84
|
+
export declare abstract class OidcAnalyticsService {
|
|
85
|
+
abstract handleOidcAnalyticsEvent(event: OidcAnalyticsEvent): void;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Injection token for the OIDC analytics service.
|
|
89
|
+
*
|
|
90
|
+
* Apps provide this (typically via {@link appOidcAnalyticsModuleMetadata} on a `@Global()`
|
|
91
|
+
* module) to enable analytics in the OIDC dispatch chain. When absent, the OIDC emitters
|
|
92
|
+
* fall back to {@link noopOidcAnalyticsService}.
|
|
93
|
+
*/
|
|
94
|
+
export declare const OIDC_ANALYTICS_SERVICE: InjectionToken<OidcAnalyticsService>;
|
|
95
|
+
/**
|
|
96
|
+
* Creates a no-op {@link OidcAnalyticsService} that silently discards all events.
|
|
97
|
+
*
|
|
98
|
+
* Used as the default fallback by the OIDC emitters when no analytics service is registered.
|
|
99
|
+
*
|
|
100
|
+
* @returns An {@link OidcAnalyticsService} that discards all analytics events.
|
|
101
|
+
* @__NO_SIDE_EFFECTS__
|
|
102
|
+
*/
|
|
103
|
+
export declare function noopOidcAnalyticsService(): OidcAnalyticsService;
|
|
104
|
+
/**
|
|
105
|
+
* Forwards an {@link OidcAnalyticsEvent} to the given service, fail-soft.
|
|
106
|
+
*
|
|
107
|
+
* A throwing analytics handler must never break the underlying OIDC operation, so the
|
|
108
|
+
* handler invocation is wrapped — on error a warning is logged and the error is swallowed.
|
|
109
|
+
* Mirrors the MCP `_emitMcpAnalytics` discipline, factored to a free function because the
|
|
110
|
+
* OIDC events fire from several emitters.
|
|
111
|
+
*
|
|
112
|
+
* @param service - The analytics service to forward to.
|
|
113
|
+
* @param event - The event to emit.
|
|
114
|
+
* @param logger - The emitter's logger, used to warn when the handler throws.
|
|
115
|
+
*/
|
|
116
|
+
export declare function emitOidcAnalyticsEvent(service: OidcAnalyticsService, event: OidcAnalyticsEvent, logger: Logger): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ModuleMetadata } from '@nestjs/common';
|
|
2
|
+
import { type Maybe } from '@dereekb/util';
|
|
3
|
+
import { type FirebaseServerOidcAnalyticsConfig } from './oidc.analytics.config';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for {@link appOidcAnalyticsModuleMetadata}.
|
|
6
|
+
*/
|
|
7
|
+
export interface AppOidcAnalyticsMetadataConfig extends Pick<ModuleMetadata, 'imports' | 'exports' | 'providers'> {
|
|
8
|
+
/**
|
|
9
|
+
* Optional {@link FirebaseServerOidcAnalyticsConfig} provided under
|
|
10
|
+
* {@link FIREBASE_SERVER_OIDC_ANALYTICS_CONFIG}. When omitted, the service uses its defaults.
|
|
11
|
+
*/
|
|
12
|
+
readonly oidcAnalyticsConfig?: Maybe<FirebaseServerOidcAnalyticsConfig>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generates NestJS module metadata that registers {@link FirebaseServerOidcAnalyticsService} as the
|
|
16
|
+
* OIDC analytics consumer, aliased to {@link OIDC_ANALYTICS_SERVICE}.
|
|
17
|
+
*
|
|
18
|
+
* Mirrors the convention used by `appMcpAnalyticsModuleMetadata`. Requires no dependency module —
|
|
19
|
+
* `FirebaseServerAnalyticsService` is resolved optionally and is expected to be supplied globally by
|
|
20
|
+
* the app's analytics module.
|
|
21
|
+
*
|
|
22
|
+
* Decorate a `@Global()` module with the result so the `OIDC_ANALYTICS_SERVICE` token is visible to
|
|
23
|
+
* the OIDC controllers and services provided by the app's OIDC module.
|
|
24
|
+
*
|
|
25
|
+
* @param config - Optional metadata + analytics config to merge in.
|
|
26
|
+
* @returns NestJS module metadata providing + exporting the OIDC analytics service and token.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* @Global()
|
|
31
|
+
* @Module(appOidcAnalyticsModuleMetadata())
|
|
32
|
+
* export class AppOidcAnalyticsModule {}
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function appOidcAnalyticsModuleMetadata(config?: AppOidcAnalyticsMetadataConfig): ModuleMetadata;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FirebaseServerAnalyticsService, FirebaseServerEnvService } from '@dereekb/firebase-server';
|
|
2
|
+
import { OidcAnalyticsService, type OidcAnalyticsEvent } from './oidc.analytics.handler';
|
|
3
|
+
import { type FirebaseServerOidcAnalyticsConfig } from './oidc.analytics.config';
|
|
4
|
+
/**
|
|
5
|
+
* Reusable {@link OidcAnalyticsService} that forwards each high-level OIDC event to the app's
|
|
6
|
+
* {@link FirebaseServerAnalyticsService}, so OIDC-transport analytics feed the same downstream
|
|
7
|
+
* pipeline (e.g. Segment) as model and MCP analytics.
|
|
8
|
+
*
|
|
9
|
+
* Each {@link OidcAnalyticsEvent.type} resolves to a distinct downstream event name
|
|
10
|
+
* (`eventNamePrefix` + per-type name, e.g. `'OIDC Login'`). Registered globally via
|
|
11
|
+
* {@link appOidcAnalyticsModuleMetadata}; tuned via {@link FirebaseServerOidcAnalyticsConfig}.
|
|
12
|
+
* When no analytics service is available the call is a no-op aside from the optional log line.
|
|
13
|
+
*/
|
|
14
|
+
export declare class FirebaseServerOidcAnalyticsService extends OidcAnalyticsService {
|
|
15
|
+
private readonly firebaseServerEnvService;
|
|
16
|
+
private readonly analyticsService?;
|
|
17
|
+
private readonly _logger;
|
|
18
|
+
private readonly _eventNamePrefix;
|
|
19
|
+
private readonly _eventNames;
|
|
20
|
+
private readonly _logEvents;
|
|
21
|
+
constructor(firebaseServerEnvService: FirebaseServerEnvService, analyticsService?: FirebaseServerAnalyticsService | undefined, config?: FirebaseServerOidcAnalyticsConfig);
|
|
22
|
+
handleOidcAnalyticsEvent(event: OidcAnalyticsEvent): void;
|
|
23
|
+
}
|
|
@@ -60,6 +60,21 @@ export declare abstract class OidcAccountServiceDelegate<S extends OidcScope = O
|
|
|
60
60
|
* @returns The claims to return for this user.
|
|
61
61
|
*/
|
|
62
62
|
abstract buildClaimsForUser(userContext: U, scopes: Set<S>): Promise<OidcAccountClaims> | OidcAccountClaims;
|
|
63
|
+
/**
|
|
64
|
+
* Resolves whether the given user is an admin, used to gate
|
|
65
|
+
* {@link OidcProviderConfig.adminOnlyScopes} at consent time.
|
|
66
|
+
*
|
|
67
|
+
* Implementations should resolve admin status from the user's raw auth claims/roles
|
|
68
|
+
* independently of the requested OIDC scopes (do not rely on a scope-gated claim builder),
|
|
69
|
+
* so the check is authoritative regardless of which scopes the client requested.
|
|
70
|
+
*
|
|
71
|
+
* Optional: when omitted, the consent flow treats the user as a non-admin, hard-rejecting any
|
|
72
|
+
* request for an admin-only scope.
|
|
73
|
+
*
|
|
74
|
+
* @param userContext - The Firebase Auth user context.
|
|
75
|
+
* @returns Whether the user is an admin.
|
|
76
|
+
*/
|
|
77
|
+
isAdminUser?(userContext: U): Promise<boolean>;
|
|
63
78
|
}
|
|
64
79
|
/**
|
|
65
80
|
* Per-user context for OIDC account operations.
|
|
@@ -6,6 +6,10 @@ import { type OidcService } from './oidc.service';
|
|
|
6
6
|
*
|
|
7
7
|
* Mirrors the oidc-provider `registration.js` flow to ensure all provider
|
|
8
8
|
* validation and lifecycle hooks run.
|
|
9
|
+
*
|
|
10
|
+
* Client create/update/rotate/delete are driven by callModel functions, so their
|
|
11
|
+
* analytics are captured by the callModel analytics layer (`OnCallModelAnalyticsService`)
|
|
12
|
+
* with the acting user — not by the OIDC analytics service.
|
|
9
13
|
*/
|
|
10
14
|
export declare class OidcClientService {
|
|
11
15
|
private readonly oidcService;
|
|
@@ -42,11 +42,11 @@ export declare class OidcInteractionService {
|
|
|
42
42
|
* @param result - The interaction results to apply.
|
|
43
43
|
* @param options - Optional settings for merging with the last submission.
|
|
44
44
|
* @param options.mergeWithLastSubmission - Whether to merge with the last submission (defaults to true)
|
|
45
|
-
* @returns The
|
|
45
|
+
* @returns The interaction that was completed. It includes the returnTo property for the next step of the interaction.
|
|
46
46
|
*/
|
|
47
47
|
finishInteractionByUid(uid: OidcInteractionUid, result: InteractionResults, options?: {
|
|
48
48
|
mergeWithLastSubmission?: boolean;
|
|
49
|
-
}): Promise<
|
|
49
|
+
}): Promise<Interaction>;
|
|
50
50
|
/**
|
|
51
51
|
* Finds an existing grant by ID, or creates a new one.
|
|
52
52
|
*
|
|
@@ -8,6 +8,21 @@ import { OidcProviderConfigService } from './oidc.config.service';
|
|
|
8
8
|
import { type OidcEntryClientId, type OidcEntryOAuthClientPayloadData } from '@dereekb/firebase';
|
|
9
9
|
import { type Maybe } from '@dereekb/util';
|
|
10
10
|
import { type OidcAuthData } from './oidc.auth';
|
|
11
|
+
/**
|
|
12
|
+
* Tier flags that select the server-max login-duration ceiling for a grant.
|
|
13
|
+
*
|
|
14
|
+
* @see OidcService.resolveLoginDurationForGrant
|
|
15
|
+
*/
|
|
16
|
+
export interface ResolveLoginDurationTier {
|
|
17
|
+
/**
|
|
18
|
+
* Whether the resolving user is an admin.
|
|
19
|
+
*/
|
|
20
|
+
readonly isAdmin: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Whether the grant being created carries an admin-only service-token scope.
|
|
23
|
+
*/
|
|
24
|
+
readonly hasServiceScope: boolean;
|
|
25
|
+
}
|
|
11
26
|
/**
|
|
12
27
|
* Core OIDC service that wraps the oidc-provider instance and exposes
|
|
13
28
|
* typed methods for interaction handling, provider initialization, and JWKS management.
|
|
@@ -36,13 +51,20 @@ export declare class OidcService {
|
|
|
36
51
|
*
|
|
37
52
|
* Mirrors the resolution used by the `Grant`/`Session` TTL functions in {@link buildProviderConfiguration}.
|
|
38
53
|
*
|
|
54
|
+
* The `serverMaxSeconds` ceiling is tiered by {@link resolveTieredServerMaxSeconds}: a non-admin
|
|
55
|
+
* caps at the non-admin tier, an admin at the admin tier, and an admin whose grant carries a
|
|
56
|
+
* service-token scope at the (highest) service-token tier. This tiered value replaces the flat
|
|
57
|
+
* {@link OidcModuleConfig.maxRequestedLoginDuration} ceiling so a service token can intentionally
|
|
58
|
+
* exceed it.
|
|
59
|
+
*
|
|
39
60
|
* @param requestedRawTtl - The raw `dbx_session_ttl` value from `interaction.params`, if any.
|
|
40
61
|
* @param clientPayload - The persisted client metadata, used to read the per-client `dbx_max_session_ttl` cap.
|
|
62
|
+
* @param tier - Whether the resolving user is an admin and whether the grant carries a service-token scope.
|
|
41
63
|
* @returns The resolved Grant TTL in seconds.
|
|
42
64
|
*/
|
|
43
65
|
resolveLoginDurationForGrant(requestedRawTtl: unknown, clientPayload: {
|
|
44
66
|
dbx_max_session_ttl?: number;
|
|
45
|
-
} | undefined): number;
|
|
67
|
+
} | undefined, tier: ResolveLoginDurationTier): number;
|
|
46
68
|
/**
|
|
47
69
|
* Verifies an opaque access token and returns the {@link OidcAuthData}.
|
|
48
70
|
*
|
|
@@ -9,6 +9,19 @@ export declare const DBX_FIREBASE_SERVER_OIDC_SESSION_TTL_PARAM = "dbx_session_t
|
|
|
9
9
|
* Custom oidc-provider client metadata field for a client's maximum requestable login duration (seconds).
|
|
10
10
|
*/
|
|
11
11
|
export declare const DBX_FIREBASE_SERVER_OIDC_MAX_SESSION_TTL_CLIENT_METADATA = "dbx_max_session_ttl";
|
|
12
|
+
/**
|
|
13
|
+
* Access-token `extra` claim carrying the grant's resolved expiry as unix seconds.
|
|
14
|
+
*
|
|
15
|
+
* Baked on at issuance (`extraTokenClaims`) and read back by `verifyAccessToken` and the
|
|
16
|
+
* `GET /oidc/session` route so clients can surface the session lifetime without decoding the token.
|
|
17
|
+
*/
|
|
18
|
+
export declare const DBX_FIREBASE_SERVER_OIDC_SESSION_EXPIRES_AT_CLAIM = "dbx_session_expires_at";
|
|
19
|
+
/**
|
|
20
|
+
* Access-token `extra` claim flagging whether the grant's refresh token rotation is disabled.
|
|
21
|
+
*
|
|
22
|
+
* `true` when the token's scope set intersects {@link OidcProviderConfig.nonRotatingScopes}.
|
|
23
|
+
*/
|
|
24
|
+
export declare const DBX_FIREBASE_SERVER_OIDC_ROTATION_DISABLED_CLAIM = "dbx_rotation_disabled";
|
|
12
25
|
/**
|
|
13
26
|
* Inputs to {@link resolveLoginDurationSeconds}.
|
|
14
27
|
*/
|
|
@@ -57,6 +70,107 @@ export interface ResolveLoginDurationInput {
|
|
|
57
70
|
* ```
|
|
58
71
|
*/
|
|
59
72
|
export declare function resolveLoginDurationSeconds(input: ResolveLoginDurationInput): number;
|
|
73
|
+
/**
|
|
74
|
+
* Inputs to {@link resolveTieredServerMaxSeconds}.
|
|
75
|
+
*/
|
|
76
|
+
export interface ResolveTieredServerMaxInput {
|
|
77
|
+
/**
|
|
78
|
+
* Whether the resolving user is an admin.
|
|
79
|
+
*/
|
|
80
|
+
readonly isAdmin: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Whether the grant carries an admin-only service-token scope.
|
|
83
|
+
*/
|
|
84
|
+
readonly hasServiceScope: boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Ceiling (seconds) for a non-admin user.
|
|
87
|
+
*/
|
|
88
|
+
readonly nonAdminMax: number;
|
|
89
|
+
/**
|
|
90
|
+
* Ceiling (seconds) for a normal (non-service-token) admin login.
|
|
91
|
+
*/
|
|
92
|
+
readonly adminMax: number;
|
|
93
|
+
/**
|
|
94
|
+
* Ceiling (seconds) for an admin login carrying a service-token scope.
|
|
95
|
+
*/
|
|
96
|
+
readonly serviceTokenMax: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Resolves the tiered server-max ceiling (seconds) used as `serverMaxSeconds` for a login duration.
|
|
100
|
+
*
|
|
101
|
+
* Tiers, highest to lowest priority:
|
|
102
|
+
* 1. Admin + service-token scope → `serviceTokenMax`.
|
|
103
|
+
* 2. Admin (normal) → `adminMax`.
|
|
104
|
+
* 3. Non-admin → `nonAdminMax`.
|
|
105
|
+
*
|
|
106
|
+
* A non-admin should never reach the service-token branch — the consent flow hard-rejects a
|
|
107
|
+
* non-admin requesting a service-token scope before this resolves — but the tiering is defensive:
|
|
108
|
+
* a non-admin always resolves to `nonAdminMax` regardless of `hasServiceScope`.
|
|
109
|
+
*
|
|
110
|
+
* @param input - The tier inputs (admin flag, service-scope flag, and the three tier ceilings).
|
|
111
|
+
* @returns The resolved ceiling in seconds.
|
|
112
|
+
*/
|
|
113
|
+
export declare function resolveTieredServerMaxSeconds(input: ResolveTieredServerMaxInput): number;
|
|
114
|
+
/**
|
|
115
|
+
* Absolute cap (seconds) on how long a refresh token may be rotated, after which its TTL is final.
|
|
116
|
+
*
|
|
117
|
+
* Mirrors node-oidc-provider's default `rotateRefreshToken` cap of `365.25 * 24 * 60 * 60`.
|
|
118
|
+
*/
|
|
119
|
+
export declare const REFRESH_TOKEN_ROTATION_MAX_LIFETIME_SECONDS: number;
|
|
120
|
+
/**
|
|
121
|
+
* Percentage of a refresh token's lifetime that must elapse before the library-default branch rotates it.
|
|
122
|
+
*
|
|
123
|
+
* Mirrors node-oidc-provider's default `rotateRefreshToken` threshold of `70`.
|
|
124
|
+
*/
|
|
125
|
+
export declare const REFRESH_TOKEN_ROTATION_TTL_PERCENTAGE_THRESHOLD = 70;
|
|
126
|
+
/**
|
|
127
|
+
* Inputs to {@link shouldRotateRefreshToken}.
|
|
128
|
+
*/
|
|
129
|
+
export interface ShouldRotateRefreshTokenInput {
|
|
130
|
+
/**
|
|
131
|
+
* Space-delimited scope string of the refresh token being exchanged, if any.
|
|
132
|
+
*/
|
|
133
|
+
readonly scope: string | undefined;
|
|
134
|
+
/**
|
|
135
|
+
* Scopes whose grants must never rotate (from {@link OidcProviderConfig.nonRotatingScopes}).
|
|
136
|
+
*/
|
|
137
|
+
readonly nonRotatingScopes: readonly string[];
|
|
138
|
+
/**
|
|
139
|
+
* The refresh token's total lifetime in seconds (`refreshToken.totalLifetime()`).
|
|
140
|
+
*/
|
|
141
|
+
readonly totalLifetimeSeconds: number;
|
|
142
|
+
/**
|
|
143
|
+
* The client's auth method (`client.clientAuthMethod`); `'none'` indicates a public client.
|
|
144
|
+
*/
|
|
145
|
+
readonly clientAuthMethod: string | undefined;
|
|
146
|
+
/**
|
|
147
|
+
* Whether the refresh token is sender-constrained (`refreshToken.isSenderConstrained()`).
|
|
148
|
+
*/
|
|
149
|
+
readonly isSenderConstrained: boolean;
|
|
150
|
+
/**
|
|
151
|
+
* Percentage of the refresh token's lifetime already elapsed (`refreshToken.ttlPercentagePassed()`).
|
|
152
|
+
*/
|
|
153
|
+
readonly ttlPercentagePassed: number;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Decides whether a refresh token should rotate on exchange.
|
|
157
|
+
*
|
|
158
|
+
* Returns `false` (no rotation) whenever the token's scope set intersects `nonRotatingScopes` — the
|
|
159
|
+
* dbx-components extension point that makes service tokens stable for server env consumption.
|
|
160
|
+
*
|
|
161
|
+
* For every other token this **replicates node-oidc-provider's default `rotateRefreshToken`**
|
|
162
|
+
* (because setting the option disables the library's built-in default, we must reproduce it):
|
|
163
|
+
* 1. `totalLifetime >= 365.25d` → `false` (rotation cap reached; TTL is final).
|
|
164
|
+
* 2. public client (`clientAuthMethod === 'none'`) and not sender-constrained → `true`.
|
|
165
|
+
* 3. otherwise → `ttlPercentagePassed >= 70`.
|
|
166
|
+
*
|
|
167
|
+
* Keep this in sync with `node_modules/oidc-provider/lib/helpers/defaults.js` (`rotateRefreshToken`)
|
|
168
|
+
* across oidc-provider upgrades.
|
|
169
|
+
*
|
|
170
|
+
* @param input - The rotation inputs.
|
|
171
|
+
* @returns Whether the refresh token should rotate.
|
|
172
|
+
*/
|
|
173
|
+
export declare function shouldRotateRefreshToken(input: ShouldRotateRefreshTokenInput): boolean;
|
|
60
174
|
/**
|
|
61
175
|
* Parses a raw `dbx_session_ttl` value (string or number, from URL query / form / persisted interaction params)
|
|
62
176
|
* into a positive integer number of seconds. Returns `undefined` when missing or invalid so the caller falls
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase-server",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.13.0",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"exports": {
|
|
6
6
|
"./test": {
|
|
@@ -57,15 +57,15 @@
|
|
|
57
57
|
"types": "./src/index.d.ts",
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"@cantoo/pdf-lib": "^2.6.5",
|
|
60
|
-
"@dereekb/analytics": "13.
|
|
61
|
-
"@dereekb/date": "13.
|
|
62
|
-
"@dereekb/dbx-core": "13.
|
|
63
|
-
"@dereekb/firebase": "13.
|
|
64
|
-
"@dereekb/model": "13.
|
|
65
|
-
"@dereekb/nestjs": "13.
|
|
66
|
-
"@dereekb/rxjs": "13.
|
|
67
|
-
"@dereekb/util": "13.
|
|
68
|
-
"@dereekb/zoho": "13.
|
|
60
|
+
"@dereekb/analytics": "13.13.0",
|
|
61
|
+
"@dereekb/date": "13.13.0",
|
|
62
|
+
"@dereekb/dbx-core": "13.13.0",
|
|
63
|
+
"@dereekb/firebase": "13.13.0",
|
|
64
|
+
"@dereekb/model": "13.13.0",
|
|
65
|
+
"@dereekb/nestjs": "13.13.0",
|
|
66
|
+
"@dereekb/rxjs": "13.13.0",
|
|
67
|
+
"@dereekb/util": "13.13.0",
|
|
68
|
+
"@dereekb/zoho": "13.13.0",
|
|
69
69
|
"@google-cloud/firestore": "^7.11.6",
|
|
70
70
|
"@google-cloud/storage": "^7.19.0",
|
|
71
71
|
"@modelcontextprotocol/sdk": "1.29.0",
|