@beignet/core 0.0.31 → 0.0.33
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 +115 -0
- package/README.md +346 -21
- 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/domain/entity.d.ts +37 -6
- package/dist/domain/entity.d.ts.map +1 -1
- package/dist/domain/entity.js +9 -7
- package/dist/domain/entity.js.map +1 -1
- package/dist/domain/index.d.ts +4 -4
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +4 -4
- package/dist/domain/index.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 +5 -4
- package/dist/flags/index.d.ts.map +1 -1
- package/dist/flags/index.js +10 -9
- package/dist/flags/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +11 -1
- package/dist/idempotency/index.d.ts.map +1 -1
- package/dist/idempotency/index.js +4 -3
- package/dist/idempotency/index.js.map +1 -1
- package/dist/jobs/index.d.ts +449 -9
- package/dist/jobs/index.d.ts.map +1 -1
- package/dist/jobs/index.js +472 -12
- package/dist/jobs/index.js.map +1 -1
- package/dist/outbox/index.d.ts +170 -3
- package/dist/outbox/index.d.ts.map +1 -1
- package/dist/outbox/index.js +192 -8
- package/dist/outbox/index.js.map +1 -1
- package/dist/ports/events.d.ts +2 -2
- package/dist/ports/index.d.ts +6 -1
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/ports/index.js +1 -0
- 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 +10 -2
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js.map +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/search/index.d.ts +2 -1
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/index.js +1 -0
- package/dist/search/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/providers/loadProviderConfig.d.ts.map +1 -1
- package/dist/server/providers/loadProviderConfig.js +15 -2
- package/dist/server/providers/loadProviderConfig.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 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +55 -1
- 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 +6 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +12 -3
- 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 +182 -2
- package/dist/uploads/index.d.ts.map +1 -1
- package/dist/uploads/index.js +468 -76
- package/dist/uploads/index.js.map +1 -1
- package/dist/webhooks/index.d.ts +56 -4
- package/dist/webhooks/index.d.ts.map +1 -1
- package/dist/webhooks/index.js +107 -2
- package/dist/webhooks/index.js.map +1 -1
- package/package.json +5 -1
- package/skills/app-architecture/SKILL.md +23 -2
- package/src/application/index.ts +88 -56
- package/src/domain/entity.ts +61 -13
- package/src/domain/index.ts +8 -7
- package/src/events/index.ts +27 -14
- package/src/flags/index.ts +23 -19
- package/src/idempotency/index.ts +17 -3
- package/src/jobs/index.ts +1022 -19
- package/src/outbox/index.ts +409 -9
- package/src/ports/events.ts +2 -2
- package/src/ports/index.ts +17 -0
- package/src/providers/instrumentation.ts +4 -0
- package/src/providers/provider.ts +11 -2
- package/src/schedules/index.ts +60 -26
- package/src/search/index.ts +3 -1
- 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/providers/loadProviderConfig.ts +19 -2
- package/src/server/request-context.ts +7 -0
- package/src/server/runtime-integrity.ts +322 -0
- package/src/server/server.ts +86 -1
- 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 +19 -3
- 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 +713 -76
- package/src/webhooks/index.ts +240 -9
- package/src/domain/events.ts +0 -59
package/src/schedules/index.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { runWithResolvedTracingContext } from "../tracing/execution.js";
|
|
3
|
+
import type { TracingPort } from "../tracing/index.js";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Any Standard Schema compatible validator.
|
|
@@ -134,9 +136,7 @@ export interface ScheduleHandleArgs<S extends ScheduleDef, Ctx> {
|
|
|
134
136
|
* Parsed schedule payload.
|
|
135
137
|
*/
|
|
136
138
|
payload: InferSchedulePayload<S>;
|
|
137
|
-
/**
|
|
138
|
-
* Handler context.
|
|
139
|
-
*/
|
|
139
|
+
/** Handler context. */
|
|
140
140
|
ctx: Ctx;
|
|
141
141
|
/**
|
|
142
142
|
* Run metadata.
|
|
@@ -219,10 +219,10 @@ export type ScheduleRunArgs<
|
|
|
219
219
|
Ctx,
|
|
220
220
|
Payload = unknown,
|
|
221
221
|
> = ScheduleRunOptions<Payload> & {
|
|
222
|
-
/**
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
/** Handler context or factory resolved inside the schedule span. */
|
|
223
|
+
ctx: Ctx | (() => MaybePromise<Ctx>);
|
|
224
|
+
/** Runtime tracing port used before a lazy context factory runs. */
|
|
225
|
+
tracing?: TracingPort;
|
|
226
226
|
};
|
|
227
227
|
|
|
228
228
|
/**
|
|
@@ -374,6 +374,8 @@ export interface InlineScheduleRunnerOptions<Ctx> {
|
|
|
374
374
|
* Static schedule context or factory evaluated for each run.
|
|
375
375
|
*/
|
|
376
376
|
ctx?: Ctx | (() => MaybePromise<Ctx>);
|
|
377
|
+
/** Runtime tracing port used before a lazy context factory runs. */
|
|
378
|
+
tracing?: TracingPort;
|
|
377
379
|
/**
|
|
378
380
|
* Clock used when run timestamps are not provided.
|
|
379
381
|
*/
|
|
@@ -575,14 +577,33 @@ function createRunContext(
|
|
|
575
577
|
};
|
|
576
578
|
}
|
|
577
579
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
580
|
+
function scheduleTraceOperation(
|
|
581
|
+
schedule: ScheduleDef,
|
|
582
|
+
options: ScheduleRunOptions<unknown>,
|
|
583
|
+
) {
|
|
584
|
+
const attributes = {
|
|
585
|
+
"beignet.schedule.name": schedule.name,
|
|
586
|
+
...(options.attempt === undefined
|
|
587
|
+
? {}
|
|
588
|
+
: { "beignet.schedule.attempt": options.attempt }),
|
|
589
|
+
...(options.source === undefined
|
|
590
|
+
? {}
|
|
591
|
+
: { "beignet.schedule.source": options.source }),
|
|
592
|
+
} as const;
|
|
593
|
+
const metricAttributes = {
|
|
594
|
+
"beignet.schedule.name": schedule.name,
|
|
595
|
+
...(options.attempt === undefined
|
|
596
|
+
? {}
|
|
597
|
+
: { "beignet.schedule.attempt": options.attempt }),
|
|
598
|
+
} as const;
|
|
584
599
|
|
|
585
|
-
return
|
|
600
|
+
return {
|
|
601
|
+
name: `beignet.schedule ${schedule.name}`,
|
|
602
|
+
type: "schedule" as const,
|
|
603
|
+
kind: "consumer" as const,
|
|
604
|
+
attributes,
|
|
605
|
+
metricAttributes,
|
|
606
|
+
};
|
|
586
607
|
}
|
|
587
608
|
|
|
588
609
|
async function resolveSchedulePayload<S extends ScheduleDef>(
|
|
@@ -739,14 +760,21 @@ export async function runSchedule<
|
|
|
739
760
|
schedule: S,
|
|
740
761
|
args: ScheduleRunArgs<Ctx, InferSchedulePayload<S>>,
|
|
741
762
|
): Promise<void> {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
await schedule.handle({
|
|
746
|
-
schedule,
|
|
747
|
-
payload,
|
|
763
|
+
await runWithResolvedTracingContext({
|
|
764
|
+
tracing: args.tracing,
|
|
748
765
|
ctx: args.ctx,
|
|
749
|
-
|
|
766
|
+
operation: scheduleTraceOperation(schedule, args),
|
|
767
|
+
run: async (ctx) => {
|
|
768
|
+
const run = createRunContext(args, () => new Date());
|
|
769
|
+
const payload = await resolveSchedulePayload(schedule, args, run);
|
|
770
|
+
|
|
771
|
+
await schedule.handle({
|
|
772
|
+
schedule,
|
|
773
|
+
payload,
|
|
774
|
+
ctx,
|
|
775
|
+
run,
|
|
776
|
+
});
|
|
777
|
+
},
|
|
750
778
|
});
|
|
751
779
|
}
|
|
752
780
|
|
|
@@ -780,11 +808,17 @@ export function createInlineScheduleRunner<Ctx>(
|
|
|
780
808
|
options.onHookError,
|
|
781
809
|
lifecycleArgs,
|
|
782
810
|
);
|
|
783
|
-
await
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
run
|
|
811
|
+
await runWithResolvedTracingContext({
|
|
812
|
+
tracing: options.tracing,
|
|
813
|
+
ctx: options.ctx as Ctx | (() => MaybePromise<Ctx>),
|
|
814
|
+
operation: scheduleTraceOperation(schedule, runOptions),
|
|
815
|
+
run: (ctx) =>
|
|
816
|
+
schedule.handle({
|
|
817
|
+
schedule,
|
|
818
|
+
payload: payload as InferSchedulePayload<S>,
|
|
819
|
+
ctx,
|
|
820
|
+
run,
|
|
821
|
+
}),
|
|
788
822
|
});
|
|
789
823
|
await recordScheduleEvent(options, schedule, "completed", run, {
|
|
790
824
|
payload,
|
package/src/search/index.ts
CHANGED
|
@@ -47,6 +47,7 @@ type SearchField<TDocument extends SearchDocumentBase> = Extract<
|
|
|
47
47
|
export type SearchIndexDef<
|
|
48
48
|
TDocument extends SearchDocumentBase = SearchDocument,
|
|
49
49
|
> = {
|
|
50
|
+
kind: "search-index";
|
|
50
51
|
name: string;
|
|
51
52
|
primaryKey: SearchField<TDocument>;
|
|
52
53
|
searchableAttributes?: readonly SearchField<TDocument>[];
|
|
@@ -60,7 +61,7 @@ export type SearchIndexDef<
|
|
|
60
61
|
* Options accepted when defining a search index.
|
|
61
62
|
*/
|
|
62
63
|
export type DefineSearchIndexOptions<TDocument extends SearchDocumentBase> =
|
|
63
|
-
Omit<SearchIndexDef<TDocument>, "name" | "primaryKey"> & {
|
|
64
|
+
Omit<SearchIndexDef<TDocument>, "kind" | "name" | "primaryKey"> & {
|
|
64
65
|
primaryKey?: SearchField<TDocument>;
|
|
65
66
|
};
|
|
66
67
|
|
|
@@ -216,6 +217,7 @@ export function defineSearchIndex<
|
|
|
216
217
|
if (!name) throw new SearchOptionsError("Search index name is required.");
|
|
217
218
|
|
|
218
219
|
return {
|
|
220
|
+
kind: "search-index",
|
|
219
221
|
name,
|
|
220
222
|
primaryKey: options.primaryKey ?? ("id" as SearchField<TDocument>),
|
|
221
223
|
searchableAttributes: options.searchableAttributes,
|
|
@@ -46,6 +46,16 @@ export {
|
|
|
46
46
|
type RateLimitIpSource,
|
|
47
47
|
type RateLimitOptions,
|
|
48
48
|
} from "./rate-limit.js";
|
|
49
|
+
export {
|
|
50
|
+
applySecurityHeaders,
|
|
51
|
+
type CsrfFailureReason,
|
|
52
|
+
type CsrfHooksOptions,
|
|
53
|
+
type CsrfTokenOptions,
|
|
54
|
+
createCsrfHooks,
|
|
55
|
+
createSecurityHeadersHooks,
|
|
56
|
+
type SecurityHeadersOptions,
|
|
57
|
+
type StrictTransportSecurityOptions,
|
|
58
|
+
} from "./security.js";
|
|
49
59
|
|
|
50
60
|
/**
|
|
51
61
|
* Flatten hook arrays into a single hook list.
|
|
@@ -9,6 +9,11 @@ import {
|
|
|
9
9
|
createProviderInstrumentation,
|
|
10
10
|
type ProviderInstrumentationTarget,
|
|
11
11
|
} from "../../providers/index.js";
|
|
12
|
+
import {
|
|
13
|
+
resolveTrustedClientIp,
|
|
14
|
+
type TrustedProxyClientIpSource,
|
|
15
|
+
type TrustedProxyConfig,
|
|
16
|
+
} from "../trusted-proxy.js";
|
|
12
17
|
import type { HttpRequestLike, ServerHook } from "../types.js";
|
|
13
18
|
|
|
14
19
|
/**
|
|
@@ -40,14 +45,11 @@ type EarlyRateLimitScope = Exclude<RateLimitScope, "user">;
|
|
|
40
45
|
* - `"x-forwarded-for-first"`: the first `x-forwarded-for` entry. This value
|
|
41
46
|
* is client-controlled, so only use it when a trusted edge normalizes the
|
|
42
47
|
* header before it reaches the app.
|
|
48
|
+
* - `"x-real-ip"` and `"cf-connecting-ip"`: dedicated platform headers.
|
|
43
49
|
* - A function receives the raw request and returns the client IP, for
|
|
44
|
-
* platform-specific
|
|
50
|
+
* platform-specific resolution.
|
|
45
51
|
*/
|
|
46
|
-
export type RateLimitIpSource =
|
|
47
|
-
| "none"
|
|
48
|
-
| "x-forwarded-for-last"
|
|
49
|
-
| "x-forwarded-for-first"
|
|
50
|
-
| ((req: HttpRequestLike) => string | undefined);
|
|
52
|
+
export type RateLimitIpSource = "none" | TrustedProxyClientIpSource;
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* Options for `createRateLimitHooks(...)`.
|
|
@@ -76,42 +78,40 @@ export interface RateLimitOptions<Ctx> {
|
|
|
76
78
|
* Resolve the client IP for `ip`-scoped limits.
|
|
77
79
|
*
|
|
78
80
|
* There is no default: when any contract declares an `ip`-scoped rate limit
|
|
79
|
-
* and neither `ipSource
|
|
80
|
-
* fails at startup.
|
|
81
|
-
*
|
|
81
|
+
* and neither `trustedProxy.clientIp`, `ipSource`, nor a custom `earlyKey`
|
|
82
|
+
* is configured, the hook fails at startup. Prefer `trustedProxy.clientIp`
|
|
83
|
+
* for production proxy headers; keep `ipSource` for custom keying or
|
|
84
|
+
* compatibility, or pass `"none"` to explicitly accept one shared
|
|
82
85
|
* `ip:unknown` bucket for all clients.
|
|
83
86
|
*/
|
|
84
87
|
ipSource?: RateLimitIpSource;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Shared trusted-proxy policy used to resolve client IPs when `ipSource` is
|
|
90
|
+
* not set.
|
|
91
|
+
*
|
|
92
|
+
* Configure this only when the app is always behind a platform or reverse
|
|
93
|
+
* proxy that strips or normalizes forwarding headers. For `ip`-scoped rate
|
|
94
|
+
* limits, set `trustedProxy.clientIp` to the header source written by that
|
|
95
|
+
* trusted edge.
|
|
96
|
+
*/
|
|
97
|
+
trustedProxy?: TrustedProxyConfig;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function resolveClientIp(
|
|
96
101
|
req: HttpRequestLike,
|
|
97
102
|
ipSource: RateLimitIpSource,
|
|
98
103
|
): string | undefined {
|
|
99
|
-
if (typeof ipSource === "function") {
|
|
100
|
-
return ipSource(req) || undefined;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
104
|
if (ipSource === "none") {
|
|
104
105
|
return undefined;
|
|
105
106
|
}
|
|
107
|
+
return resolveTrustedClientIp(req, ipSource);
|
|
108
|
+
}
|
|
106
109
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return ipSource === "x-forwarded-for-first"
|
|
113
|
-
? entries[0]
|
|
114
|
-
: entries[entries.length - 1];
|
|
110
|
+
function hasTrustedProxyClientIp(
|
|
111
|
+
config: TrustedProxyConfig | undefined,
|
|
112
|
+
): boolean {
|
|
113
|
+
if (!config) return false;
|
|
114
|
+
return Boolean(config.clientIp);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
function formatContractNames(names: readonly string[]): string {
|
|
@@ -120,10 +120,9 @@ function formatContractNames(names: readonly string[]): string {
|
|
|
120
120
|
|
|
121
121
|
function ipSourceConfigurationError(contractNames: string): Error {
|
|
122
122
|
return new Error(
|
|
123
|
-
`createRateLimitHooks(...) has no
|
|
124
|
-
`Set ipSource to
|
|
125
|
-
`"x-forwarded-for-first"
|
|
126
|
-
`a function for platform-specific headers such as cf-connecting-ip, ` +
|
|
123
|
+
`createRateLimitHooks(...) has no client IP source configured, but contract(s) ${contractNames} declare an "ip"-scoped rate limit. ` +
|
|
124
|
+
`Set trustedProxy.clientIp or ipSource to a header source written by a trusted edge, ` +
|
|
125
|
+
`for example "x-forwarded-for-last", "x-forwarded-for-first", "x-real-ip", "cf-connecting-ip", or a custom function, ` +
|
|
127
126
|
`or "none" to explicitly accept one shared ip:unknown bucket for all clients.`,
|
|
128
127
|
);
|
|
129
128
|
}
|
|
@@ -240,14 +239,14 @@ async function enforceRateLimit(
|
|
|
240
239
|
* never sent to clients; denials emit a `rateLimit.denied` instrumentation
|
|
241
240
|
* event that carries the key for operators.
|
|
242
241
|
*
|
|
243
|
-
* `ip`-scoped limits require an explicit `
|
|
244
|
-
* the hook's `validate` phase fails `createServer(...)`
|
|
245
|
-
* registered contract declares an `ip` scope without one,
|
|
246
|
-
* collapsing all clients into a shared `ip:unknown` bucket.
|
|
247
|
-
* later through `server.route(...)` are not visible to
|
|
248
|
-
* enforcing an `ip`-scoped limit without
|
|
249
|
-
* configuration error at request time as a backstop. Pass
|
|
250
|
-
* to explicitly opt in to the shared `ip:unknown` bucket.
|
|
242
|
+
* `ip`-scoped limits require an explicit `trustedProxy.clientIp`, `ipSource`,
|
|
243
|
+
* or custom `earlyKey`: the hook's `validate` phase fails `createServer(...)`
|
|
244
|
+
* startup when a registered contract declares an `ip` scope without one,
|
|
245
|
+
* instead of silently collapsing all clients into a shared `ip:unknown` bucket.
|
|
246
|
+
* Contracts added later through `server.route(...)` are not visible to
|
|
247
|
+
* `validate`, so enforcing an `ip`-scoped limit without a client-IP source
|
|
248
|
+
* throws the same configuration error at request time as a backstop. Pass
|
|
249
|
+
* `ipSource: "none"` to explicitly opt in to the shared `ip:unknown` bucket.
|
|
251
250
|
*
|
|
252
251
|
* @param options - Optional key builders and client-IP source.
|
|
253
252
|
* @returns A server hook backed by `ctx.ports.rateLimit`.
|
|
@@ -255,21 +254,28 @@ async function enforceRateLimit(
|
|
|
255
254
|
export function createRateLimitHooks<Ctx extends CtxWithRateLimit>(
|
|
256
255
|
options: RateLimitOptions<Ctx> = {},
|
|
257
256
|
): ServerHook<Ctx, RateLimitPorts> {
|
|
258
|
-
const { ipSource } = options;
|
|
257
|
+
const { ipSource, trustedProxy } = options;
|
|
259
258
|
const getClientIp = (
|
|
260
259
|
req: HttpRequestLike,
|
|
261
260
|
contractName: string,
|
|
262
261
|
): string | undefined => {
|
|
263
|
-
if (ipSource
|
|
264
|
-
|
|
262
|
+
if (ipSource !== undefined) {
|
|
263
|
+
return resolveClientIp(req, ipSource);
|
|
264
|
+
}
|
|
265
|
+
if (hasTrustedProxyClientIp(trustedProxy) && trustedProxy) {
|
|
266
|
+
return resolveTrustedClientIp(req, trustedProxy.clientIp);
|
|
265
267
|
}
|
|
266
|
-
|
|
268
|
+
throw ipSourceConfigurationError(formatContractNames([contractName]));
|
|
267
269
|
};
|
|
268
270
|
|
|
269
271
|
return {
|
|
270
272
|
name: "rate-limit",
|
|
271
273
|
validate: ({ contracts }) => {
|
|
272
|
-
if (
|
|
274
|
+
if (
|
|
275
|
+
ipSource !== undefined ||
|
|
276
|
+
hasTrustedProxyClientIp(trustedProxy) ||
|
|
277
|
+
options.earlyKey
|
|
278
|
+
) {
|
|
273
279
|
return;
|
|
274
280
|
}
|
|
275
281
|
const ipScoped = contracts
|