@daloyjs/core 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +21 -2
  2. package/bin/daloy.mjs +2 -0
  3. package/dist/adapters/bun.js +16 -9
  4. package/dist/adapters/deno.js +7 -1
  5. package/dist/adapters/node.d.ts +11 -0
  6. package/dist/adapters/node.js +24 -0
  7. package/dist/app.d.ts +144 -1
  8. package/dist/app.js +208 -1
  9. package/dist/asyncapi.d.ts +98 -0
  10. package/dist/asyncapi.js +212 -0
  11. package/dist/auto-ban.d.ts +205 -0
  12. package/dist/auto-ban.js +222 -0
  13. package/dist/bot-guard.d.ts +209 -0
  14. package/dist/bot-guard.js +291 -0
  15. package/dist/cli.d.ts +8 -0
  16. package/dist/cli.js +88 -4
  17. package/dist/concurrency-limit.d.ts +135 -0
  18. package/dist/concurrency-limit.js +254 -0
  19. package/dist/docs.d.ts +57 -6
  20. package/dist/docs.js +34 -3
  21. package/dist/errors.d.ts +20 -0
  22. package/dist/errors.js +27 -0
  23. package/dist/fetch-guard.js +4 -0
  24. package/dist/fetch-resilience.d.ts +295 -0
  25. package/dist/fetch-resilience.js +485 -0
  26. package/dist/geo-block.d.ts +184 -0
  27. package/dist/geo-block.js +153 -0
  28. package/dist/hashing.d.ts +2 -1
  29. package/dist/hashing.js +12 -1
  30. package/dist/http-signatures.d.ts +303 -0
  31. package/dist/http-signatures.js +782 -0
  32. package/dist/idempotency.d.ts +204 -0
  33. package/dist/idempotency.js +341 -0
  34. package/dist/index.d.ts +38 -4
  35. package/dist/index.js +18 -1
  36. package/dist/ip-reputation.d.ts +198 -0
  37. package/dist/ip-reputation.js +253 -0
  38. package/dist/jwk.d.ts +15 -0
  39. package/dist/jwk.js +24 -2
  40. package/dist/load-shedding.d.ts +5 -0
  41. package/dist/logger.js +6 -2
  42. package/dist/metrics.d.ts +208 -0
  43. package/dist/metrics.js +452 -0
  44. package/dist/middleware.js +0 -10
  45. package/dist/mtls.d.ts +266 -0
  46. package/dist/mtls.js +488 -0
  47. package/dist/multipart.js +1 -1
  48. package/dist/openapi-diff.d.ts +79 -0
  49. package/dist/openapi-diff.js +246 -0
  50. package/dist/openapi.js +4 -1
  51. package/dist/pagination.d.ts +210 -0
  52. package/dist/pagination.js +353 -0
  53. package/dist/rate-limit-redis.d.ts +8 -0
  54. package/dist/rate-limit-redis.js +8 -0
  55. package/dist/request-decompression.d.ts +200 -0
  56. package/dist/request-decompression.js +363 -0
  57. package/dist/response-cache.d.ts +205 -0
  58. package/dist/response-cache.js +374 -0
  59. package/dist/router.d.ts +22 -0
  60. package/dist/router.js +64 -7
  61. package/dist/safe-redirect.d.ts +2 -2
  62. package/dist/safe-redirect.js +3 -8
  63. package/dist/sbom.cdx.json +9 -9
  64. package/dist/sbom.spdx.json +5 -5
  65. package/dist/scheduler.d.ts +315 -0
  66. package/dist/scheduler.js +546 -0
  67. package/dist/security.d.ts +27 -7
  68. package/dist/security.js +27 -7
  69. package/dist/session.js +3 -3
  70. package/dist/types.d.ts +33 -0
  71. package/dist/waf.d.ts +213 -0
  72. package/dist/waf.js +334 -0
  73. package/dist/webhook-delivery.d.ts +263 -0
  74. package/dist/webhook-delivery.js +311 -0
  75. package/dist/websocket.d.ts +52 -0
  76. package/dist/websocket.js +13 -0
  77. package/package.json +76 -2
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Country-level access control for {@link Hooks}. The {@link geoBlock}
3
+ * middleware enforces ISO 3166-1 alpha-2 country allow- and deny-lists by
4
+ * mapping the client IP to a country **using an operator-supplied lookup** —
5
+ * Daloy bundles no GeoIP database and adds no runtime dependency, keeping the
6
+ * `@daloyjs/core` zero-dependency floor intact.
7
+ *
8
+ * Two resolution strategies are supported, exactly one of which must be wired:
9
+ *
10
+ * - `lookupCountry(ip)` — you own the IP → country mapping (e.g. a MaxMind
11
+ * GeoLite2 reader, an `ip2location` reader, or your own table). Daloy
12
+ * resolves the client IP (reusing the same `X-Forwarded-For` / `X-Real-IP`
13
+ * handling as {@link "./ip-restriction.js".ipRestriction}) and hands you the
14
+ * string.
15
+ * - `resolveCountry(ctx)` — the country is already attached to the request by
16
+ * an upstream edge (e.g. Cloudflare's `CF-IPCountry`, AWS CloudFront's
17
+ * `CloudFront-Viewer-Country`, Vercel's `x-vercel-ip-country`); you read it
18
+ * straight off the context.
19
+ *
20
+ * Like the other network guards this fails **closed for allow-lists** (an
21
+ * unknown country is rejected when an allow-list is configured) and **open for
22
+ * deny-only** configurations, so a missing lookup cannot silently widen access.
23
+ *
24
+ * @module
25
+ * @since 0.37.0
26
+ */
27
+ import { ForbiddenError } from "./errors.js";
28
+ /** @internal Validate + normalise a configured country code, or throw. */
29
+ function normalizeConfiguredCode(input) {
30
+ const code = input.trim().toUpperCase();
31
+ if (!/^[A-Z0-9]{2}$/.test(code)) {
32
+ throw new Error(`geoBlock(): invalid country code ${JSON.stringify(input)}; expected a ` +
33
+ "2-character ISO 3166-1 alpha-2 code.");
34
+ }
35
+ return code;
36
+ }
37
+ /** @internal Default resolver: deliberately yields nothing (fail closed). */
38
+ function noIpResolver(_ctx) {
39
+ return undefined;
40
+ }
41
+ /** @internal Read the leading `X-Forwarded-For` / `X-Real-IP` hop. */
42
+ function forwardedIpResolver(ctx) {
43
+ const headers = ctx.request.headers;
44
+ const forwarded = headers.get("x-forwarded-for");
45
+ if (forwarded) {
46
+ const first = forwarded.split(",")[0]?.trim();
47
+ if (first)
48
+ return first;
49
+ }
50
+ return ctx.request.headers.get("x-real-ip") ?? undefined;
51
+ }
52
+ /**
53
+ * Block or allow requests by client country. Daloy ships no GeoIP database;
54
+ * supply either an IP → country `lookupCountry` (e.g. a MaxMind reader) or a
55
+ * `resolveCountry` that reads an edge-injected country header.
56
+ *
57
+ * @example MaxMind-style IP lookup behind a trusted proxy
58
+ * ```ts
59
+ * import maxmind from "maxmind"; // operator dependency, not a Daloy one
60
+ * const reader = await maxmind.open<{ country?: { iso_code?: string } }>("GeoLite2-Country.mmdb");
61
+ * app.use(geoBlock({
62
+ * deny: ["KP", "IR"],
63
+ * trustProxyHeaders: true,
64
+ * lookupCountry: (ip) => reader.get(ip)?.country?.iso_code,
65
+ * }));
66
+ * ```
67
+ *
68
+ * @example Cloudflare edge header (no IP lookup needed)
69
+ * ```ts
70
+ * app.use(geoBlock({
71
+ * allow: ["US", "CA", "GB"],
72
+ * resolveCountry: (ctx) => ctx.request.headers.get("cf-ipcountry"),
73
+ * }));
74
+ * ```
75
+ *
76
+ * On reject the middleware throws a {@link ForbiddenError}, which Daloy renders
77
+ * as RFC 9457 `application/problem+json` with `Cache-Control: no-store`.
78
+ *
79
+ * @param opts - Geo-blocking configuration.
80
+ * @returns {@link Hooks} to register via `app.use(...)`.
81
+ * @throws Error when neither `allow` nor `deny` is provided, when both or
82
+ * neither of `lookupCountry` / `resolveCountry` are provided, when a country
83
+ * code is malformed, or when `mode` is invalid.
84
+ * @since 0.37.0
85
+ */
86
+ export function geoBlock(opts) {
87
+ if (!opts.allow?.length && !opts.deny?.length) {
88
+ throw new Error('geoBlock(): at least one of "allow" or "deny" must be provided.');
89
+ }
90
+ const hasLookup = typeof opts.lookupCountry === "function";
91
+ const hasResolve = typeof opts.resolveCountry === "function";
92
+ if (hasLookup === hasResolve) {
93
+ throw new Error('geoBlock(): exactly one of "lookupCountry" or "resolveCountry" must ' +
94
+ "be provided.");
95
+ }
96
+ if (opts.mode !== undefined && opts.mode !== "block" && opts.mode !== "log") {
97
+ throw new Error(`geoBlock(): invalid mode ${JSON.stringify(opts.mode)}; expected ` +
98
+ '"block" or "log".');
99
+ }
100
+ const allow = new Set((opts.allow ?? []).map(normalizeConfiguredCode));
101
+ const deny = new Set((opts.deny ?? []).map(normalizeConfiguredCode));
102
+ // Allow-lists fail closed on an unknown country; deny-only fails open.
103
+ const allowUnknown = opts.allowUnknownCountry ?? allow.size === 0;
104
+ const mode = opts.mode ?? "block";
105
+ const message = opts.message ?? "Access from your region is not permitted";
106
+ const stateKey = opts.stateKey ?? "geo";
107
+ const onBlock = opts.onBlock;
108
+ const lookupCountry = opts.lookupCountry;
109
+ const resolveCountry = opts.resolveCountry;
110
+ const resolveIp = opts.resolveIp ??
111
+ (opts.trustProxyHeaders ? forwardedIpResolver : noIpResolver);
112
+ return {
113
+ async beforeHandle(ctx) {
114
+ let ip;
115
+ let rawCountry;
116
+ if (resolveCountry) {
117
+ rawCountry = await resolveCountry(ctx);
118
+ }
119
+ else {
120
+ ip = resolveIp(ctx) ?? undefined;
121
+ rawCountry = ip ? await lookupCountry(ip) : undefined;
122
+ }
123
+ const country = rawCountry && rawCountry.trim()
124
+ ? rawCountry.trim().toUpperCase()
125
+ : undefined;
126
+ let reason;
127
+ if (!country) {
128
+ if (!allowUnknown)
129
+ reason = "unknown_country";
130
+ }
131
+ else if (deny.has(country)) {
132
+ reason = "denied_country";
133
+ }
134
+ else if (allow.size > 0 && !allow.has(country)) {
135
+ reason = "not_in_allowlist";
136
+ }
137
+ if (reason) {
138
+ const decision = {
139
+ ...(ip ? { ip } : {}),
140
+ ...(country ? { country } : {}),
141
+ reason,
142
+ };
143
+ onBlock?.(decision);
144
+ if (mode === "block")
145
+ throw new ForbiddenError(message);
146
+ return;
147
+ }
148
+ // Allowed: expose the resolved country to downstream handlers.
149
+ const state = country ? { country } : {};
150
+ ctx.state[stateKey] = state;
151
+ },
152
+ };
153
+ }
package/dist/hashing.d.ts CHANGED
@@ -40,7 +40,8 @@
40
40
  *
41
41
  * @param password - The plaintext password. UTF-8 encoded internally.
42
42
  * @returns A PHC-style hash string safe to store in a database column.
43
- * @throws {TypeError} When `password` is empty.
43
+ * @throws {TypeError} When `password` is empty or exceeds
44
+ * {@link MAX_PASSWORD_BYTES} UTF-8 bytes.
44
45
  * @since 0.15.0
45
46
  */
46
47
  export declare function passwordHash(password: string): Promise<string>;
package/dist/hashing.js CHANGED
@@ -31,6 +31,11 @@ const SCRYPT_KEYLEN = 32;
31
31
  const SCRYPT_SALTLEN = 16;
32
32
  // Generous max — covers OWASP's recommended `N = 2^17` at `r = 8`, `p = 1`.
33
33
  const SCRYPT_MAXMEM = 192 * 1024 * 1024;
34
+ // Upper bound on password size (UTF-8 bytes). scrypt runs PBKDF2-HMAC-SHA256
35
+ // over the full password, so an unbounded input lets an attacker amplify CPU
36
+ // per call. OWASP's Password Storage Cheat Sheet recommends capping length;
37
+ // 4096 bytes is well above any legitimate passphrase while blocking abuse.
38
+ const MAX_PASSWORD_BYTES = 4096;
34
39
  function scryptAsync(password, salt, keylen) {
35
40
  return new Promise((resolve, reject) => {
36
41
  scryptCb(password, salt, keylen, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P, maxmem: SCRYPT_MAXMEM }, (err, key) => {
@@ -75,13 +80,17 @@ function fromBase64(s) {
75
80
  *
76
81
  * @param password - The plaintext password. UTF-8 encoded internally.
77
82
  * @returns A PHC-style hash string safe to store in a database column.
78
- * @throws {TypeError} When `password` is empty.
83
+ * @throws {TypeError} When `password` is empty or exceeds
84
+ * {@link MAX_PASSWORD_BYTES} UTF-8 bytes.
79
85
  * @since 0.15.0
80
86
  */
81
87
  export async function passwordHash(password) {
82
88
  if (typeof password !== "string" || password.length === 0) {
83
89
  throw new TypeError("password must be a non-empty string");
84
90
  }
91
+ if (Buffer.byteLength(password, "utf8") > MAX_PASSWORD_BYTES) {
92
+ throw new TypeError(`password must not exceed ${MAX_PASSWORD_BYTES} bytes`);
93
+ }
85
94
  const salt = randomBytes(SCRYPT_SALTLEN);
86
95
  const key = await scryptAsync(Buffer.from(password, "utf8"), salt, SCRYPT_KEYLEN);
87
96
  return `$scrypt$N=${SCRYPT_N},r=${SCRYPT_R},p=${SCRYPT_P}$${toBase64(salt)}$${toBase64(key)}`;
@@ -130,6 +139,8 @@ function parsePhc(s) {
130
139
  export async function passwordVerify(password, storedHash) {
131
140
  if (typeof password !== "string" || password.length === 0)
132
141
  return false;
142
+ if (Buffer.byteLength(password, "utf8") > MAX_PASSWORD_BYTES)
143
+ return false;
133
144
  const parsed = parsePhc(storedHash);
134
145
  if (!parsed)
135
146
  return false;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * HTTP Message Signatures (RFC 9421).
3
+ *
4
+ * First-party, dependency-free sign + verify for server-to-server request
5
+ * authentication. Where {@link "./security.js" | verifyWebhookSignature} binds
6
+ * an HMAC to a request *body* and {@link "./mtls.js" | clientCertAuth}
7
+ * authenticates the TLS *peer*, HTTP Message Signatures bind a signature to a
8
+ * caller-chosen set of **HTTP message components** (method, path, authority,
9
+ * selected headers, …) carried in the standard `Signature` /
10
+ * `Signature-Input` headers — the IETF-standard answer to "prove this internal
11
+ * call came from a trusted peer."
12
+ *
13
+ * The implementation is runtime-portable (WebCrypto only — no `node:` imports)
14
+ * and secure-by-default on the verify path:
15
+ *
16
+ * - The verifier requires an explicit {@link VerifyMessageOptions.algorithms}
17
+ * allowlist; there is no implicit "accept any algorithm" mode, and a
18
+ * resolved key may pin its own algorithm to defeat algorithm-confusion.
19
+ * - `created` is required by default and the signature is rejected once it is
20
+ * older than {@link DEFAULT_MAX_SIGNATURE_AGE_SECONDS}, or if `created` is in
21
+ * the future / `expires` has passed (outside a small clock-skew tolerance).
22
+ * - A configurable {@link VerifyMessageOptions.requiredComponents} set must be
23
+ * covered, so a peer cannot sign an empty/irrelevant component set.
24
+ * - Raw HMAC keys must be at least 32 bytes (RFC 7518 §3.2 floor); SHA-1 and
25
+ * `alg: "none"`-style escapes do not exist.
26
+ *
27
+ * Supported algorithms map 1:1 onto the RFC 9421 HTTP Signature Algorithms
28
+ * registry: `hmac-sha256`, `ed25519`, `ecdsa-p256-sha256`, `ecdsa-p384-sha384`,
29
+ * `rsa-pss-sha512`, and `rsa-v1_5-sha256`.
30
+ *
31
+ * @module
32
+ * @since 0.37.0
33
+ */
34
+ import type { Hooks } from "./types.js";
35
+ /**
36
+ * HTTP Signature algorithm identifiers from the RFC 9421 registry that this
37
+ * module can sign and verify with.
38
+ *
39
+ * @since 0.37.0
40
+ */
41
+ export type HttpSignatureAlgorithm = "hmac-sha256" | "ed25519" | "ecdsa-p256-sha256" | "ecdsa-p384-sha384" | "rsa-pss-sha512" | "rsa-v1_5-sha256";
42
+ /** Key material accepted by the signer/verifier. */
43
+ export type HttpSignatureKeyMaterial = CryptoKey | Uint8Array | JsonWebKey;
44
+ /**
45
+ * A resolved verification key, optionally pinning the algorithm it may be used
46
+ * with. Returning the `alg` from {@link VerifyMessageOptions.resolveKey}
47
+ * defeats algorithm-confusion: the signature's declared `alg` must then match.
48
+ *
49
+ * @since 0.37.0
50
+ */
51
+ export interface HttpSignatureKey {
52
+ /** Algorithm this key is bound to. When set, the message `alg` must match. */
53
+ alg?: HttpSignatureAlgorithm;
54
+ /** Raw secret (HMAC), `CryptoKey`, or JWK. */
55
+ key: HttpSignatureKeyMaterial;
56
+ }
57
+ /** Default signature label used when the caller does not supply one. */
58
+ export declare const DEFAULT_SIGNATURE_LABEL = "sig1";
59
+ /**
60
+ * Default maximum age (seconds) a signature's `created` timestamp may have
61
+ * before the verifier rejects it as stale. Mirrors the webhook-HMAC and
62
+ * Standard-Webhooks five-minute convention.
63
+ *
64
+ * @since 0.37.0
65
+ */
66
+ export declare const DEFAULT_MAX_SIGNATURE_AGE_SECONDS = 300;
67
+ /** Default clock-skew tolerance (seconds) for future `created` / `expires`. */
68
+ export declare const DEFAULT_SIGNATURE_CLOCK_SKEW_SECONDS = 60;
69
+ /**
70
+ * Options for {@link signMessage}. Describes the message to cover and the key
71
+ * to sign with.
72
+ *
73
+ * @since 0.37.0
74
+ */
75
+ export interface SignMessageOptions {
76
+ /** HTTP method (e.g. `"POST"`). */
77
+ method: string;
78
+ /** Absolute request URL. */
79
+ url: string | URL;
80
+ /** Request headers referenced by header components. */
81
+ headers?: HeadersInit;
82
+ /** Response status code (only when signing a response, for `@status`). */
83
+ status?: number;
84
+ /**
85
+ * Covered component identifiers, in order. Derived components start with `@`
86
+ * (`@method`, `@target-uri`, `@authority`, `@scheme`, `@request-target`,
87
+ * `@path`, `@query`, `@query-param;name="…"`, `@status`); everything else is
88
+ * a lowercased HTTP header name. Defaults to `["@method", "@target-uri"]`.
89
+ */
90
+ components?: string[];
91
+ /** Algorithm to sign with. */
92
+ alg: HttpSignatureAlgorithm;
93
+ /** Signing key (HMAC secret, `CryptoKey`, or JWK). */
94
+ key: HttpSignatureKeyMaterial;
95
+ /** Key identifier surfaced as the `keyid` parameter (recommended). */
96
+ keyid?: string;
97
+ /** Signature label in the dictionary. Defaults to {@link DEFAULT_SIGNATURE_LABEL}. */
98
+ label?: string;
99
+ /** `created` timestamp (Unix seconds). Defaults to the current time. */
100
+ created?: number;
101
+ /** Optional `expires` timestamp (Unix seconds). */
102
+ expires?: number;
103
+ /** Optional `nonce` for replay defense. */
104
+ nonce?: string;
105
+ /** Optional `tag` (application-specific signature label). */
106
+ tag?: string;
107
+ /** Whether to emit the `alg` parameter. Defaults to `true`. */
108
+ includeAlg?: boolean;
109
+ /** Clock used to default `created`. Returns milliseconds. Defaults to `Date.now`. */
110
+ now?: () => number;
111
+ }
112
+ /**
113
+ * Computed `Signature` / `Signature-Input` header values plus the signature
114
+ * base that was signed (useful for debugging / interop testing).
115
+ *
116
+ * @since 0.37.0
117
+ */
118
+ export interface MessageSignature {
119
+ /** Value for the `Signature-Input` header. */
120
+ signatureInput: string;
121
+ /** Value for the `Signature` header. */
122
+ signature: string;
123
+ /** The exact UTF-8 signature base that was signed. */
124
+ signatureBase: string;
125
+ /** The label used in the dictionary. */
126
+ label: string;
127
+ }
128
+ /**
129
+ * Compute HTTP Message Signature header values (RFC 9421) over the described
130
+ * message.
131
+ *
132
+ * @throws {TypeError} for unsupported algorithms, weak HMAC keys, or
133
+ * unserializable parameter values.
134
+ * @throws {Error} when a covered component cannot be resolved (e.g. a covered
135
+ * header is missing) or WebCrypto is unavailable.
136
+ * @since 0.37.0
137
+ */
138
+ export declare function signMessage(opts: SignMessageOptions): Promise<MessageSignature>;
139
+ /**
140
+ * Options for {@link signRequest}; the method, URL, and headers are taken from
141
+ * the {@link Request} itself.
142
+ *
143
+ * @since 0.37.0
144
+ */
145
+ export type SignRequestOptions = Omit<SignMessageOptions, "method" | "url" | "headers" | "status">;
146
+ /**
147
+ * Sign an outbound {@link Request} and return a new `Request` with the
148
+ * `Signature` and `Signature-Input` headers attached. The original request is
149
+ * not mutated.
150
+ *
151
+ * @since 0.37.0
152
+ */
153
+ export declare function signRequest(request: Request, opts: SignRequestOptions): Promise<Request>;
154
+ /** Information passed to {@link VerifyMessageOptions.resolveKey}. */
155
+ export interface KeyResolutionInfo {
156
+ /** The `keyid` parameter, if the signer included one. */
157
+ keyid?: string;
158
+ /** The declared `alg` parameter, if present. */
159
+ alg?: HttpSignatureAlgorithm;
160
+ /** The signature label being verified. */
161
+ label: string;
162
+ /** The `tag` parameter, if present. */
163
+ tag?: string;
164
+ }
165
+ /** Successful verification result. */
166
+ export interface VerifySuccess {
167
+ valid: true;
168
+ /** The verified signature label. */
169
+ label: string;
170
+ /** Algorithm used. */
171
+ alg: HttpSignatureAlgorithm;
172
+ /** The `keyid` parameter, if present. */
173
+ keyid?: string;
174
+ /** Covered component identifiers (serialized form). */
175
+ components: string[];
176
+ /** `created` timestamp (Unix seconds), if present. */
177
+ created?: number;
178
+ /** `expires` timestamp (Unix seconds), if present. */
179
+ expires?: number;
180
+ /** `nonce` parameter, if present. */
181
+ nonce?: string;
182
+ /** `tag` parameter, if present. */
183
+ tag?: string;
184
+ }
185
+ /** Failed verification result. Never throws on a bad signature. */
186
+ export interface VerifyFailure {
187
+ valid: false;
188
+ /** Stable machine-readable reason code. */
189
+ reason: string;
190
+ }
191
+ /** Result of {@link verifyMessage} / {@link verifyRequest}. */
192
+ export type VerifyResult = VerifySuccess | VerifyFailure;
193
+ /**
194
+ * Options for {@link verifyMessage}. The verifier is secure-by-default: it
195
+ * requires an explicit {@link algorithms} allowlist and a {@link resolveKey}
196
+ * callback.
197
+ *
198
+ * @since 0.37.0
199
+ */
200
+ export interface VerifyMessageOptions {
201
+ /** HTTP method of the received message. */
202
+ method: string;
203
+ /** Absolute URL of the received message. */
204
+ url: string | URL;
205
+ /** Headers of the received message (must include `Signature` / `Signature-Input`). */
206
+ headers: HeadersInit;
207
+ /** Response status code (only when verifying a response). */
208
+ status?: number;
209
+ /** Allowed algorithms. Required — there is no implicit "accept any" mode. */
210
+ algorithms: HttpSignatureAlgorithm[];
211
+ /**
212
+ * Resolve the verification key for a signature. Returning an
213
+ * {@link HttpSignatureKey} with `alg` pins the algorithm (defeats
214
+ * algorithm-confusion). Return `undefined` to reject an unknown key.
215
+ */
216
+ resolveKey: (info: KeyResolutionInfo) => HttpSignatureKey | HttpSignatureKeyMaterial | undefined | Promise<HttpSignatureKey | HttpSignatureKeyMaterial | undefined>;
217
+ /** Which signature label to verify. Defaults to the sole present label. */
218
+ label?: string;
219
+ /**
220
+ * Component identifiers that MUST be covered. Defaults to
221
+ * `["@method", "@path"]`. Pass `[]` to disable the check (not recommended).
222
+ */
223
+ requiredComponents?: string[];
224
+ /** Require the `created` parameter. Defaults to `true`. */
225
+ requireCreated?: boolean;
226
+ /** Maximum `created` age in seconds. Defaults to {@link DEFAULT_MAX_SIGNATURE_AGE_SECONDS}. */
227
+ maxAgeSeconds?: number;
228
+ /** Clock-skew tolerance in seconds. Defaults to {@link DEFAULT_SIGNATURE_CLOCK_SKEW_SECONDS}. */
229
+ toleranceSeconds?: number;
230
+ /** Require this exact `tag` parameter value. */
231
+ requiredTag?: string;
232
+ /**
233
+ * Replay check. When provided, a `nonce` is required and the signature is
234
+ * rejected if this returns `true`.
235
+ */
236
+ isReplay?: (nonce: string, info: KeyResolutionInfo) => boolean | Promise<boolean>;
237
+ /** Clock used for age checks. Returns milliseconds. Defaults to `Date.now`. */
238
+ now?: () => number;
239
+ }
240
+ /**
241
+ * Verify an HTTP Message Signature (RFC 9421) on a received message. Returns a
242
+ * structured result and never throws on a bad/forged signature — only on a
243
+ * programming error (e.g. WebCrypto unavailable).
244
+ *
245
+ * @since 0.37.0
246
+ */
247
+ export declare function verifyMessage(opts: VerifyMessageOptions): Promise<VerifyResult>;
248
+ /**
249
+ * Verify an HTTP Message Signature on an inbound {@link Request}. Thin wrapper
250
+ * over {@link verifyMessage} that pulls the method, URL, and headers from the
251
+ * request.
252
+ *
253
+ * @since 0.37.0
254
+ */
255
+ export declare function verifyRequest(request: Request, opts: Omit<VerifyMessageOptions, "method" | "url" | "headers" | "status">): Promise<VerifyResult>;
256
+ /**
257
+ * Options for {@link httpSignatureAuth}. Extends {@link VerifyMessageOptions}
258
+ * minus the per-message fields (which come from the request).
259
+ *
260
+ * @since 0.37.0
261
+ */
262
+ export interface HttpSignatureAuthOptions extends Omit<VerifyMessageOptions, "method" | "url" | "headers" | "status"> {
263
+ /** Detail surfaced on the 401 problem+json response. */
264
+ message?: string;
265
+ /** `ctx.state` key the {@link VerifySuccess} is stamped on. Default `"httpSignature"`. */
266
+ stateKey?: string;
267
+ /**
268
+ * When `true`, requests **without** any `Signature` header pass through
269
+ * unauthenticated (a present-but-invalid signature is still rejected).
270
+ * Defaults to `false` — signatures are mandatory.
271
+ */
272
+ optional?: boolean;
273
+ }
274
+ /**
275
+ * Middleware that enforces a valid RFC 9421 HTTP Message Signature on inbound
276
+ * requests. On success the {@link VerifySuccess} is stamped on `ctx.state`; on
277
+ * a missing (unless `optional`) or invalid signature it throws
278
+ * {@link UnauthorizedError} (`401` + `Cache-Control: no-store`).
279
+ *
280
+ * @since 0.37.0
281
+ */
282
+ export declare function httpSignatureAuth(opts: HttpSignatureAuthOptions): Hooks;
283
+ /** Hash algorithms supported for {@link contentDigest}. */
284
+ export type ContentDigestAlgorithm = "sha-256" | "sha-512";
285
+ /**
286
+ * Compute an RFC 9530 `Content-Digest` header value over `body`, e.g.
287
+ * `sha-256=:<base64>:`. Pair it with a `content-digest` covered component to
288
+ * bind the request body into the signature, then re-check it against the
289
+ * received body with {@link verifyContentDigest}.
290
+ *
291
+ * @since 0.37.0
292
+ */
293
+ export declare function contentDigest(body: Uint8Array | string, opts?: {
294
+ algorithm?: ContentDigestAlgorithm;
295
+ }): Promise<string>;
296
+ /**
297
+ * Verify that an RFC 9530 `Content-Digest` header value matches `body`.
298
+ * Returns `false` for any malformed header or mismatch (never throws on bad
299
+ * input). Only `sha-256` / `sha-512` members are considered.
300
+ *
301
+ * @since 0.37.0
302
+ */
303
+ export declare function verifyContentDigest(header: string, body: Uint8Array | string): Promise<boolean>;