@dereekb/firebase-server 13.30.0 → 13.32.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 +2116 -242
- package/model/index.esm.js +2114 -247
- package/model/package.json +9 -9
- package/model/src/lib/mailgun/index.d.ts +1 -0
- package/model/src/lib/mailgun/notification.healthcheck.mailgun.d.ts +115 -0
- package/model/src/lib/notification/index.d.ts +2 -0
- package/model/src/lib/notification/notification.action.server.d.ts +42 -3
- package/model/src/lib/notification/notification.error.d.ts +31 -0
- package/model/src/lib/notification/notification.healthcheck.d.ts +39 -0
- package/model/src/lib/notification/notification.healthcheck.service.d.ts +118 -0
- package/model/src/lib/notification/notification.send.service.d.ts +22 -0
- package/oidc/index.cjs.js +28 -12
- package/oidc/index.esm.js +29 -13
- package/oidc/package.json +10 -10
- package/oidc/src/lib/oidc.config.d.ts +11 -0
- package/oidc/src/lib/profile.d.ts +7 -0
- package/package.json +10 -10
- package/test/index.cjs.js +94 -58
- package/test/index.esm.js +95 -59
- package/test/package.json +11 -11
- package/test/src/lib/oidc/oidc.test.fixture.d.ts +9 -1
- package/test/src/lib/oidc/oidc.test.flow.d.ts +13 -3
- package/twilio/package.json +8 -8
- package/zoho/package.json +9 -9
package/model/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dereekb/firebase-server/model",
|
|
3
|
-
"version": "13.
|
|
3
|
+
"version": "13.32.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.
|
|
5
|
+
"@dereekb/analytics": "13.32.0",
|
|
6
|
+
"@dereekb/date": "13.32.0",
|
|
7
|
+
"@dereekb/firebase": "13.32.0",
|
|
8
|
+
"@dereekb/firebase-server": "13.32.0",
|
|
9
|
+
"@dereekb/model": "13.32.0",
|
|
10
|
+
"@dereekb/nestjs": "13.32.0",
|
|
11
|
+
"@dereekb/rxjs": "13.32.0",
|
|
12
|
+
"@dereekb/util": "13.32.0",
|
|
13
13
|
"@nestjs/common": "^11.1.19",
|
|
14
14
|
"@nestjs/config": "^4.0.4",
|
|
15
15
|
"archiver": "^7.0.1",
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module notification.healthcheck.mailgun
|
|
3
|
+
*
|
|
4
|
+
* Mailgun-backed diagnostics for the email delivery method.
|
|
5
|
+
*
|
|
6
|
+
* This is where "the user says they aren't getting emails" turns into an actual answer. In practice the
|
|
7
|
+
* cause is almost always one of:
|
|
8
|
+
*
|
|
9
|
+
* - the address is on the domain's **suppression list** (a past bounce or spam complaint), after which
|
|
10
|
+
* Mailgun silently drops every subsequent message to it
|
|
11
|
+
* - recent messages to the address were **rejected or failed**, with a reason Mailgun recorded
|
|
12
|
+
* - nothing was ever sent to the address at all, which points upstream at configuration
|
|
13
|
+
* - the sending **domain** itself is not active
|
|
14
|
+
*
|
|
15
|
+
* All four are readable from Mailgun without sending anything. When that is not conclusive, an opt-in
|
|
16
|
+
* probe sends a real message and resolves its outcome from the Events API. Resolution is asynchronous —
|
|
17
|
+
* Mailgun records the outcome seconds after the send — so the probe is recorded as pending and settled
|
|
18
|
+
* by a later verification, which the client polls for automatically. Nothing here ever asks the user to
|
|
19
|
+
* come back and check for themselves.
|
|
20
|
+
*/
|
|
21
|
+
import { type Maybe, type Minutes, type PromiseOrValue } from '@dereekb/util';
|
|
22
|
+
import { type FirebaseAuthUserId } from '@dereekb/firebase';
|
|
23
|
+
import { type MailgunRecipient, type MailgunService, type MailgunTemplateEmailRequest } from '@dereekb/nestjs/mailgun';
|
|
24
|
+
import { type NotificationEmailSendServiceHealthCheckService } from '../notification/notification.healthcheck.service';
|
|
25
|
+
/**
|
|
26
|
+
* How long a dispatched probe may stay unresolved before it is reported as failed.
|
|
27
|
+
*
|
|
28
|
+
* Mailgun normally records a `delivered` or `failed` event within seconds; a probe with no event after
|
|
29
|
+
* this long is not going to arrive.
|
|
30
|
+
*/
|
|
31
|
+
export declare const DEFAULT_MAILGUN_HEALTH_CHECK_PROBE_TIMEOUT_MINUTES = 15;
|
|
32
|
+
/**
|
|
33
|
+
* Input for building the test email dispatched as a delivery probe.
|
|
34
|
+
*/
|
|
35
|
+
export interface MailgunNotificationHealthCheckProbeBuilderInput {
|
|
36
|
+
/**
|
|
37
|
+
* The Mailgun service, for reading the configured sender and client url.
|
|
38
|
+
*/
|
|
39
|
+
readonly mailgunService: MailgunService;
|
|
40
|
+
/**
|
|
41
|
+
* The recipient to send the probe to.
|
|
42
|
+
*/
|
|
43
|
+
readonly recipient: MailgunRecipient;
|
|
44
|
+
/**
|
|
45
|
+
* The user the health check is running for.
|
|
46
|
+
*/
|
|
47
|
+
readonly uid: FirebaseAuthUserId;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Builds the test email dispatched as a delivery probe.
|
|
51
|
+
*
|
|
52
|
+
* The message should be recognizable to whoever receives it — it lands in a real inbox, unannounced,
|
|
53
|
+
* because someone asked the system to check whether their email works.
|
|
54
|
+
*/
|
|
55
|
+
export type MailgunNotificationHealthCheckProbeBuilder = (input: MailgunNotificationHealthCheckProbeBuilderInput) => PromiseOrValue<MailgunTemplateEmailRequest>;
|
|
56
|
+
/**
|
|
57
|
+
* Configuration for {@link mailgunNotificationEmailSendServiceHealthCheckService}.
|
|
58
|
+
*/
|
|
59
|
+
export interface MailgunNotificationEmailSendServiceHealthCheckServiceConfig {
|
|
60
|
+
/**
|
|
61
|
+
* The Mailgun service to diagnose against.
|
|
62
|
+
*/
|
|
63
|
+
readonly mailgunService: MailgunService;
|
|
64
|
+
/**
|
|
65
|
+
* Builds the probe message. Probing is unavailable when this is not provided.
|
|
66
|
+
*/
|
|
67
|
+
readonly probeBuilder?: Maybe<MailgunNotificationHealthCheckProbeBuilder>;
|
|
68
|
+
/**
|
|
69
|
+
* The maximum number of recent events to inspect per address.
|
|
70
|
+
*/
|
|
71
|
+
readonly recentEventsLimit?: Maybe<number>;
|
|
72
|
+
/**
|
|
73
|
+
* How far back to look for recent events, in days.
|
|
74
|
+
*/
|
|
75
|
+
readonly recentEventsWindowDays?: Maybe<number>;
|
|
76
|
+
/**
|
|
77
|
+
* Whether to run Mailgun address validation as part of the check.
|
|
78
|
+
*
|
|
79
|
+
* Off by default, because validation consumes a separate Mailgun quota.
|
|
80
|
+
*/
|
|
81
|
+
readonly validateAddress?: Maybe<boolean>;
|
|
82
|
+
/**
|
|
83
|
+
* How long a dispatched probe may stay unresolved before it is reported as failed.
|
|
84
|
+
*
|
|
85
|
+
* Defaults to {@link DEFAULT_MAILGUN_HEALTH_CHECK_PROBE_TIMEOUT_MINUTES}.
|
|
86
|
+
*/
|
|
87
|
+
readonly probeTimeoutMinutes?: Maybe<Minutes>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Creates a {@link NotificationEmailSendServiceHealthCheckService} backed by the Mailgun API.
|
|
91
|
+
*
|
|
92
|
+
* Attach the result to a {@link NotificationEmailSendService} as its `healthCheckService` and the
|
|
93
|
+
* notification health check will pick it up automatically.
|
|
94
|
+
*
|
|
95
|
+
* @param config - The Mailgun service plus optional probe builder and inspection limits.
|
|
96
|
+
* @returns A health check service for the email delivery method.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```ts
|
|
100
|
+
* const emailSendService = mailgunNotificationEmailSendService({ mailgunService, messageBuilders });
|
|
101
|
+
*
|
|
102
|
+
* const sendServiceWithHealthCheck: NotificationEmailSendService = {
|
|
103
|
+
* ...emailSendService,
|
|
104
|
+
* healthCheckService: mailgunNotificationEmailSendServiceHealthCheckService({
|
|
105
|
+
* mailgunService,
|
|
106
|
+
* probeBuilder: ({ recipient }) => ({
|
|
107
|
+
* to: recipient,
|
|
108
|
+
* template: 'notificationtemplate',
|
|
109
|
+
* subject: 'Email delivery test'
|
|
110
|
+
* })
|
|
111
|
+
* })
|
|
112
|
+
* };
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function mailgunNotificationEmailSendServiceHealthCheckService(config: MailgunNotificationEmailSendServiceHealthCheckServiceConfig): NotificationEmailSendServiceHealthCheckService;
|
|
@@ -3,6 +3,8 @@ export * from './notification.action.server.init';
|
|
|
3
3
|
export * from './notification.config.service';
|
|
4
4
|
export * from './notification.config';
|
|
5
5
|
export * from './notification.create.run';
|
|
6
|
+
export * from './notification.healthcheck';
|
|
7
|
+
export * from './notification.healthcheck.service';
|
|
6
8
|
export * from './notification.module';
|
|
7
9
|
export * from './notification.send.service.notificationsummary';
|
|
8
10
|
export * from './notification.send.service.text';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type AsyncNotificationSummaryCreateAction, type AsyncNotificationUserCreateAction, type AsyncNotificationUserUpdateAction, type CleanupSentNotificationsParams, type CleanupOldNotificationLoggedEventDaysParams, type CleanupOldNotificationLoggedEventDaysResult, type CreateNotificationBoxParams, type CreateNotificationSummaryParams, type CreateNotificationUserParams, type NotificationSummaryDocument, type NotificationUser, type NotificationUserDocument, type SendNotificationParams, type SendQueuedNotificationsParams, type UpdateNotificationBoxParams, type UpdateNotificationBoxRecipientParams, type UpdateNotificationUserParams, type AsyncNotificationBoxCreateAction, type AsyncNotificationBoxUpdateAction, type CleanupSentNotificationsResult, type FirestoreContextReference, type NotificationBox, type NotificationBoxDocument, type NotificationDocument, type NotificationFirestoreCollections, type SendNotificationResult, type SendQueuedNotificationsResult, type Transaction, type ResyncAllNotificationUserParams, type ResyncAllNotificationUsersResult, type ResyncNotificationUserResult, type ResyncNotificationUserParams, type AppNotificationTemplateTypeInfoRecordServiceRef, type AsyncNotificationSummaryUpdateAction, type UpdateNotificationSummaryParams, type NotificationBoxDocumentReferencePair } from '@dereekb/firebase';
|
|
1
|
+
import { type AsyncNotificationSummaryCreateAction, type AsyncNotificationUserCreateAction, type AsyncNotificationUserUpdateAction, type CleanupSentNotificationsParams, type CleanupOldNotificationLoggedEventDaysParams, type CleanupOldNotificationLoggedEventDaysResult, type CreateNotificationBoxParams, type CreateNotificationSummaryParams, type CreateNotificationUserParams, type NotificationSummaryDocument, type NotificationUser, type NotificationUserDocument, type SendNotificationParams, type SendQueuedNotificationsParams, type UpdateNotificationBoxParams, type UpdateNotificationBoxRecipientParams, type NotificationUserHealthCheckParams, type NotificationUserHealthCheckResult, type UpdateNotificationUserParams, type AsyncNotificationBoxCreateAction, type AsyncNotificationBoxUpdateAction, type CleanupSentNotificationsResult, type FirestoreContextReference, type NotificationBox, type NotificationBoxDocument, type NotificationDocument, type NotificationFirestoreCollections, type SendNotificationResult, type SendQueuedNotificationsResult, type Transaction, type ResyncAllNotificationUserParams, type ResyncAllNotificationUsersResult, type ResyncNotificationUserResult, type ResyncNotificationUserParams, type AppNotificationTemplateTypeInfoRecordServiceRef, type AsyncNotificationSummaryUpdateAction, type UpdateNotificationSummaryParams, type NotificationBoxDocumentReferencePair } from '@dereekb/firebase';
|
|
2
2
|
import { type FirebaseServerActionsContext, type FirebaseServerAuthServiceRef } from '@dereekb/firebase-server';
|
|
3
3
|
import { type TransformAndValidateFunctionResult } from '@dereekb/model';
|
|
4
|
-
import { type Maybe } from '@dereekb/util';
|
|
4
|
+
import { type Maybe, type Minutes, type Seconds } from '@dereekb/util';
|
|
5
5
|
import { type InjectionToken } from '@nestjs/common';
|
|
6
6
|
import { type NotificationTemplateServiceRef } from './notification.config.service';
|
|
7
7
|
import { type NotificationSendServiceRef } from './notification.send.service';
|
|
@@ -24,11 +24,49 @@ export declare const NOTIFICATION_SERVER_ACTION_CONTEXT_TOKEN: InjectionToken;
|
|
|
24
24
|
*/
|
|
25
25
|
export interface BaseNotificationServerActionsContext extends FirebaseServerActionsContext, NotificationFirestoreCollections, FirebaseServerAuthServiceRef, FirestoreContextReference {
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* App-level tuning for the NotificationUser delivery health check.
|
|
29
|
+
*
|
|
30
|
+
* Both windows are enforced by the server and counted down by the client, so an app that overrides one
|
|
31
|
+
* must give the SAME value to its client (see the dbx-firebase health check config). Declaring the value
|
|
32
|
+
* once in a package both sides import is the way to keep them from drifting — otherwise the UI will
|
|
33
|
+
* offer a check or a test message that the server then rejects.
|
|
34
|
+
*/
|
|
35
|
+
export interface NotificationUserHealthCheckServerConfig {
|
|
36
|
+
/**
|
|
37
|
+
* How long a user must wait between test messages on a single delivery method.
|
|
38
|
+
*
|
|
39
|
+
* Defaults to {@link DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_PROBE_THROTTLE_MINUTES}.
|
|
40
|
+
*/
|
|
41
|
+
readonly probeThrottleMinutes?: Maybe<Minutes>;
|
|
42
|
+
/**
|
|
43
|
+
* How long a user must wait between health check runs.
|
|
44
|
+
*
|
|
45
|
+
* Defaults to {@link DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_THROTTLE_MINUTES}.
|
|
46
|
+
*/
|
|
47
|
+
readonly runThrottleMinutes?: Maybe<Minutes>;
|
|
48
|
+
/**
|
|
49
|
+
* How long a caller must wait between verifications of a check's pending probes.
|
|
50
|
+
*
|
|
51
|
+
* Defaults to {@link DEFAULT_NOTIFICATION_USER_HEALTH_CHECK_VERIFY_THROTTLE_SECONDS}. In seconds
|
|
52
|
+
* because a verification exists to be polled while the user watches for their test message to land.
|
|
53
|
+
*/
|
|
54
|
+
readonly verifyThrottleSeconds?: Maybe<Seconds>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Reference to a {@link NotificationUserHealthCheckServerConfig}.
|
|
58
|
+
*/
|
|
59
|
+
export interface NotificationUserHealthCheckServerConfigRef {
|
|
60
|
+
/**
|
|
61
|
+
* Tuning for the delivery health check. Every field falls back to the library default when absent.
|
|
62
|
+
*/
|
|
63
|
+
readonly notificationUserHealthCheckConfig?: Maybe<NotificationUserHealthCheckServerConfig>;
|
|
64
|
+
}
|
|
27
65
|
/**
|
|
28
66
|
* Full context for notification server actions, extending the base with template resolution,
|
|
29
67
|
* send channel orchestration, task dispatch, and expedite services.
|
|
30
68
|
*/
|
|
31
|
-
export interface NotificationServerActionsContext extends BaseNotificationServerActionsContext, AppNotificationTemplateTypeInfoRecordServiceRef, NotificationTemplateServiceRef, NotificationSendServiceRef, NotificationTaskServiceRef {
|
|
69
|
+
export interface NotificationServerActionsContext extends BaseNotificationServerActionsContext, AppNotificationTemplateTypeInfoRecordServiceRef, NotificationTemplateServiceRef, NotificationSendServiceRef, NotificationTaskServiceRef, NotificationUserHealthCheckServerConfigRef {
|
|
32
70
|
}
|
|
33
71
|
/**
|
|
34
72
|
* Abstract service class defining all server-side notification CRUD and delivery actions.
|
|
@@ -50,6 +88,7 @@ export declare abstract class NotificationServerActions {
|
|
|
50
88
|
abstract updateNotificationUser(params: UpdateNotificationUserParams): AsyncNotificationUserUpdateAction<UpdateNotificationUserParams>;
|
|
51
89
|
abstract resyncNotificationUser(params: ResyncNotificationUserParams): Promise<TransformAndValidateFunctionResult<ResyncNotificationUserParams, (notificationUserDocument: NotificationUserDocument) => Promise<ResyncNotificationUserResult>>>;
|
|
52
90
|
abstract resyncAllNotificationUsers(params?: ResyncAllNotificationUserParams): Promise<ResyncAllNotificationUsersResult>;
|
|
91
|
+
abstract notificationUserHealthCheck(params: NotificationUserHealthCheckParams): Promise<TransformAndValidateFunctionResult<NotificationUserHealthCheckParams, (notificationUserDocument: NotificationUserDocument) => Promise<NotificationUserHealthCheckResult>>>;
|
|
53
92
|
abstract createNotificationSummary(params: CreateNotificationSummaryParams): AsyncNotificationSummaryCreateAction<CreateNotificationSummaryParams>;
|
|
54
93
|
abstract updateNotificationSummary(params: UpdateNotificationSummaryParams): AsyncNotificationSummaryUpdateAction<UpdateNotificationSummaryParams>;
|
|
55
94
|
abstract createNotificationBox(params: CreateNotificationBoxParams): AsyncNotificationBoxCreateAction<CreateNotificationBoxParams>;
|
|
@@ -59,6 +59,37 @@ export declare function notificationBoxExistsForModelError(): import("firebase-f
|
|
|
59
59
|
* @returns A precondition conflict error with the recipient-not-found error code.
|
|
60
60
|
*/
|
|
61
61
|
export declare function notificationBoxRecipientDoesNotExistsError(): import("firebase-functions/https").HttpsError;
|
|
62
|
+
/**
|
|
63
|
+
* Creates an error indicating that a health check was run again before its throttle window passed.
|
|
64
|
+
*
|
|
65
|
+
* Thrown by the {@link NotificationUser} health check when the stored check is too recent.
|
|
66
|
+
*
|
|
67
|
+
* @param nextRunAt - The time the next health check may be run.
|
|
68
|
+
* @returns A precondition conflict error with the health-check-throttled error code.
|
|
69
|
+
*/
|
|
70
|
+
export declare function notificationUserHealthCheckThrottledError(nextRunAt: Date): import("firebase-functions/https").HttpsError;
|
|
71
|
+
/**
|
|
72
|
+
* Creates an error indicating that a test message was requested again before its throttle window passed.
|
|
73
|
+
*
|
|
74
|
+
* Thrown by the {@link NotificationUser} health check when a probe was dispatched too recently. Separate
|
|
75
|
+
* from {@link notificationUserHealthCheckThrottledError}: running the check does not consume the test
|
|
76
|
+
* message allowance.
|
|
77
|
+
*
|
|
78
|
+
* @param nextProbeAt - The time the next test message may be dispatched.
|
|
79
|
+
* @returns A precondition conflict error with the probe-throttled error code.
|
|
80
|
+
*/
|
|
81
|
+
export declare function notificationUserHealthCheckProbeThrottledError(nextProbeAt: Date): import("firebase-functions/https").HttpsError;
|
|
82
|
+
/**
|
|
83
|
+
* Creates an error indicating that pending probes were verified again before their throttle window passed.
|
|
84
|
+
*
|
|
85
|
+
* Thrown by the {@link NotificationUser} health check on a `verifyPendingProbesOnly` run that arrives too
|
|
86
|
+
* soon after the last one. Separate from {@link notificationUserHealthCheckThrottledError}: verifying an
|
|
87
|
+
* in-flight test message is a poll, so it neither answers to nor consumes the user's run allowance.
|
|
88
|
+
*
|
|
89
|
+
* @param nextVerifyAt - The time the next verification may be run.
|
|
90
|
+
* @returns A precondition conflict error with the verify-throttled error code.
|
|
91
|
+
*/
|
|
92
|
+
export declare function notificationUserHealthCheckVerifyThrottledError(nextVerifyAt: Date): import("firebase-functions/https").HttpsError;
|
|
62
93
|
/**
|
|
63
94
|
* Creates an error indicating that the given UID does not correspond to an existing Firebase Auth user.
|
|
64
95
|
*
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module notification.healthcheck
|
|
3
|
+
*
|
|
4
|
+
* The `healthCheck` server action: a self-serve diagnosis of why a given user is or is not receiving
|
|
5
|
+
* notifications on each delivery method.
|
|
6
|
+
*
|
|
7
|
+
* The check runs in three passes:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Configuration** — mirrors the gates {@link expandNotificationRecipients} applies at send time,
|
|
10
|
+
* so a finding here corresponds to a real reason a message would be dropped.
|
|
11
|
+
* 2. **Subscriptions** — inspects the user's {@link NotificationBox} documents to confirm they exist,
|
|
12
|
+
* are initialized, and carry a recipient entry matching the user's own configuration.
|
|
13
|
+
* 3. **Provider** — delegates to each delivery method's optional
|
|
14
|
+
* {@link NotificationSendServiceHealthCheckService} for provider-specific diagnostics and for
|
|
15
|
+
* dispatching/resolving a delivery probe.
|
|
16
|
+
*
|
|
17
|
+
* The result is persisted to the user's `hc` field, because delivery confirmation is asynchronous: a
|
|
18
|
+
* probe dispatched by one run is resolved by a later one. That later run is normally a
|
|
19
|
+
* `verifyPendingProbesOnly` VERIFICATION rather than anything the user does — a cheap poll that consults
|
|
20
|
+
* a provider only where a probe is actually in flight, answers to its own short window, and advances
|
|
21
|
+
* `vat` instead of `at` so that watching a test message never consumes the user's run allowance. The
|
|
22
|
+
* client reads the stored check live, so a resolved probe simply appears.
|
|
23
|
+
*/
|
|
24
|
+
import { type NotificationUserDocument, type NotificationUserHealthCheckParams, type NotificationUserHealthCheckResult } from '@dereekb/firebase';
|
|
25
|
+
import { type NotificationServerActionsContext } from './notification.action.server';
|
|
26
|
+
/**
|
|
27
|
+
* The maximum number of the user's notification boxes to inspect in a single health check.
|
|
28
|
+
*
|
|
29
|
+
* A user can be subscribed to an unbounded number of boxes; a health check is interactive, so it
|
|
30
|
+
* samples rather than walking all of them.
|
|
31
|
+
*/
|
|
32
|
+
export declare const DEFAULT_MAX_NOTIFICATION_BOXES_TO_INSPECT_PER_HEALTH_CHECK = 10;
|
|
33
|
+
/**
|
|
34
|
+
* Factory for the `healthCheck` action on a {@link NotificationUser}.
|
|
35
|
+
*
|
|
36
|
+
* @param context - The notification server actions context.
|
|
37
|
+
* @returns A transform-and-validate function that runs a delivery health check for a notification user.
|
|
38
|
+
*/
|
|
39
|
+
export declare function notificationUserHealthCheckFactory(context: NotificationServerActionsContext): import("@dereekb/model").TransformAndValidateFunctionResultFunction<NotificationUserHealthCheckParams, (notificationUserDocument: NotificationUserDocument) => Promise<NotificationUserHealthCheckResult>, object, unknown>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module notification.healthcheck.service
|
|
3
|
+
*
|
|
4
|
+
* The extension point that lets each delivery method contribute its own diagnostics to a
|
|
5
|
+
* notification delivery health check.
|
|
6
|
+
*
|
|
7
|
+
* The shared health check action can only inspect what it knows about: the user's configuration,
|
|
8
|
+
* their notification boxes, and the send queue. Everything beyond that is provider-specific — whether
|
|
9
|
+
* an address is on an email provider's suppression list, whether a carrier rejected an SMS, whether a
|
|
10
|
+
* test message actually arrived. A send service opts into contributing that knowledge by exposing a
|
|
11
|
+
* {@link NotificationSendServiceHealthCheckService}.
|
|
12
|
+
*/
|
|
13
|
+
import { type EmailAddress, type E164PhoneNumber, type Maybe } from '@dereekb/util';
|
|
14
|
+
import { type FirebaseAuthUserId, type NotificationDeliveryMethod, type NotificationHealthCheckIssue, type NotificationHealthCheckProbe, type NotificationSummaryId, type NotificationTemplateType } from '@dereekb/firebase';
|
|
15
|
+
/**
|
|
16
|
+
* Input to a {@link NotificationSendServiceHealthCheckService}.
|
|
17
|
+
*
|
|
18
|
+
* @template T - The delivery target type for the method (email address, phone number, summary id).
|
|
19
|
+
*/
|
|
20
|
+
export interface NotificationSendServiceHealthCheckRequest<T = unknown> {
|
|
21
|
+
/**
|
|
22
|
+
* The delivery method being diagnosed.
|
|
23
|
+
*/
|
|
24
|
+
readonly method: NotificationDeliveryMethod;
|
|
25
|
+
/**
|
|
26
|
+
* The resolved delivery target to diagnose.
|
|
27
|
+
*/
|
|
28
|
+
readonly target: T;
|
|
29
|
+
/**
|
|
30
|
+
* The user the check is being run for.
|
|
31
|
+
*/
|
|
32
|
+
readonly uid: FirebaseAuthUserId;
|
|
33
|
+
/**
|
|
34
|
+
* Whether this run is permitted to dispatch a real probe message to the target.
|
|
35
|
+
*
|
|
36
|
+
* Always respect this — a probe sends real mail or SMS to a real person, and the caller has decided
|
|
37
|
+
* whether that is acceptable right now.
|
|
38
|
+
*/
|
|
39
|
+
readonly sendProbe: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* A probe dispatched by an earlier run that has not reached a final status yet.
|
|
42
|
+
*
|
|
43
|
+
* When present, the service should try to resolve it and return it (updated) as the response's probe.
|
|
44
|
+
*/
|
|
45
|
+
readonly pendingProbe?: Maybe<NotificationHealthCheckProbe>;
|
|
46
|
+
/**
|
|
47
|
+
* The notification template type the check is being evaluated against, if the caller specified one.
|
|
48
|
+
*/
|
|
49
|
+
readonly notificationTemplateType?: Maybe<NotificationTemplateType>;
|
|
50
|
+
/**
|
|
51
|
+
* The time the health check started. Use this rather than reading the clock so every finding in a
|
|
52
|
+
* single check shares one reference time.
|
|
53
|
+
*/
|
|
54
|
+
readonly now: Date;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Output of a {@link NotificationSendServiceHealthCheckService}.
|
|
58
|
+
*/
|
|
59
|
+
export interface NotificationSendServiceHealthCheckResponse {
|
|
60
|
+
/**
|
|
61
|
+
* Provider-level findings. May be empty when the provider had nothing to report.
|
|
62
|
+
*/
|
|
63
|
+
readonly issues: NotificationHealthCheckIssue[];
|
|
64
|
+
/**
|
|
65
|
+
* The probe for this delivery method — a newly dispatched one, a resolved pending one, or a pending
|
|
66
|
+
* one that still has no outcome.
|
|
67
|
+
*/
|
|
68
|
+
readonly probe?: Maybe<NotificationHealthCheckProbe>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Contributes provider-specific diagnostics for one delivery method.
|
|
72
|
+
*
|
|
73
|
+
* Implementations should be failure-tolerant: if a provider API is unreachable, report that as an
|
|
74
|
+
* {@link NotificationHealthCheckStatus.UNKNOWN} issue rather than throwing, so the rest of the health
|
|
75
|
+
* check still reaches the user.
|
|
76
|
+
*
|
|
77
|
+
* @template T - The delivery target type for the method.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const healthCheckService: NotificationEmailSendServiceHealthCheckService = {
|
|
82
|
+
* supportsProbe: true,
|
|
83
|
+
* async runHealthCheck({ target, sendProbe }) {
|
|
84
|
+
* const issues = await inspectSuppressionLists(target);
|
|
85
|
+
* const probe = sendProbe ? await dispatchProbe(target) : undefined;
|
|
86
|
+
* return { issues, probe };
|
|
87
|
+
* }
|
|
88
|
+
* };
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export interface NotificationSendServiceHealthCheckService<T = unknown> {
|
|
92
|
+
/**
|
|
93
|
+
* Whether this service can dispatch a probe message.
|
|
94
|
+
*
|
|
95
|
+
* Reported to callers so a UI can hide the "send a test message" affordance for methods that do not
|
|
96
|
+
* support it. Defaults to false.
|
|
97
|
+
*/
|
|
98
|
+
readonly supportsProbe?: Maybe<boolean>;
|
|
99
|
+
/**
|
|
100
|
+
* Runs the provider's diagnostics for a single delivery target.
|
|
101
|
+
*
|
|
102
|
+
* @param request - The target to diagnose and whether probing is permitted.
|
|
103
|
+
* @returns The provider's findings and probe state.
|
|
104
|
+
*/
|
|
105
|
+
runHealthCheck(request: NotificationSendServiceHealthCheckRequest<T>): Promise<NotificationSendServiceHealthCheckResponse>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* A {@link NotificationSendServiceHealthCheckService} for the email delivery method.
|
|
109
|
+
*/
|
|
110
|
+
export type NotificationEmailSendServiceHealthCheckService = NotificationSendServiceHealthCheckService<EmailAddress>;
|
|
111
|
+
/**
|
|
112
|
+
* A {@link NotificationSendServiceHealthCheckService} for the text/SMS delivery method.
|
|
113
|
+
*/
|
|
114
|
+
export type NotificationTextSendServiceHealthCheckService = NotificationSendServiceHealthCheckService<E164PhoneNumber>;
|
|
115
|
+
/**
|
|
116
|
+
* A {@link NotificationSendServiceHealthCheckService} for the in-app notification summary delivery method.
|
|
117
|
+
*/
|
|
118
|
+
export type NotificationSummarySendServiceHealthCheckService = NotificationSendServiceHealthCheckService<NotificationSummaryId>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type NotificationSummaryIdForUidFunction, type NotificationMessage, type NotificationSendEmailMessagesResult, type NotificationSendNotificationSummaryMessagesResult, type NotificationSendTextMessagesResult } from '@dereekb/firebase';
|
|
2
2
|
import { type NotificationSendMessagesInstance } from './notification.send';
|
|
3
|
+
import { type NotificationEmailSendServiceHealthCheckService, type NotificationSummarySendServiceHealthCheckService, type NotificationTextSendServiceHealthCheckService } from './notification.healthcheck.service';
|
|
3
4
|
import { type Maybe } from '@dereekb/util';
|
|
4
5
|
/**
|
|
5
6
|
* Provides a reference to a NotificationSendService instance.
|
|
@@ -46,6 +47,13 @@ export interface NotificationEmailSendService {
|
|
|
46
47
|
* Can throw an error if the messages cannot be sent or generated properly due to a configuration error.
|
|
47
48
|
*/
|
|
48
49
|
buildSendInstanceForEmailNotificationMessages(notificationMessages: NotificationMessage[]): Promise<NotificationSendMessagesInstance<NotificationSendEmailMessagesResult>>;
|
|
50
|
+
/**
|
|
51
|
+
* Optional. Contributes provider-specific diagnostics for the email channel to a notification
|
|
52
|
+
* delivery health check.
|
|
53
|
+
*
|
|
54
|
+
* When absent, a health check reports only what it can determine from configuration and send history.
|
|
55
|
+
*/
|
|
56
|
+
readonly healthCheckService?: Maybe<NotificationEmailSendServiceHealthCheckService>;
|
|
49
57
|
}
|
|
50
58
|
/**
|
|
51
59
|
* Service dedicated to sending notification text messages.
|
|
@@ -57,6 +65,13 @@ export interface NotificationTextSendService {
|
|
|
57
65
|
* Can throw an error if the messages cannot be sent or generated properly due to a configuration error.
|
|
58
66
|
*/
|
|
59
67
|
buildSendInstanceForTextNotificationMessages(notificationMessages: NotificationMessage[]): Promise<NotificationSendMessagesInstance<NotificationSendTextMessagesResult>>;
|
|
68
|
+
/**
|
|
69
|
+
* Optional. Contributes provider-specific diagnostics for the text/SMS channel to a notification
|
|
70
|
+
* delivery health check.
|
|
71
|
+
*
|
|
72
|
+
* When absent, a health check reports only what it can determine from configuration and send history.
|
|
73
|
+
*/
|
|
74
|
+
readonly healthCheckService?: Maybe<NotificationTextSendServiceHealthCheckService>;
|
|
60
75
|
}
|
|
61
76
|
/**
|
|
62
77
|
* Service dedicated to sending/updating NotificationSummary values in the system for the input messages.
|
|
@@ -68,4 +83,11 @@ export interface NotificationSummarySendService {
|
|
|
68
83
|
* Can throw an error if the messages cannot be sent or generated properly due to a configuration error.
|
|
69
84
|
*/
|
|
70
85
|
buildSendInstanceForNotificationSummaryMessages(notificationMessages: NotificationMessage[]): Promise<NotificationSendMessagesInstance<NotificationSendNotificationSummaryMessagesResult>>;
|
|
86
|
+
/**
|
|
87
|
+
* Optional. Contributes diagnostics for the in-app notification summary channel to a notification
|
|
88
|
+
* delivery health check.
|
|
89
|
+
*
|
|
90
|
+
* When absent, a health check reports only what it can determine from configuration and send history.
|
|
91
|
+
*/
|
|
92
|
+
readonly healthCheckService?: Maybe<NotificationSummarySendServiceHealthCheckService>;
|
|
71
93
|
}
|
package/oidc/index.cjs.js
CHANGED
|
@@ -3555,11 +3555,18 @@ exports.OidcProviderConfigService = __decorate([
|
|
|
3555
3555
|
* Resolves the unlocked + required scope sets for a client from the provider-profile registry and
|
|
3556
3556
|
* the profile keys assigned to that client.
|
|
3557
3557
|
*
|
|
3558
|
+
* A client with NO assigned profiles resolves to the registry's default profiles (those marked
|
|
3559
|
+
* `isDefault`), so a registry declaring no default resolves exactly as it would from the assigned
|
|
3560
|
+
* keys alone.
|
|
3561
|
+
*
|
|
3562
|
+
* This is the single choke point for a client's profile resolution — the consent unlock gate, the
|
|
3563
|
+
* consent required gate, and the `requiredScopes` interaction param all read through it.
|
|
3564
|
+
*
|
|
3558
3565
|
* @param providerProfiles - The provider-profile registry (from {@link OidcProviderConfig.providerProfiles}).
|
|
3559
3566
|
* @param clientProfileKeys - The profile keys assigned to the client (its `dbx_provider_profiles`).
|
|
3560
3567
|
* @returns The unlocked and required scope sets for the client.
|
|
3561
3568
|
*/ function oidcClientProviderProfileScopes(providerProfiles, clientProfileKeys) {
|
|
3562
|
-
var clientProfiles = firebase.
|
|
3569
|
+
var clientProfiles = firebase.oidcProviderProfilesForClient(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : [], clientProfileKeys);
|
|
3563
3570
|
return {
|
|
3564
3571
|
unlocked: firebase.scopesForOidcProviderProfiles(clientProfiles),
|
|
3565
3572
|
required: firebase.requiredScopesForOidcProviderProfiles(clientProfiles)
|
|
@@ -6212,7 +6219,7 @@ var OidcInteractionController_1;
|
|
|
6212
6219
|
* @throws {HttpException} HTTP 400 when the consent interaction cannot be completed.
|
|
6213
6220
|
*/ function postConsent(uid, body, res) {
|
|
6214
6221
|
return _async_to_generator$2(function() {
|
|
6215
|
-
var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _ref3, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref4, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, clientPayload, requestedOIDCScopeSet, adminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref5, redirectTo1,
|
|
6222
|
+
var startedAt, clientId, accountId, _ref, _prompt_details_missingOIDCScope, _this_accountService_providerConfig_adminOnlyScopes, _ref1, _ref2, _ref3, _prompt_details_missingOIDCClaims, _prompt_details_missingResourceScopes, _this_accountService_delegate_isAdminUser, _this_accountService_delegate, _ref4, redirectTo, interaction, prompt, params, session, missingOIDCScope, existingGrant, _tmp, encounteredOIDCScopes, clientPayload, requestedOIDCScopeSet, providerProfiles, configAdminOnlyScopes, adminOnlyScopes, requestedAdminOnlyScopes, requestsServiceToken, isAdmin, _tmp1, _ref5, redirectTo1, _oidcClientProviderProfileScopes, clientUnlockedScopes, clientRequiredScopes, profileGatedScopes, disallowedGatedScopes, _ref6, redirectTo2, missingRequiredScopes, _ref7, redirectTo3, requestedRawTtl, clientMaxSessionTtl, expiresInSeconds, grant, _tmp2, _resolveEffectiveSubset, granted, rejected, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, value, encounteredOIDCClaims, missingOIDCClaims, _resolveEffectiveSubset1, granted1, rejected1, missingResourceScopes, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, _step_value, indicator, scopes, _body_grantedResourceScopes, requestedSubset, encounteredResourceScopes, _resolveEffectiveSubset2, granted2, rejected2, _iteratorNormalCompletion2, _didIteratorError2, _iteratorError2, _iterator2, _step2, value1, grantId, _ref8, redirectTo4, err;
|
|
6216
6223
|
return _ts_generator$2(this, function(_state) {
|
|
6217
6224
|
switch(_state.label){
|
|
6218
6225
|
case 0:
|
|
@@ -6296,11 +6303,20 @@ var OidcInteractionController_1;
|
|
|
6296
6303
|
clientPayload = _state.sent();
|
|
6297
6304
|
// Admin-only scope gate. The requested OIDC scope set is `missingOIDCScope ∪ encounteredOIDCScopes`
|
|
6298
6305
|
// (a fresh consent has everything in `missing`; a re-consent may carry already-granted scopes in
|
|
6299
|
-
// `encountered`). If that set intersects the provider's `adminOnlyScopes`
|
|
6300
|
-
//
|
|
6306
|
+
// `encountered`). If that set intersects the admin-only scopes — the provider's `adminOnlyScopes`
|
|
6307
|
+
// unioned with the scopes of every profile marked `adminOnly` — and the resolving user is not an
|
|
6308
|
+
// admin, hard-reject with `access_denied` rather than silently dropping the scope.
|
|
6301
6309
|
requestedOIDCScopeSet = new Set(_to_consumable_array$1(missingOIDCScope).concat(_to_consumable_array$1(encounteredOIDCScopes)));
|
|
6302
|
-
|
|
6303
|
-
|
|
6310
|
+
providerProfiles = this.accountService.providerConfig.providerProfiles;
|
|
6311
|
+
configAdminOnlyScopes = (_this_accountService_providerConfig_adminOnlyScopes = this.accountService.providerConfig.adminOnlyScopes) !== null && _this_accountService_providerConfig_adminOnlyScopes !== void 0 ? _this_accountService_providerConfig_adminOnlyScopes : [];
|
|
6312
|
+
adminOnlyScopes = new Set(_to_consumable_array$1(configAdminOnlyScopes).concat(_to_consumable_array$1(firebase.adminOnlyScopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []))));
|
|
6313
|
+
requestedAdminOnlyScopes = Array.from(adminOnlyScopes).filter(function(scope) {
|
|
6314
|
+
return requestedOIDCScopeSet.has(scope);
|
|
6315
|
+
});
|
|
6316
|
+
// Deliberately sourced from the provider config's `adminOnlyScopes` ALONE, unlike the gate above:
|
|
6317
|
+
// this flag also selects the (highest) service-token TTL tier further down, and a profile marked
|
|
6318
|
+
// `adminOnly` must gate by admin status WITHOUT widening its grant's lifetime to that tier.
|
|
6319
|
+
requestsServiceToken = configAdminOnlyScopes.some(function(scope) {
|
|
6304
6320
|
return requestedOIDCScopeSet.has(scope);
|
|
6305
6321
|
});
|
|
6306
6322
|
if (!accountId) return [
|
|
@@ -6322,7 +6338,7 @@ var OidcInteractionController_1;
|
|
|
6322
6338
|
_state.label = 12;
|
|
6323
6339
|
case 12:
|
|
6324
6340
|
isAdmin = _tmp1;
|
|
6325
|
-
if (!(
|
|
6341
|
+
if (!(requestedAdminOnlyScopes.length > 0 && !isAdmin)) return [
|
|
6326
6342
|
3,
|
|
6327
6343
|
14
|
|
6328
6344
|
];
|
|
@@ -6330,7 +6346,7 @@ var OidcInteractionController_1;
|
|
|
6330
6346
|
4,
|
|
6331
6347
|
this.oidcInteractionService.finishInteractionByUid(uid, {
|
|
6332
6348
|
error: 'access_denied',
|
|
6333
|
-
error_description:
|
|
6349
|
+
error_description: "The following scope(s) are restricted to admins: ".concat(requestedAdminOnlyScopes.join(', '), ".")
|
|
6334
6350
|
}, {
|
|
6335
6351
|
mergeWithLastSubmission: true
|
|
6336
6352
|
})
|
|
@@ -6342,7 +6358,7 @@ var OidcInteractionController_1;
|
|
|
6342
6358
|
isSuccessful: false,
|
|
6343
6359
|
uid: accountId,
|
|
6344
6360
|
clientId: clientId,
|
|
6345
|
-
serviceToken:
|
|
6361
|
+
serviceToken: requestsServiceToken,
|
|
6346
6362
|
isAdmin: false,
|
|
6347
6363
|
reason: 'service_token_non_admin',
|
|
6348
6364
|
durationMs: Date.now() - startedAt
|
|
@@ -6355,9 +6371,9 @@ var OidcInteractionController_1;
|
|
|
6355
6371
|
];
|
|
6356
6372
|
case 14:
|
|
6357
6373
|
// Provider-profile scope gate. Any scope referenced by a provider profile is "gated" — available
|
|
6358
|
-
// only to clients whose assigned profiles unlock it
|
|
6359
|
-
// scope may be
|
|
6360
|
-
|
|
6374
|
+
// only to clients whose assigned profiles unlock it (a client with NO assigned profiles resolves
|
|
6375
|
+
// to the registry's default profiles). Independent of the admin-only gate above; a scope may be
|
|
6376
|
+
// subject to both.
|
|
6361
6377
|
_oidcClientProviderProfileScopes = oidcClientProviderProfileScopes(providerProfiles, (_ref2 = clientPayload === null || clientPayload === void 0 ? void 0 : clientPayload.dbx_provider_profiles) !== null && _ref2 !== void 0 ? _ref2 : undefined), clientUnlockedScopes = _oidcClientProviderProfileScopes.unlocked, clientRequiredScopes = _oidcClientProviderProfileScopes.required;
|
|
6362
6378
|
profileGatedScopes = firebase.scopesForOidcProviderProfiles(providerProfiles !== null && providerProfiles !== void 0 ? providerProfiles : []);
|
|
6363
6379
|
// Unlock gate: reject any requested gated scope this client's profiles do not unlock.
|