@beignet/core 0.0.32 → 0.0.34
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/CHANGELOG.md +42 -0
- package/README.md +409 -34
- package/dist/application/index.d.ts.map +1 -1
- package/dist/application/index.js +63 -47
- package/dist/application/index.js.map +1 -1
- package/dist/contracts/contract-builder.d.ts +0 -4
- package/dist/contracts/contract-builder.d.ts.map +1 -1
- package/dist/contracts/contract-builder.js +0 -6
- package/dist/contracts/contract-builder.js.map +1 -1
- package/dist/events/index.d.ts +6 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +20 -10
- package/dist/events/index.js.map +1 -1
- package/dist/flags/index.d.ts +0 -4
- package/dist/flags/index.d.ts.map +1 -1
- package/dist/flags/index.js +0 -4
- package/dist/flags/index.js.map +1 -1
- package/dist/jobs/index.d.ts +415 -4
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +392 -3
- package/dist/jobs/index.js.map +1 -1
- package/dist/notifications/index.d.ts +194 -10
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +397 -59
- package/dist/notifications/index.js.map +1 -1
- package/dist/outbox/index.d.ts +150 -1
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +170 -1
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/index.d.ts +7 -2
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +2 -1
- package/dist/ports/index.js.map +1 -1
- package/dist/providers/instrumentation.d.ts +4 -0
- package/dist/providers/instrumentation.d.ts.map +1 -1
- package/dist/providers/instrumentation.js.map +1 -1
- package/dist/providers/provider.d.ts +1 -1
- package/dist/schedules/index.d.ts +8 -7
- package/dist/schedules/index.d.ts.map +1 -1
- package/dist/schedules/index.js +47 -16
- package/dist/schedules/index.js.map +1 -1
- package/dist/server/hooks/index.d.ts +1 -0
- package/dist/server/hooks/index.d.ts.map +1 -1
- package/dist/server/hooks/index.js +1 -0
- package/dist/server/hooks/index.js.map +1 -1
- package/dist/server/hooks/rate-limit.d.ts +26 -13
- package/dist/server/hooks/rate-limit.d.ts.map +1 -1
- package/dist/server/hooks/rate-limit.js +28 -34
- package/dist/server/hooks/rate-limit.js.map +1 -1
- package/dist/server/hooks/security.d.ts +179 -0
- package/dist/server/hooks/security.d.ts.map +1 -0
- package/dist/server/hooks/security.js +225 -0
- package/dist/server/hooks/security.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/instrumentation.d.ts.map +1 -1
- package/dist/server/instrumentation.js +22 -6
- package/dist/server/instrumentation.js.map +1 -1
- package/dist/server/request-context.d.ts +4 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js +3 -0
- package/dist/server/request-context.js.map +1 -1
- package/dist/server/runtime-integrity.d.ts +84 -0
- package/dist/server/runtime-integrity.d.ts.map +1 -0
- package/dist/server/runtime-integrity.js +160 -0
- package/dist/server/runtime-integrity.js.map +1 -0
- package/dist/server/server.d.ts +10 -10
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +75 -11
- package/dist/server/server.js.map +1 -1
- package/dist/server/trusted-proxy.d.ts +77 -0
- package/dist/server/trusted-proxy.d.ts.map +1 -0
- package/dist/server/trusted-proxy.js +129 -0
- package/dist/server/trusted-proxy.js.map +1 -0
- package/dist/tasks/index.d.ts +6 -7
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +22 -5
- package/dist/tasks/index.js.map +1 -1
- package/dist/tenancy/index.d.ts +41 -0
- package/dist/tenancy/index.d.ts.map +1 -0
- package/dist/tenancy/index.js +34 -0
- package/dist/tenancy/index.js.map +1 -0
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/tracing/execution.d.ts +14 -0
- package/dist/tracing/execution.d.ts.map +1 -0
- package/dist/tracing/execution.js +17 -0
- package/dist/tracing/execution.js.map +1 -0
- package/dist/tracing/index.d.ts +49 -0
- package/dist/tracing/index.d.ts.map +1 -1
- package/dist/tracing/index.js +59 -0
- package/dist/tracing/index.js.map +1 -1
- package/dist/uploads/client.d.ts.map +1 -1
- package/dist/uploads/client.js +44 -6
- package/dist/uploads/client.js.map +1 -1
- package/dist/uploads/index.d.ts +71 -0
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +344 -15
- package/dist/uploads/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +55 -4
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +106 -2
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +5 -5
- package/skills/app-architecture/SKILL.md +31 -2
- package/src/application/index.ts +88 -56
- package/src/contracts/contract-builder.ts +0 -7
- package/src/events/index.ts +27 -14
- package/src/flags/index.ts +0 -5
- package/src/jobs/index.ts +895 -7
- package/src/notifications/index.ts +678 -70
- package/src/outbox/index.ts +358 -2
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +17 -1
- package/src/providers/instrumentation.ts +4 -0
- package/src/providers/provider.ts +1 -1
- package/src/schedules/index.ts +60 -26
- package/src/server/hooks/index.ts +10 -0
- package/src/server/hooks/rate-limit.ts +52 -46
- package/src/server/hooks/security.ts +503 -0
- package/src/server/index.ts +8 -0
- package/src/server/instrumentation.ts +25 -5
- package/src/server/request-context.ts +7 -0
- package/src/server/runtime-integrity.ts +322 -0
- package/src/server/server.ts +88 -35
- package/src/server/trusted-proxy.ts +249 -0
- package/src/tasks/index.ts +29 -12
- package/src/tenancy/index.ts +55 -0
- package/src/testing/index.ts +2 -0
- package/src/tracing/execution.ts +37 -0
- package/src/tracing/index.ts +137 -0
- package/src/uploads/client.ts +65 -6
- package/src/uploads/index.ts +539 -14
- package/src/webhooks/index.ts +238 -9
package/src/webhooks/index.ts
CHANGED
|
@@ -105,11 +105,72 @@ export interface DefineWebhookOptions<
|
|
|
105
105
|
/**
|
|
106
106
|
* Options accepted by `verifyWebhook(...)`.
|
|
107
107
|
*/
|
|
108
|
-
export interface VerifyWebhookOptions
|
|
108
|
+
export interface VerifyWebhookOptions<
|
|
109
|
+
AllowUnknownEvents extends boolean = boolean,
|
|
110
|
+
> {
|
|
109
111
|
verifier?: WebhookVerifier;
|
|
110
|
-
allowUnknownEvents?:
|
|
112
|
+
allowUnknownEvents?: AllowUnknownEvents;
|
|
111
113
|
}
|
|
112
114
|
|
|
115
|
+
type InferWebhookVerificationResult<
|
|
116
|
+
TWebhook extends WebhookDef<string, WebhookEventSchemas>,
|
|
117
|
+
AllowUnknownEvents extends boolean,
|
|
118
|
+
> = true extends AllowUnknownEvents
|
|
119
|
+
? InferWebhookEvent<TWebhook> | WebhookEvent
|
|
120
|
+
: InferWebhookEvent<TWebhook>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Timestamp formats accepted by the generic HMAC webhook verifier.
|
|
124
|
+
*/
|
|
125
|
+
export type HmacWebhookTimestampFormat =
|
|
126
|
+
| "unix-seconds"
|
|
127
|
+
| "unix-milliseconds"
|
|
128
|
+
| "iso8601";
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Timestamp source and tolerance for generic HMAC replay protection.
|
|
132
|
+
*/
|
|
133
|
+
export type HmacWebhookTimestampOptions =
|
|
134
|
+
| {
|
|
135
|
+
/**
|
|
136
|
+
* Header that carries the provider event timestamp.
|
|
137
|
+
*/
|
|
138
|
+
header: string;
|
|
139
|
+
payloadPath?: never;
|
|
140
|
+
/**
|
|
141
|
+
* Timestamp format.
|
|
142
|
+
*
|
|
143
|
+
* @default "unix-seconds"
|
|
144
|
+
*/
|
|
145
|
+
format?: HmacWebhookTimestampFormat;
|
|
146
|
+
/**
|
|
147
|
+
* Maximum absolute clock skew between the event timestamp and receipt.
|
|
148
|
+
*
|
|
149
|
+
* @default 300
|
|
150
|
+
*/
|
|
151
|
+
toleranceSec?: number;
|
|
152
|
+
}
|
|
153
|
+
| {
|
|
154
|
+
header?: never;
|
|
155
|
+
/**
|
|
156
|
+
* Dot path used to read the provider event timestamp from the JSON
|
|
157
|
+
* payload.
|
|
158
|
+
*/
|
|
159
|
+
payloadPath: string;
|
|
160
|
+
/**
|
|
161
|
+
* Timestamp format.
|
|
162
|
+
*
|
|
163
|
+
* @default "unix-seconds"
|
|
164
|
+
*/
|
|
165
|
+
format?: HmacWebhookTimestampFormat;
|
|
166
|
+
/**
|
|
167
|
+
* Maximum absolute clock skew between the event timestamp and receipt.
|
|
168
|
+
*
|
|
169
|
+
* @default 300
|
|
170
|
+
*/
|
|
171
|
+
toleranceSec?: number;
|
|
172
|
+
};
|
|
173
|
+
|
|
113
174
|
/**
|
|
114
175
|
* Options for the in-memory webhook verifier.
|
|
115
176
|
*/
|
|
@@ -163,6 +224,10 @@ export interface CreateHmacWebhookVerifierOptions {
|
|
|
163
224
|
* @default "type"
|
|
164
225
|
*/
|
|
165
226
|
eventTypePath?: string;
|
|
227
|
+
/**
|
|
228
|
+
* Optional timestamp source used to reject replayed generic HMAC webhooks.
|
|
229
|
+
*/
|
|
230
|
+
timestamp?: HmacWebhookTimestampOptions;
|
|
166
231
|
}
|
|
167
232
|
|
|
168
233
|
/**
|
|
@@ -250,11 +315,12 @@ export function defineWebhook<
|
|
|
250
315
|
*/
|
|
251
316
|
export async function verifyWebhook<
|
|
252
317
|
TWebhook extends WebhookDef<string, WebhookEventSchemas>,
|
|
318
|
+
AllowUnknownEvents extends boolean = false,
|
|
253
319
|
>(
|
|
254
320
|
webhook: TWebhook,
|
|
255
321
|
input: VerifyWebhookInput,
|
|
256
|
-
options: VerifyWebhookOptions = {},
|
|
257
|
-
): Promise<
|
|
322
|
+
options: VerifyWebhookOptions<AllowUnknownEvents> = {},
|
|
323
|
+
): Promise<InferWebhookVerificationResult<TWebhook, AllowUnknownEvents>> {
|
|
258
324
|
validateWebhook(webhook);
|
|
259
325
|
const verifier = options.verifier ?? webhook.verifier;
|
|
260
326
|
if (!verifier) {
|
|
@@ -285,17 +351,26 @@ export async function verifyWebhook<
|
|
|
285
351
|
*/
|
|
286
352
|
export async function parseWebhookEvent<
|
|
287
353
|
TWebhook extends WebhookDef<string, WebhookEventSchemas>,
|
|
354
|
+
AllowUnknownEvents extends boolean = false,
|
|
288
355
|
>(
|
|
289
356
|
webhook: TWebhook,
|
|
290
357
|
event: WebhookEvent,
|
|
291
|
-
options: Pick<
|
|
292
|
-
|
|
358
|
+
options: Pick<
|
|
359
|
+
VerifyWebhookOptions<AllowUnknownEvents>,
|
|
360
|
+
"allowUnknownEvents"
|
|
361
|
+
> = {},
|
|
362
|
+
): Promise<InferWebhookVerificationResult<TWebhook, AllowUnknownEvents>> {
|
|
293
363
|
validateWebhook(webhook);
|
|
294
364
|
validateEvent(webhook, event);
|
|
295
365
|
|
|
296
366
|
const schema = webhook.events[event.type];
|
|
297
367
|
if (!schema) {
|
|
298
|
-
if (options.allowUnknownEvents
|
|
368
|
+
if (options.allowUnknownEvents === true) {
|
|
369
|
+
return event as InferWebhookVerificationResult<
|
|
370
|
+
TWebhook,
|
|
371
|
+
AllowUnknownEvents
|
|
372
|
+
>;
|
|
373
|
+
}
|
|
299
374
|
throw new WebhookVerificationError({
|
|
300
375
|
message: `Webhook "${webhook.name}" received unknown event type "${event.type}".`,
|
|
301
376
|
webhookName: webhook.name,
|
|
@@ -320,7 +395,7 @@ export async function parseWebhookEvent<
|
|
|
320
395
|
return {
|
|
321
396
|
...event,
|
|
322
397
|
payload: result.value,
|
|
323
|
-
}
|
|
398
|
+
} as InferWebhookVerificationResult<TWebhook, AllowUnknownEvents>;
|
|
324
399
|
}
|
|
325
400
|
|
|
326
401
|
/**
|
|
@@ -373,6 +448,7 @@ export function createHmacWebhookVerifier(
|
|
|
373
448
|
const algorithm = options.algorithm ?? "SHA-256";
|
|
374
449
|
const eventIdPath = options.eventIdPath ?? "id";
|
|
375
450
|
const eventTypePath = options.eventTypePath ?? "type";
|
|
451
|
+
const timestamp = normalizeHmacTimestampOptions(options.timestamp);
|
|
376
452
|
|
|
377
453
|
return {
|
|
378
454
|
async verify(input) {
|
|
@@ -396,6 +472,12 @@ export function createHmacWebhookVerifier(
|
|
|
396
472
|
}
|
|
397
473
|
|
|
398
474
|
const payload = parseJsonBody(input.rawBody);
|
|
475
|
+
const createdAt = validateHmacTimestamp({
|
|
476
|
+
input,
|
|
477
|
+
payload,
|
|
478
|
+
provider: options.provider,
|
|
479
|
+
timestamp,
|
|
480
|
+
});
|
|
399
481
|
const id = readStringPath(payload, eventIdPath);
|
|
400
482
|
const type = readStringPath(payload, eventTypePath);
|
|
401
483
|
|
|
@@ -418,6 +500,7 @@ export function createHmacWebhookVerifier(
|
|
|
418
500
|
id,
|
|
419
501
|
type,
|
|
420
502
|
provider: options.provider,
|
|
503
|
+
...(createdAt ? { createdAt } : {}),
|
|
421
504
|
payload,
|
|
422
505
|
raw: payload,
|
|
423
506
|
};
|
|
@@ -502,7 +585,7 @@ function parseJsonBody(rawBody: WebhookRawBody): unknown {
|
|
|
502
585
|
}
|
|
503
586
|
}
|
|
504
587
|
|
|
505
|
-
function
|
|
588
|
+
function readPath(input: unknown, path: string): unknown {
|
|
506
589
|
let value = input;
|
|
507
590
|
for (const segment of path.split(".")) {
|
|
508
591
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -511,9 +594,155 @@ function readStringPath(input: unknown, path: string): string | undefined {
|
|
|
511
594
|
value = (value as Record<string, unknown>)[segment];
|
|
512
595
|
}
|
|
513
596
|
|
|
597
|
+
return value;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function readStringPath(input: unknown, path: string): string | undefined {
|
|
601
|
+
const value = readPath(input, path);
|
|
514
602
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
515
603
|
}
|
|
516
604
|
|
|
605
|
+
type NormalizedHmacTimestampOptions =
|
|
606
|
+
| {
|
|
607
|
+
source: "header";
|
|
608
|
+
key: string;
|
|
609
|
+
format: HmacWebhookTimestampFormat;
|
|
610
|
+
toleranceSec: number;
|
|
611
|
+
}
|
|
612
|
+
| {
|
|
613
|
+
source: "payload";
|
|
614
|
+
path: string;
|
|
615
|
+
format: HmacWebhookTimestampFormat;
|
|
616
|
+
toleranceSec: number;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
function normalizeHmacTimestampOptions(
|
|
620
|
+
timestamp: HmacWebhookTimestampOptions | undefined,
|
|
621
|
+
): NormalizedHmacTimestampOptions | undefined {
|
|
622
|
+
if (!timestamp) return undefined;
|
|
623
|
+
|
|
624
|
+
const format = timestamp.format ?? "unix-seconds";
|
|
625
|
+
if (
|
|
626
|
+
format !== "unix-seconds" &&
|
|
627
|
+
format !== "unix-milliseconds" &&
|
|
628
|
+
format !== "iso8601"
|
|
629
|
+
) {
|
|
630
|
+
throw new WebhookOptionsError(
|
|
631
|
+
"Webhook HMAC timestamp format must be unix-seconds, unix-milliseconds, or iso8601.",
|
|
632
|
+
);
|
|
633
|
+
}
|
|
634
|
+
const toleranceSec = timestamp.toleranceSec ?? 300;
|
|
635
|
+
if (!Number.isInteger(toleranceSec) || toleranceSec <= 0) {
|
|
636
|
+
throw new WebhookOptionsError(
|
|
637
|
+
"Webhook HMAC timestamp toleranceSec must be a positive integer.",
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if ("header" in timestamp) {
|
|
642
|
+
if (!timestamp.header) {
|
|
643
|
+
throw new WebhookOptionsError(
|
|
644
|
+
"Webhook HMAC timestamp header is required.",
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
source: "header",
|
|
650
|
+
key: normalizeHeaderName(timestamp.header),
|
|
651
|
+
format,
|
|
652
|
+
toleranceSec,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (!timestamp.payloadPath) {
|
|
657
|
+
throw new WebhookOptionsError(
|
|
658
|
+
"Webhook HMAC timestamp payloadPath is required.",
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
source: "payload",
|
|
664
|
+
path: timestamp.payloadPath,
|
|
665
|
+
format,
|
|
666
|
+
toleranceSec,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function validateHmacTimestamp(args: {
|
|
671
|
+
input: VerifyWebhookInput;
|
|
672
|
+
payload: unknown;
|
|
673
|
+
provider: string | undefined;
|
|
674
|
+
timestamp: NormalizedHmacTimestampOptions | undefined;
|
|
675
|
+
}): Date | undefined {
|
|
676
|
+
const { timestamp } = args;
|
|
677
|
+
if (!timestamp) return undefined;
|
|
678
|
+
|
|
679
|
+
const value =
|
|
680
|
+
timestamp.source === "header"
|
|
681
|
+
? args.input.headers?.[timestamp.key]
|
|
682
|
+
: readPath(args.payload, timestamp.path);
|
|
683
|
+
|
|
684
|
+
if (value === undefined || value === null || value === "") {
|
|
685
|
+
throw new WebhookVerificationError({
|
|
686
|
+
message:
|
|
687
|
+
timestamp.source === "header"
|
|
688
|
+
? `Missing ${timestamp.key} header.`
|
|
689
|
+
: `Webhook payload is missing timestamp at "${timestamp.path}".`,
|
|
690
|
+
provider: args.provider,
|
|
691
|
+
code: "missing_timestamp",
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const createdAt = parseHmacTimestampValue(
|
|
696
|
+
value,
|
|
697
|
+
timestamp.format,
|
|
698
|
+
args.provider,
|
|
699
|
+
);
|
|
700
|
+
const receivedAt = args.input.receivedAt ?? new Date();
|
|
701
|
+
const skewMs = Math.abs(receivedAt.getTime() - createdAt.getTime());
|
|
702
|
+
if (skewMs > timestamp.toleranceSec * 1000) {
|
|
703
|
+
throw new WebhookVerificationError({
|
|
704
|
+
message: `Webhook timestamp is outside the ${timestamp.toleranceSec}s tolerance.`,
|
|
705
|
+
provider: args.provider,
|
|
706
|
+
code: "timestamp_outside_tolerance",
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
return createdAt;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function parseHmacTimestampValue(
|
|
714
|
+
value: unknown,
|
|
715
|
+
format: HmacWebhookTimestampFormat,
|
|
716
|
+
provider: string | undefined,
|
|
717
|
+
): Date {
|
|
718
|
+
const fail = () =>
|
|
719
|
+
new WebhookVerificationError({
|
|
720
|
+
message: "Webhook timestamp is invalid.",
|
|
721
|
+
provider,
|
|
722
|
+
code: "invalid_timestamp",
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
let millis: number;
|
|
726
|
+
if (format === "iso8601") {
|
|
727
|
+
if (typeof value !== "string") throw fail();
|
|
728
|
+
millis = Date.parse(value);
|
|
729
|
+
} else {
|
|
730
|
+
const numeric =
|
|
731
|
+
typeof value === "number"
|
|
732
|
+
? value
|
|
733
|
+
: typeof value === "string"
|
|
734
|
+
? Number(value)
|
|
735
|
+
: Number.NaN;
|
|
736
|
+
if (!Number.isFinite(numeric)) throw fail();
|
|
737
|
+
millis = format === "unix-seconds" ? numeric * 1000 : numeric;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (!Number.isFinite(millis)) throw fail();
|
|
741
|
+
const date = new Date(millis);
|
|
742
|
+
if (Number.isNaN(date.getTime())) throw fail();
|
|
743
|
+
return date;
|
|
744
|
+
}
|
|
745
|
+
|
|
517
746
|
async function hmacHex(
|
|
518
747
|
algorithm: CreateHmacWebhookVerifierOptions["algorithm"],
|
|
519
748
|
secret: string,
|