@blamejs/core 0.8.43 → 0.8.49
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 +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
|
@@ -34,6 +34,40 @@ var OpenApiError = defineClass("OpenApiError", { alwaysPermanent: true });
|
|
|
34
34
|
var openapiYaml = lazyRequire(function () { return require("../openapi-yaml"); });
|
|
35
35
|
var audit = lazyRequire(function () { return require("../audit"); });
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @primitive b.middleware.openapiServe
|
|
39
|
+
* @signature b.middleware.openapiServe(opts)
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
* @related b.middleware.asyncapiServe, b.openapi.create
|
|
42
|
+
*
|
|
43
|
+
* Serves an OpenAPI 3.1 document built via `b.openapi.create` at a
|
|
44
|
+
* configurable JSON + YAML mount point. GET/HEAD only; everything
|
|
45
|
+
* else falls through. SHA3-512 ETag enables conditional 304. With
|
|
46
|
+
* `accessControl: "public"` (default) emits
|
|
47
|
+
* `Access-Control-Allow-Origin: *` so external doc tooling can
|
|
48
|
+
* fetch; `same-origin` omits the CORS header for internal-only docs.
|
|
49
|
+
*
|
|
50
|
+
* @opts
|
|
51
|
+
* {
|
|
52
|
+
* document: object, // builder from b.openapi.create()
|
|
53
|
+
* pathJson: string, // default "/openapi.json"
|
|
54
|
+
* pathYaml: string, // default "/openapi.yaml"
|
|
55
|
+
* pretty: boolean,
|
|
56
|
+
* cacheControl: string, // default "public, max-age=300"
|
|
57
|
+
* accessControl: "public"|"same-origin"|{ allowOrigin: string },
|
|
58
|
+
* audit: boolean,
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* var b = require("@blamejs/core");
|
|
63
|
+
* var app = b.router.create();
|
|
64
|
+
* var doc = b.openapi.create({ title: "api", version: "1.0.0" });
|
|
65
|
+
* app.use(b.middleware.openapiServe({
|
|
66
|
+
* document: doc,
|
|
67
|
+
* pretty: true,
|
|
68
|
+
* cacheControl: "public, max-age=300",
|
|
69
|
+
* }));
|
|
70
|
+
*/
|
|
37
71
|
function create(opts) {
|
|
38
72
|
opts = opts || {};
|
|
39
73
|
validateOpts(opts, [
|
|
@@ -3,20 +3,27 @@
|
|
|
3
3
|
* Rate-limit middleware — pluggable backend, default in-memory.
|
|
4
4
|
*
|
|
5
5
|
* Per-IP by default; key extractor is configurable (per-user, per-API-key,
|
|
6
|
-
* per-route). Two built-in backends:
|
|
6
|
+
* per-route). Two built-in backends, two in-memory algorithms:
|
|
7
7
|
*
|
|
8
|
-
* - 'memory' (default) —
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* - 'memory' (default) — in-process counter. The `algorithm` opt
|
|
9
|
+
* selects the shape:
|
|
10
|
+
* 'token-bucket' (default) — each key gets `burst` tokens up
|
|
11
|
+
* front; tokens refill at `refillPerSecond`; each request
|
|
12
|
+
* costs 1 token. Smooths bursty traffic.
|
|
13
|
+
* 'fixed-window' — per-key counter resets at the start of each
|
|
14
|
+
* window (`windowMs`); allow up to `max` per window. Matches
|
|
15
|
+
* the cluster backend's algorithm without an SQL hop. Cheaper
|
|
16
|
+
* per request than token-bucket; tradeoff is the boundary
|
|
17
|
+
* burst at window edges (worst case 2*max in 1*windowMs).
|
|
11
18
|
*
|
|
12
19
|
* - 'cluster' — fixed-window counter shared across the cluster
|
|
13
20
|
* via `_blamejs_rate_limit_counters`. Atomic INSERT...ON CONFLICT
|
|
14
21
|
* increments per key within a window and rolls over when the
|
|
15
22
|
* window advances. Multi-process / multi-node accurate.
|
|
16
23
|
*
|
|
17
|
-
* Cluster opt-in
|
|
18
|
-
*
|
|
19
|
-
* the
|
|
24
|
+
* Cluster opt-in implies fixed-window because that's what models
|
|
25
|
+
* cleanly in SQL — the operator-facing config keys for the
|
|
26
|
+
* cluster backend match the fixed-window memory shape.
|
|
20
27
|
*
|
|
21
28
|
* Operators can also pass a custom `{ take, reset }` object as the
|
|
22
29
|
* backend for Redis / Memcached / etc.
|
|
@@ -30,15 +37,21 @@
|
|
|
30
37
|
* skipPaths: [] // string-prefix or regex matchers
|
|
31
38
|
* scope: 'global' | 'per-route' (default 'global')
|
|
32
39
|
* backend: 'memory' (default) | 'cluster' | { take, reset, gc? }
|
|
40
|
+
* algorithm: 'token-bucket' (default) | 'fixed-window'
|
|
41
|
+
* // memory backend only; ignored
|
|
42
|
+
* // for cluster backend (which is
|
|
43
|
+
* // always fixed-window) and custom
|
|
44
|
+
* // backend objects (operator decides)
|
|
33
45
|
*
|
|
34
|
-
* // Memory
|
|
46
|
+
* // Memory backend, token-bucket algorithm:
|
|
35
47
|
* burst: 60 // initial token bucket size
|
|
36
48
|
* refillPerSecond: 10 // sustained throughput
|
|
37
49
|
*
|
|
38
|
-
* //
|
|
39
|
-
*
|
|
50
|
+
* // Memory backend, fixed-window algorithm + cluster backend:
|
|
51
|
+
* max: 60 // max requests per window (memory only)
|
|
52
|
+
* limit: 60 // alias of `max` (cluster backend uses this name)
|
|
40
53
|
* windowMs: C.TIME.minutes(1) // window duration
|
|
41
|
-
* pruneIntervalMs: C.TIME.minutes(5) // how often the leader prunes expired rows
|
|
54
|
+
* pruneIntervalMs: C.TIME.minutes(5) // cluster: how often the leader prunes expired rows
|
|
42
55
|
* }
|
|
43
56
|
*
|
|
44
57
|
* Audit: every limit hit emits system.ratelimit.block with the key + path.
|
|
@@ -74,9 +87,25 @@ function _requirePositiveNumber(name, value) {
|
|
|
74
87
|
}
|
|
75
88
|
}
|
|
76
89
|
|
|
77
|
-
// ---- Memory backend
|
|
90
|
+
// ---- Memory backend ----
|
|
91
|
+
//
|
|
92
|
+
// `algorithm: "token-bucket"` (default) — smoothed throughput.
|
|
93
|
+
// `algorithm: "fixed-window"` — per-key counter resets at the start of
|
|
94
|
+
// each window. Boundary-burst tradeoff
|
|
95
|
+
// in exchange for matching the cluster
|
|
96
|
+
// backend's shape without an SQL hop.
|
|
78
97
|
|
|
79
98
|
function _memoryBackend(opts) {
|
|
99
|
+
var algorithm = opts.algorithm || "token-bucket";
|
|
100
|
+
if (algorithm !== "token-bucket" && algorithm !== "fixed-window") {
|
|
101
|
+
throw new Error("middleware.rateLimit: algorithm must be 'token-bucket' or 'fixed-window', got " +
|
|
102
|
+
JSON.stringify(algorithm));
|
|
103
|
+
}
|
|
104
|
+
if (algorithm === "fixed-window") return _memoryFixedWindowBackend(opts);
|
|
105
|
+
return _memoryTokenBucketBackend(opts);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function _memoryTokenBucketBackend(opts) {
|
|
80
109
|
// Default 1-per-second tokens fully refilled in 1 minute = 60 tokens.
|
|
81
110
|
var burst = opts.burst != null ? opts.burst : C.TIME.minutes(1) / C.TIME.seconds(1);
|
|
82
111
|
var refillPerSecond = opts.refillPerSecond != null ? opts.refillPerSecond : 10;
|
|
@@ -138,6 +167,71 @@ function _memoryBackend(opts) {
|
|
|
138
167
|
return { take: take, reset: reset, close: close };
|
|
139
168
|
}
|
|
140
169
|
|
|
170
|
+
// Fixed-window in-memory algorithm — per-key counter that resets at the
|
|
171
|
+
// start of each window. Same shape as the cluster backend but without
|
|
172
|
+
// the SQL hop, so single-process apps that want fixed-window semantics
|
|
173
|
+
// (e.g. matching a cluster-backend deploy in dev) avoid setting up a DB.
|
|
174
|
+
function _memoryFixedWindowBackend(opts) {
|
|
175
|
+
// `max` is the memory-fixed-window operator-facing name. `limit` is
|
|
176
|
+
// accepted as an alias so a config can switch from the cluster
|
|
177
|
+
// backend to memory + fixed-window without renaming opts.
|
|
178
|
+
var max = opts.max != null ? opts.max
|
|
179
|
+
: opts.limit != null ? opts.limit
|
|
180
|
+
: C.TIME.minutes(1) / C.TIME.seconds(1);
|
|
181
|
+
var windowMs = opts.windowMs != null ? opts.windowMs : C.TIME.minutes(1);
|
|
182
|
+
_requirePositiveNumber("max", max);
|
|
183
|
+
_requirePositiveNumber("windowMs", windowMs);
|
|
184
|
+
|
|
185
|
+
var counters = new Map();
|
|
186
|
+
|
|
187
|
+
// Periodic GC of stale counters so the map doesn't grow unbounded.
|
|
188
|
+
var gcInterval = safeAsync.repeating(function () {
|
|
189
|
+
var now = Date.now();
|
|
190
|
+
for (var k of counters.keys()) {
|
|
191
|
+
var c = counters.get(k);
|
|
192
|
+
if (c.windowStart + windowMs * 2 < now) counters.delete(k);
|
|
193
|
+
}
|
|
194
|
+
}, C.TIME.minutes(5), { name: "rate-limit-fixed-window-gc" });
|
|
195
|
+
|
|
196
|
+
// Synchronous take — same hot-path shape as the token-bucket backend
|
|
197
|
+
// so the middleware doesn't pay a microtask cost when memory-fixed
|
|
198
|
+
// is selected.
|
|
199
|
+
function take(key, _cost) {
|
|
200
|
+
var now = Date.now();
|
|
201
|
+
var windowStart = Math.floor(now / windowMs) * windowMs;
|
|
202
|
+
var c = counters.get(key);
|
|
203
|
+
if (!c || c.windowStart !== windowStart) {
|
|
204
|
+
c = { windowStart: windowStart, count: 0 };
|
|
205
|
+
counters.set(key, c);
|
|
206
|
+
}
|
|
207
|
+
c.count += 1;
|
|
208
|
+
if (c.count <= max) {
|
|
209
|
+
return {
|
|
210
|
+
allowed: true,
|
|
211
|
+
limit: max,
|
|
212
|
+
remaining: Math.max(0, max - c.count),
|
|
213
|
+
retryAfter: 0,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
var retryMs = (windowStart + windowMs) - now;
|
|
217
|
+
return {
|
|
218
|
+
allowed: false,
|
|
219
|
+
limit: max,
|
|
220
|
+
remaining: 0,
|
|
221
|
+
retryAfter: Math.max(1, Math.ceil(retryMs / C.TIME.seconds(1))),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function reset(key) { counters.delete(key); }
|
|
226
|
+
|
|
227
|
+
function close() {
|
|
228
|
+
try { gcInterval.stop(); } catch (_e) { /* timer already stopped */ }
|
|
229
|
+
counters.clear();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { take: take, reset: reset, close: close };
|
|
233
|
+
}
|
|
234
|
+
|
|
141
235
|
// ---- Cluster backend (fixed-window counter, SQL-backed) ----
|
|
142
236
|
|
|
143
237
|
function _clusterBackend(opts) {
|
|
@@ -241,15 +335,61 @@ function _resolveBackend(opts) {
|
|
|
241
335
|
"' (must be 'memory', 'cluster', or { take, reset })");
|
|
242
336
|
}
|
|
243
337
|
|
|
338
|
+
/**
|
|
339
|
+
* @primitive b.middleware.rateLimit
|
|
340
|
+
* @signature b.middleware.rateLimit(req, res, next)
|
|
341
|
+
* @since 0.1.0
|
|
342
|
+
* @related b.middleware.dailyByteQuota, b.middleware.botGuard
|
|
343
|
+
*
|
|
344
|
+
* Pluggable-backend rate limiter. Constructed via
|
|
345
|
+
* `b.middleware.rateLimit(opts)`; the resulting middleware has the
|
|
346
|
+
* `(req, res, next)` shape shown above. Default `memory` backend offers
|
|
347
|
+
* `token-bucket` (smooths bursts) and `fixed-window` algorithms;
|
|
348
|
+
* `cluster` backend uses `_blamejs_rate_limit_counters` for
|
|
349
|
+
* multi-node accurate fixed-window counts. Operators bring their
|
|
350
|
+
* own `{ take, reset }` for Redis / Memcached. Per-IP by default;
|
|
351
|
+
* `keyFn(req)` overrides for per-user / per-API-key / per-route.
|
|
352
|
+
* Refuses with HTTP 429 + `X-RateLimit-*` headers and emits
|
|
353
|
+
* `system.ratelimit.block` audit on every hit.
|
|
354
|
+
*
|
|
355
|
+
* @opts
|
|
356
|
+
* {
|
|
357
|
+
* keyFn: function(req): string,
|
|
358
|
+
* statusOnLimit: number, // default 429
|
|
359
|
+
* bodyOnLimit: string, // default "Too Many Requests"
|
|
360
|
+
* header: boolean, // default true
|
|
361
|
+
* skipPaths: Array<string|RegExp>,
|
|
362
|
+
* scope: "global"|"per-route",
|
|
363
|
+
* backend: "memory"|"cluster"|{ take, reset, gc },
|
|
364
|
+
* algorithm: "token-bucket"|"fixed-window",
|
|
365
|
+
* burst: number,
|
|
366
|
+
* refillPerSecond: number,
|
|
367
|
+
* max: number,
|
|
368
|
+
* limit: number,
|
|
369
|
+
* windowMs: number,
|
|
370
|
+
* pruneIntervalMs: number,
|
|
371
|
+
* trustProxy: boolean|number,
|
|
372
|
+
* }
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* var b = require("@blamejs/core");
|
|
376
|
+
* var app = b.router.create();
|
|
377
|
+
* app.use(b.middleware.rateLimit({
|
|
378
|
+
* backend: "memory",
|
|
379
|
+
* algorithm: "token-bucket",
|
|
380
|
+
* burst: 60,
|
|
381
|
+
* refillPerSecond: 10,
|
|
382
|
+
* }));
|
|
383
|
+
*/
|
|
244
384
|
function create(opts) {
|
|
245
385
|
opts = opts || {};
|
|
246
386
|
validateOpts(opts, [
|
|
247
387
|
"keyFn", "statusOnLimit", "bodyOnLimit", "header", "skipPaths", "scope",
|
|
248
|
-
"backend", "trustProxy",
|
|
249
|
-
// memory backend
|
|
388
|
+
"backend", "trustProxy", "algorithm",
|
|
389
|
+
// memory backend (token-bucket)
|
|
250
390
|
"burst", "refillPerSecond",
|
|
251
|
-
// cluster backend
|
|
252
|
-
"limit", "windowMs", "pruneIntervalMs",
|
|
391
|
+
// memory backend (fixed-window) + cluster backend
|
|
392
|
+
"max", "limit", "windowMs", "pruneIntervalMs",
|
|
253
393
|
], "middleware.rateLimit");
|
|
254
394
|
var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
255
395
|
? opts.trustProxy : false;
|
|
@@ -353,6 +493,8 @@ function create(opts) {
|
|
|
353
493
|
module.exports = {
|
|
354
494
|
create: create,
|
|
355
495
|
// Backends exported for tests + advanced operator wiring.
|
|
356
|
-
_memoryBackend:
|
|
357
|
-
|
|
496
|
+
_memoryBackend: _memoryBackend,
|
|
497
|
+
_memoryTokenBucketBackend: _memoryTokenBucketBackend,
|
|
498
|
+
_memoryFixedWindowBackend: _memoryFixedWindowBackend,
|
|
499
|
+
_clusterBackend: _clusterBackend,
|
|
358
500
|
};
|
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* Request-ID middleware. Propagates an existing X-Request-Id header
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* correlate.
|
|
8
|
-
*
|
|
9
|
-
* Threading the request ID into audit.record() metadata is what makes
|
|
10
|
-
* the cross-event correlation traceable; apps should pass this through
|
|
11
|
-
* to every audit.record() they call within the request lifecycle.
|
|
12
|
-
*
|
|
13
|
-
* Options:
|
|
14
|
-
* {
|
|
15
|
-
* headerName: 'X-Request-Id'
|
|
16
|
-
* trustUpstream: true // propagate upstream id if it matches
|
|
17
|
-
* // the format check; false → always
|
|
18
|
-
* // generate fresh
|
|
19
|
-
* formatRegex: /^[A-Za-z0-9._-]{8,128}$/
|
|
20
|
-
* }
|
|
3
|
+
* Request-ID middleware. Propagates an existing X-Request-Id header
|
|
4
|
+
* when present and well-formed; otherwise generates a fresh 32-hex
|
|
5
|
+
* value. Sets req.requestId AND emits the same value as a response
|
|
6
|
+
* header so downstream services + auditors can correlate.
|
|
21
7
|
*/
|
|
22
8
|
var C = require("../constants");
|
|
23
9
|
var { generateToken } = require("../crypto");
|
|
@@ -30,6 +16,38 @@ var DEFAULT_FORMAT = /^[A-Za-z0-9._-]{8,128}$/;
|
|
|
30
16
|
// can't drive ReDoS even against a careless operator pattern.
|
|
31
17
|
var MAX_INBOUND_LEN = C.BYTES.bytes(256);
|
|
32
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @primitive b.middleware.requestId
|
|
21
|
+
* @signature b.middleware.requestId(req, res, next)
|
|
22
|
+
* @since 0.1.0
|
|
23
|
+
* @related b.middleware.requestLog, b.middleware.traceLogCorrelation
|
|
24
|
+
*
|
|
25
|
+
* Sets a stable correlation id on every request. Constructed via
|
|
26
|
+
* the factory call `b.middleware.requestId(opts)`; the resulting
|
|
27
|
+
* middleware has the `(req, res, next)` shape shown above.
|
|
28
|
+
* Propagates a trusted inbound `X-Request-Id` (or operator-named
|
|
29
|
+
* header) when it matches the format regex; otherwise generates a
|
|
30
|
+
* fresh 16-byte hex token. The id lands on `req.requestId` and on
|
|
31
|
+
* the response header so downstream services + the framework's
|
|
32
|
+
* audit log can correlate the request across hops. Mount FIRST in
|
|
33
|
+
* the chain — every later primitive expects `req.requestId` to
|
|
34
|
+
* be present for log lines and audit-record metadata.
|
|
35
|
+
*
|
|
36
|
+
* @opts
|
|
37
|
+
* {
|
|
38
|
+
* headerName: string, // default "X-Request-Id"
|
|
39
|
+
* trustUpstream: boolean, // default true; false → always re-mint
|
|
40
|
+
* formatRegex: RegExp, // default /^[A-Za-z0-9._-]{8,128}$/
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* var b = require("@blamejs/core");
|
|
45
|
+
* var app = b.router.create();
|
|
46
|
+
* app.use(b.middleware.requestId({ trustUpstream: true }));
|
|
47
|
+
* app.get("/health", function (req, res) {
|
|
48
|
+
* res.end(req.requestId);
|
|
49
|
+
* });
|
|
50
|
+
*/
|
|
33
51
|
function create(opts) {
|
|
34
52
|
opts = opts || {};
|
|
35
53
|
validateOpts(opts, [
|
|
@@ -43,6 +43,43 @@ function _defaultLevel(status) {
|
|
|
43
43
|
return "info";
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* @primitive b.middleware.requestLog
|
|
48
|
+
* @signature b.middleware.requestLog(req, res, next)
|
|
49
|
+
* @since 0.1.0
|
|
50
|
+
* @related b.middleware.requestId, b.middleware.traceLogCorrelation
|
|
51
|
+
*
|
|
52
|
+
* HTTP access-log middleware. Constructed via
|
|
53
|
+
* `b.middleware.requestLog(opts)`; the resulting middleware has the
|
|
54
|
+
* `(req, res, next)` shape shown above. Emits one structured log entry per
|
|
55
|
+
* request via the operator-supplied `b.log` instance, capturing
|
|
56
|
+
* method / path / status / durationMs / bytes / actorIp / userAgent
|
|
57
|
+
* / requestId. Reads the final status via
|
|
58
|
+
* `b.requestHelpers.captureResponseStatus` so handlers using any
|
|
59
|
+
* shape (`writeHead` / `statusCode =` / fluent `status(...).send`)
|
|
60
|
+
* report correctly. `levelFn(status)` defaults to 5xx=error,
|
|
61
|
+
* 4xx=warn, else info; pass a string `level` or custom function for
|
|
62
|
+
* different policies. `trustProxy` gates `X-Forwarded-For`
|
|
63
|
+
* consumption.
|
|
64
|
+
*
|
|
65
|
+
* @opts
|
|
66
|
+
* {
|
|
67
|
+
* logger: object, // required b.log instance
|
|
68
|
+
* skipPaths: Array<string|RegExp>,
|
|
69
|
+
* trustProxy: boolean|number,
|
|
70
|
+
* level: string,
|
|
71
|
+
* levelFn: function(status): string,
|
|
72
|
+
* fields: string[],
|
|
73
|
+
* }
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* var b = require("@blamejs/core");
|
|
77
|
+
* var app = b.router.create();
|
|
78
|
+
* app.use(b.middleware.requestLog({
|
|
79
|
+
* logger: b.log.boot("http"),
|
|
80
|
+
* skipPaths: ["/healthz"],
|
|
81
|
+
* }));
|
|
82
|
+
*/
|
|
46
83
|
function create(opts) {
|
|
47
84
|
opts = opts || {};
|
|
48
85
|
validateOpts(opts, [
|
|
@@ -44,6 +44,35 @@ function _writeUnauthorized(res, requiredBand, actualBand, realm) {
|
|
|
44
44
|
res.end(body);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* @primitive b.middleware.requireAal
|
|
49
|
+
* @signature b.middleware.requireAal(opts)
|
|
50
|
+
* @since 0.1.0
|
|
51
|
+
* @related b.middleware.requireStepUp, b.middleware.requireAuth
|
|
52
|
+
*
|
|
53
|
+
* Gates routes by NIST SP 800-63-4 Authenticator Assurance Level
|
|
54
|
+
* (AAL1 / AAL2 / AAL3). Reads the actual band from `req.user.aal`
|
|
55
|
+
* by default; operators with a different shape pass `getAal(req)`.
|
|
56
|
+
* Refuses below-minimum requests with HTTP 401 +
|
|
57
|
+
* `WWW-Authenticate: AAL-StepUp realm="<X>", required="<minimum>"`
|
|
58
|
+
* — the bespoke scheme name signals the frontend to trigger a
|
|
59
|
+
* step-up flow (re-prompt for TOTP / passkey). Throws at create()
|
|
60
|
+
* on an invalid `minimum` band. Emits `auth.aal.granted` /
|
|
61
|
+
* `auth.aal.denied` audit events.
|
|
62
|
+
*
|
|
63
|
+
* @opts
|
|
64
|
+
* {
|
|
65
|
+
* minimum: "AAL1"|"AAL2"|"AAL3", // required
|
|
66
|
+
* getAal: function(req): string,
|
|
67
|
+
* realm: string,
|
|
68
|
+
* audit: boolean, // default true
|
|
69
|
+
* }
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* var b = require("@blamejs/core");
|
|
73
|
+
* var app = b.router.create();
|
|
74
|
+
* app.use("/admin", b.middleware.requireAal({ minimum: "AAL2" }));
|
|
75
|
+
*/
|
|
47
76
|
function create(opts) {
|
|
48
77
|
opts = opts || {};
|
|
49
78
|
validateOpts(opts, [
|
|
@@ -47,6 +47,38 @@ function _defaultPrefersJson(req) {
|
|
|
47
47
|
return false;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @primitive b.middleware.requireAuth
|
|
52
|
+
* @signature b.middleware.requireAuth(req, res, next)
|
|
53
|
+
* @since 0.1.0
|
|
54
|
+
* @related b.middleware.attachUser, b.middleware.bearerAuth, b.middleware.requireAal
|
|
55
|
+
*
|
|
56
|
+
* Gates routes that require an authenticated user. Constructed via
|
|
57
|
+
* `b.middleware.requireAuth(opts)`; the resulting middleware has
|
|
58
|
+
* the `(req, res, next)` shape shown above. Mount AFTER
|
|
59
|
+
* `attachUser`; this middleware reads `req.user` and either passes
|
|
60
|
+
* the request or rejects. JSON-preferring callers (Accept includes
|
|
61
|
+
* `application/json` or `X-Requested-With: XMLHttpRequest`) get 401
|
|
62
|
+
* `application/json`; browser-preferring with `redirectTo` get 302
|
|
63
|
+
* Location; otherwise 401 `text/plain`. The REQUEST Content-Type
|
|
64
|
+
* is intentionally NOT a signal — what the client SENT is not
|
|
65
|
+
* what they want BACK. Always emits `auth.required.denied` audit
|
|
66
|
+
* (method + path + client IP, no body content).
|
|
67
|
+
*
|
|
68
|
+
* @opts
|
|
69
|
+
* {
|
|
70
|
+
* redirectTo: string, // 302 location for browser
|
|
71
|
+
* prefersJson: function(req): boolean,
|
|
72
|
+
* errorMessage: string, // default "Authentication required."
|
|
73
|
+
* audit: boolean, // default true
|
|
74
|
+
* }
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* var b = require("@blamejs/core");
|
|
78
|
+
* var app = b.router.create();
|
|
79
|
+
* app.use(b.middleware.attachUser({ userLoader: async function () { return { id: 1 }; } }));
|
|
80
|
+
* app.use(b.middleware.requireAuth({ redirectTo: "/login" }));
|
|
81
|
+
*/
|
|
50
82
|
function create(opts) {
|
|
51
83
|
opts = opts || {};
|
|
52
84
|
validateOpts(opts, [
|
|
@@ -70,6 +70,47 @@ function _timingSafeStringEqual(a, b) {
|
|
|
70
70
|
return crypto().timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @primitive b.middleware.requireBoundKey
|
|
75
|
+
* @signature b.middleware.requireBoundKey(opts)
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @related b.middleware.bearerAuth, b.middleware.requireMtls
|
|
78
|
+
*
|
|
79
|
+
* Bearer-API-key auth with scope + bound-fields + peer-cert
|
|
80
|
+
* fingerprint binding. Covers the service-to-service /
|
|
81
|
+
* partner-webhook / CI-runner case where a stable API key is
|
|
82
|
+
* registered with `{ scopes, boundFields, peerCertFingerprints }`.
|
|
83
|
+
* The middleware verifies the inbound `Bearer` token, checks
|
|
84
|
+
* scopes against `requiredScopes`, pulls each bound field via
|
|
85
|
+
* the operator-supplied `getBoundField[name](req)` and compares to
|
|
86
|
+
* the registered value, and (when registered) compares the
|
|
87
|
+
* peer-cert fingerprint to the allowlist. Fails closed on resolver
|
|
88
|
+
* error / undefined return. Refuses with HTTP 401/403 + structured
|
|
89
|
+
* JSON identifying which check failed; audits the api-key id (not
|
|
90
|
+
* the secret) on every decision.
|
|
91
|
+
*
|
|
92
|
+
* @opts
|
|
93
|
+
* {
|
|
94
|
+
* resolver: async function(apiKey): { id, scopes, boundFields, peerCertFingerprints } | null, // required
|
|
95
|
+
* requiredScopes: string[],
|
|
96
|
+
* getBoundField: Record<string, function(req): string|null>,
|
|
97
|
+
* tolerateMissingPeerCert: boolean,
|
|
98
|
+
* errorMessage: string,
|
|
99
|
+
* auditAction: string,
|
|
100
|
+
* audit: object,
|
|
101
|
+
* }
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* var b = require("@blamejs/core");
|
|
105
|
+
* var app = b.router.create();
|
|
106
|
+
* app.post("/webhook", b.middleware.requireBoundKey({
|
|
107
|
+
* resolver: async function (apiKey) {
|
|
108
|
+
* if (apiKey === "valid-key") return { id: "k1", scopes: ["webhook.ingest"], boundFields: {} };
|
|
109
|
+
* return null;
|
|
110
|
+
* },
|
|
111
|
+
* requiredScopes: ["webhook.ingest"],
|
|
112
|
+
* }));
|
|
113
|
+
*/
|
|
73
114
|
function create(opts) {
|
|
74
115
|
opts = opts || {};
|
|
75
116
|
validateOpts(opts, [
|
|
@@ -39,6 +39,38 @@ function _normalizeAllowed(types) {
|
|
|
39
39
|
return out;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* @primitive b.middleware.requireContentType
|
|
44
|
+
* @signature b.middleware.requireContentType(allowed, opts)
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
* @related b.middleware.requireMethods, b.middleware.bodyParser
|
|
47
|
+
*
|
|
48
|
+
* Refuses requests with a body (POST/PUT/PATCH by default) whose
|
|
49
|
+
* `Content-Type` header isn't in the operator-supplied allowlist.
|
|
50
|
+
* Defends against MIME-type confusion: a route that processes JSON
|
|
51
|
+
* shouldn't accept `application/x-www-form-urlencoded` even if the
|
|
52
|
+
* body parses, and vice versa. Refuses with HTTP 415 + `Accept:`
|
|
53
|
+
* listing the allowed types per RFC 9110 §15.5.16, BEFORE the
|
|
54
|
+
* body parser runs. Idempotent verbs (GET / HEAD / DELETE /
|
|
55
|
+
* OPTIONS) bypass by default; operators with rare DELETE-with-body
|
|
56
|
+
* shapes pass `methods` to override. Throws on empty / non-array
|
|
57
|
+
* allowlist.
|
|
58
|
+
*
|
|
59
|
+
* @opts
|
|
60
|
+
* {
|
|
61
|
+
* methods: string[], // override default ["POST", "PUT", "PATCH"]
|
|
62
|
+
* audit: boolean, // default true
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* var b = require("@blamejs/core");
|
|
67
|
+
* var app = b.router.create();
|
|
68
|
+
* app.post("/api/echo",
|
|
69
|
+
* b.middleware.requireContentType(["application/json"]),
|
|
70
|
+
* b.middleware.bodyParser({ json: { limit: 1024 } }),
|
|
71
|
+
* function (req, res) { res.end(JSON.stringify(req.body)); }
|
|
72
|
+
* );
|
|
73
|
+
*/
|
|
42
74
|
function create(allowed, opts) {
|
|
43
75
|
var normalized = _normalizeAllowed(allowed);
|
|
44
76
|
if (!normalized) {
|
|
@@ -22,6 +22,33 @@ var RequireMethodsError = defineClass("RequireMethodsError", { alwaysPermanent:
|
|
|
22
22
|
|
|
23
23
|
var observability = lazyRequire(function () { return require("../observability"); });
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* @primitive b.middleware.requireMethods
|
|
27
|
+
* @signature b.middleware.requireMethods(allowed, opts)
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
* @related b.middleware.requireContentType
|
|
30
|
+
*
|
|
31
|
+
* Refuses HTTP methods outside the operator-supplied allowlist.
|
|
32
|
+
* Defends against unexpected verb routing — many CVE-class bugs
|
|
33
|
+
* trace to a route wired for GET that accidentally accepts arbitrary
|
|
34
|
+
* verbs (PROPFIND, OPTIONS, custom). Refuses with HTTP 405 +
|
|
35
|
+
* `Allow:` header listing the allowed methods per RFC 9110 §15.5.6.
|
|
36
|
+
* Throws at create-time on empty / non-array allowlist or methods
|
|
37
|
+
* containing whitespace/separator characters.
|
|
38
|
+
*
|
|
39
|
+
* @opts
|
|
40
|
+
* {
|
|
41
|
+
* audit: boolean, // default true
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* var b = require("@blamejs/core");
|
|
46
|
+
* var app = b.router.create();
|
|
47
|
+
* app.use("/api",
|
|
48
|
+
* b.middleware.requireMethods(["GET", "POST"]),
|
|
49
|
+
* function (req, res) { res.end("ok"); }
|
|
50
|
+
* );
|
|
51
|
+
*/
|
|
25
52
|
function create(allowed, opts) {
|
|
26
53
|
if (!Array.isArray(allowed) || allowed.length === 0) {
|
|
27
54
|
throw new RequireMethodsError("require-methods/no-allowlist",
|
|
@@ -63,6 +63,39 @@ function _normalizeFingerprintEntry(entry) {
|
|
|
63
63
|
return entry;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @primitive b.middleware.requireMtls
|
|
68
|
+
* @signature b.middleware.requireMtls(opts)
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
* @related b.middleware.requireBoundKey, b.middleware.bearerAuth
|
|
71
|
+
*
|
|
72
|
+
* Soft-enforcement gate for routes that require an authenticated
|
|
73
|
+
* client certificate. Refuses with HTTP 401 when no peer cert is
|
|
74
|
+
* presented, when the TLS layer marks `req.client.authorized ===
|
|
75
|
+
* false`, when the SHA3-512 fingerprint isn't on the operator
|
|
76
|
+
* allowlist, or when it appears on the denylist. Allowlist of
|
|
77
|
+
* null / empty means "any peer cert authorized at the TLS layer
|
|
78
|
+
* is fine"; non-empty allowlist additionally requires fingerprint
|
|
79
|
+
* match. Pair with `b.app({ tlsOptions: { requestCert: true, ca:
|
|
80
|
+
* [...] } })` so the TLS layer captures the client cert.
|
|
81
|
+
*
|
|
82
|
+
* @opts
|
|
83
|
+
* {
|
|
84
|
+
* fingerprintAllowList: string[],
|
|
85
|
+
* denyList: string[],
|
|
86
|
+
* onAuthenticated: function(req, res, next): void,
|
|
87
|
+
* auditAction: string,
|
|
88
|
+
* errorMessage: string,
|
|
89
|
+
* audit: object,
|
|
90
|
+
* }
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* var b = require("@blamejs/core");
|
|
94
|
+
* var app = b.router.create();
|
|
95
|
+
* app.use("/internal", b.middleware.requireMtls({
|
|
96
|
+
* fingerprintAllowList: ["AB:CD:EF:01:23:45"],
|
|
97
|
+
* }));
|
|
98
|
+
*/
|
|
66
99
|
function create(opts) {
|
|
67
100
|
opts = opts || {};
|
|
68
101
|
validateOpts(opts, [
|
|
@@ -75,6 +75,43 @@ function _writeChallenge(res, challenge, body, statusCode) {
|
|
|
75
75
|
res.end(json);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @primitive b.middleware.requireStepUp
|
|
80
|
+
* @signature b.middleware.requireStepUp(opts)
|
|
81
|
+
* @since 0.1.0
|
|
82
|
+
* @related b.middleware.requireAal, b.middleware.bearerAuth
|
|
83
|
+
*
|
|
84
|
+
* Gates routes per RFC 9470 OAuth 2.0 Step-Up Authentication
|
|
85
|
+
* Challenge. Mount AFTER `attachUser` / `bearerAuth` so the
|
|
86
|
+
* request carries verified token claims. Refuses with HTTP 401 +
|
|
87
|
+
* `WWW-Authenticate: Bearer error="insufficient_user_authentication",
|
|
88
|
+
* acr_values="...", max_age="..."`. With `acceptGrant: true`
|
|
89
|
+
* (default) the middleware first checks for a fresh
|
|
90
|
+
* `b.auth.stepUp.grant` token in `X-Step-Up-Grant` so a
|
|
91
|
+
* multi-step flow doesn't re-prompt on every action. Never weakens
|
|
92
|
+
* defaults to accommodate missing IdP claims — operators configure
|
|
93
|
+
* the IdP to emit `acr` / `auth_time` / `amr` correctly.
|
|
94
|
+
*
|
|
95
|
+
* @opts
|
|
96
|
+
* {
|
|
97
|
+
* requirement: { acr, acrValues, maxAge, requiredAmr, phishingResistant }, // required
|
|
98
|
+
* getClaims: function(req): object,
|
|
99
|
+
* realm: string,
|
|
100
|
+
* acceptGrant: boolean, // default true
|
|
101
|
+
* grantHeader: string, // default "X-Step-Up-Grant"
|
|
102
|
+
* grantScope: string,
|
|
103
|
+
* errorDescription: string,
|
|
104
|
+
* audit: boolean, // default true
|
|
105
|
+
* }
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* var b = require("@blamejs/core");
|
|
109
|
+
* var app = b.router.create();
|
|
110
|
+
* app.use("/billing/transfer", b.middleware.requireStepUp({
|
|
111
|
+
* requirement: { acr: "high", maxAge: 300 },
|
|
112
|
+
* realm: "billing-api",
|
|
113
|
+
* }));
|
|
114
|
+
*/
|
|
78
115
|
function create(opts) {
|
|
79
116
|
opts = opts || {};
|
|
80
117
|
validateOpts(opts, [
|