@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
|
@@ -114,6 +114,7 @@ var path = require("path");
|
|
|
114
114
|
var nodeCrypto = require("node:crypto");
|
|
115
115
|
var atomicFile = require("../atomic-file");
|
|
116
116
|
var crypto = require("../crypto");
|
|
117
|
+
var lazyRequire = require("../lazy-require");
|
|
117
118
|
var requestHelpers = require("../request-helpers");
|
|
118
119
|
var safeBuffer = require("../safe-buffer");
|
|
119
120
|
var safeJson = require("../safe-json");
|
|
@@ -121,6 +122,34 @@ var validateOpts = require("../validate-opts");
|
|
|
121
122
|
var C = require("../constants");
|
|
122
123
|
var { defineClass } = require("../framework-error");
|
|
123
124
|
|
|
125
|
+
var auditFwk = lazyRequire(function () { return require("../audit"); });
|
|
126
|
+
|
|
127
|
+
// Node's HTTP parser surfaces malformed chunked-transfer-encoding via a
|
|
128
|
+
// stable family of HPE_* codes. RFC 9112 §7.1 — when a server rejects a
|
|
129
|
+
// chunked decode the connection MUST close so a downstream proxy can't
|
|
130
|
+
// reuse the socket with the next request's body bytes still pending.
|
|
131
|
+
// HPE_INVALID_CHUNK_SIZE / HPE_CHUNK_EXTENSIONS_OVERFLOW (Node 24+) /
|
|
132
|
+
// HPE_INVALID_TRANSFER_ENCODING / HPE_INVALID_EOF_STATE (chunk truncated)
|
|
133
|
+
// all land here. The framework's Connection: close + audit emit closes
|
|
134
|
+
// the smuggling-adjacent socket-reuse path that bare 400-only handling
|
|
135
|
+
// leaves open.
|
|
136
|
+
var CHUNKED_MALFORMED_CODES = new Set([
|
|
137
|
+
"HPE_INVALID_CHUNK_SIZE",
|
|
138
|
+
"HPE_INVALID_TRANSFER_ENCODING",
|
|
139
|
+
"HPE_INVALID_EOF_STATE",
|
|
140
|
+
"HPE_INVALID_CONSTANT",
|
|
141
|
+
"HPE_CHUNK_EXTENSIONS_OVERFLOW",
|
|
142
|
+
"HPE_UNEXPECTED_CONTENT_LENGTH",
|
|
143
|
+
"ERR_HTTP_INVALID_CHUNK",
|
|
144
|
+
]);
|
|
145
|
+
function _isChunkedMalformed(e) {
|
|
146
|
+
if (!e) return false;
|
|
147
|
+
if (typeof e.code === "string" && CHUNKED_MALFORMED_CODES.has(e.code)) return true;
|
|
148
|
+
if (typeof e.code === "string" && e.code.indexOf("HPE_") === 0 &&
|
|
149
|
+
typeof e.message === "string" && /chunk/i.test(e.message)) return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
124
153
|
var HTTP_STATUS = requestHelpers.HTTP_STATUS;
|
|
125
154
|
var BodyParserError = defineClass("BodyParserError", { withStatusCode: true });
|
|
126
155
|
|
|
@@ -1098,6 +1127,46 @@ async function _parseMultipart(req, opts, ctParams) {
|
|
|
1098
1127
|
|
|
1099
1128
|
// ---- main middleware factory ----
|
|
1100
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* @primitive b.middleware.bodyParser
|
|
1132
|
+
* @signature b.middleware.bodyParser(req, res, next)
|
|
1133
|
+
* @since 0.1.0
|
|
1134
|
+
* @related b.middleware.bodyParser.raw, b.parsers.json, b.parsers.multipart
|
|
1135
|
+
*
|
|
1136
|
+
* Buffers and parses request bodies based on Content-Type.
|
|
1137
|
+
* Constructed via `b.middleware.bodyParser(opts)`; the resulting
|
|
1138
|
+
* middleware has the `(req, res, next)` shape shown above. Five
|
|
1139
|
+
* sub-parsers ship: JSON (via `safe-json` — POISONED_KEYS stripped,
|
|
1140
|
+
* depth + size caps), urlencoded, text, raw octet-stream, and
|
|
1141
|
+
* multipart/form-data. Multipart streams file parts to a tmp dir
|
|
1142
|
+
* with per-file + total-request size caps, filename sanitization,
|
|
1143
|
+
* SHA3-512 hashing during streaming, and tmp-file cleanup on
|
|
1144
|
+
* response end. Defends against RFC 9112 §6.1 request smuggling
|
|
1145
|
+
* before any body bytes are read. Each sub-parser can be disabled
|
|
1146
|
+
* by passing `false` in its slot.
|
|
1147
|
+
*
|
|
1148
|
+
* @opts
|
|
1149
|
+
* {
|
|
1150
|
+
* json: false | { limit, strict, charset, parseHook, contentTypes },
|
|
1151
|
+
* urlencoded: false | { limit, arrayLimit, contentTypes },
|
|
1152
|
+
* text: false | { limit, charset, contentTypes },
|
|
1153
|
+
* raw: false | { limit, contentTypes },
|
|
1154
|
+
* multipart: false | {
|
|
1155
|
+
* tmpDir, fileSize, totalSize, fileCount, fieldCount, fieldSize,
|
|
1156
|
+
* mimeAllowlist, fileFilter, fields, audit, contentTypes,
|
|
1157
|
+
* },
|
|
1158
|
+
* keepRawBody: boolean, // expose req.bodyRaw for webhook signing
|
|
1159
|
+
* }
|
|
1160
|
+
*
|
|
1161
|
+
* @example
|
|
1162
|
+
* var b = require("@blamejs/core");
|
|
1163
|
+
* var app = b.router.create();
|
|
1164
|
+
* app.use(b.middleware.bodyParser({
|
|
1165
|
+
* json: { limit: b.constants.BYTES.mib(1) },
|
|
1166
|
+
* urlencoded: { limit: b.constants.BYTES.mib(1) },
|
|
1167
|
+
* multipart: false,
|
|
1168
|
+
* }));
|
|
1169
|
+
*/
|
|
1101
1170
|
function create(opts) {
|
|
1102
1171
|
opts = opts || {};
|
|
1103
1172
|
validateOpts(opts, [
|
|
@@ -1191,6 +1260,52 @@ function create(opts) {
|
|
|
1191
1260
|
"body-parser/unsupported-content-type"
|
|
1192
1261
|
);
|
|
1193
1262
|
} catch (e) {
|
|
1263
|
+
// RFC 9112 §7.1 — a server that rejects a chunked-decoded body
|
|
1264
|
+
// MUST close the connection so the upstream proxy cannot reuse
|
|
1265
|
+
// the socket with the next request's bytes still in flight. The
|
|
1266
|
+
// smuggling-shape pre-flight at top of the request already
|
|
1267
|
+
// catches the static TE/CL conflict cases; this catch handles
|
|
1268
|
+
// mid-stream parser failure (HPE_INVALID_CHUNK_SIZE etc. surfaced
|
|
1269
|
+
// by Node's HTTP parser as the body bytes arrive). Set
|
|
1270
|
+
// Connection: close + audit + 400.
|
|
1271
|
+
if (_isChunkedMalformed(e)) {
|
|
1272
|
+
// CVE-2026-33870 — chunked-encoding extension smuggling. When
|
|
1273
|
+
// Node's parser surfaces HPE_CHUNK_EXTENSIONS_OVERFLOW the
|
|
1274
|
+
// chunk-extension parameters exceeded llhttp's cap; the
|
|
1275
|
+
// framework emits a distinct audit action so operators can
|
|
1276
|
+
// alert on extension-smuggling specifically. RFC 9112 §7.1.1
|
|
1277
|
+
// chunk-ext is `; chunk-ext-name [= chunk-ext-val]` per chunk;
|
|
1278
|
+
// multi-`;` and `;param=value` shapes reach this code path
|
|
1279
|
+
// when the operator sets a tighter
|
|
1280
|
+
// `--max-http-header-size` / per-chunk extension cap.
|
|
1281
|
+
var chunkAction = (e && e.code === "HPE_CHUNK_EXTENSIONS_OVERFLOW")
|
|
1282
|
+
? "http.chunked.extension.refused"
|
|
1283
|
+
: "http.chunked.malformed.refused";
|
|
1284
|
+
try {
|
|
1285
|
+
auditFwk().safeEmit({
|
|
1286
|
+
action: chunkAction,
|
|
1287
|
+
outcome: "denied",
|
|
1288
|
+
metadata: {
|
|
1289
|
+
code: e.code || null,
|
|
1290
|
+
message: (e && e.message) ? String(e.message).slice(0, 256) : "", // allow:raw-byte-literal — diagnostic-message clamp characters, not bytes
|
|
1291
|
+
},
|
|
1292
|
+
});
|
|
1293
|
+
} catch (_e) { /* audit best-effort */ }
|
|
1294
|
+
if (!res.headersSent) {
|
|
1295
|
+
var malformedBody = JSON.stringify({
|
|
1296
|
+
error: "malformed chunked transfer-encoding (RFC 9112 §7.1 — connection closed)",
|
|
1297
|
+
code: "http/chunked-malformed",
|
|
1298
|
+
});
|
|
1299
|
+
res.writeHead(HTTP_STATUS.BAD_REQUEST, {
|
|
1300
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1301
|
+
"Content-Length": Buffer.byteLength(malformedBody),
|
|
1302
|
+
"Connection": "close",
|
|
1303
|
+
});
|
|
1304
|
+
res.end(malformedBody);
|
|
1305
|
+
}
|
|
1306
|
+
try { req.destroy(); } catch (_e) { /* socket already closed */ }
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1194
1309
|
var status = (e && typeof e.statusCode === "number") ? e.statusCode : HTTP_STATUS.BAD_REQUEST;
|
|
1195
1310
|
var code = (e && typeof e.code === "string") ? e.code : "body-parser/error";
|
|
1196
1311
|
var message = (e && e.message) ? e.message : String(e);
|
|
@@ -1238,6 +1353,36 @@ async function _parseJsonFromBuf(buf, opts) {
|
|
|
1238
1353
|
// Accepts the same `raw`-section opts as create() (limit, contentTypes).
|
|
1239
1354
|
// contentTypes default expands to `["*/*"]` so any Content-Type lands
|
|
1240
1355
|
// as raw bytes.
|
|
1356
|
+
|
|
1357
|
+
/**
|
|
1358
|
+
* @primitive b.middleware.bodyParser.raw
|
|
1359
|
+
* @signature b.middleware.bodyParser.raw(opts)
|
|
1360
|
+
* @since 0.1.0
|
|
1361
|
+
* @related b.middleware.bodyParser
|
|
1362
|
+
*
|
|
1363
|
+
* Convenience factory that mounts only the raw-bytes sub-parser of
|
|
1364
|
+
* `bodyParser`. Sets `req.body` to a Buffer regardless of
|
|
1365
|
+
* `Content-Type`. Use on webhook-signature routes where the HMAC is
|
|
1366
|
+
* computed over the literal body bytes — JSON-parsing first would
|
|
1367
|
+
* change them. The `contentTypes` default expands to `["*\/*"]` so
|
|
1368
|
+
* any inbound type is captured.
|
|
1369
|
+
*
|
|
1370
|
+
* @opts
|
|
1371
|
+
* {
|
|
1372
|
+
* limit: number, // default ~10 MiB
|
|
1373
|
+
* contentTypes: string[], // default ["*\/*"]
|
|
1374
|
+
* }
|
|
1375
|
+
*
|
|
1376
|
+
* @example
|
|
1377
|
+
* var b = require("@blamejs/core");
|
|
1378
|
+
* var app = b.router.create();
|
|
1379
|
+
* app.post("/hooks/in", b.middleware.bodyParser.raw({
|
|
1380
|
+
* limit: b.constants.BYTES.mib(1),
|
|
1381
|
+
* }), function (req, res) {
|
|
1382
|
+
* // req.body is a Buffer of the raw request bytes
|
|
1383
|
+
* res.end(String(req.body.length));
|
|
1384
|
+
* });
|
|
1385
|
+
*/
|
|
1241
1386
|
function raw(opts) {
|
|
1242
1387
|
opts = opts || {};
|
|
1243
1388
|
return create({
|
|
@@ -1257,10 +1402,95 @@ function raw(opts) {
|
|
|
1257
1402
|
// itself, so static helpers hang off it).
|
|
1258
1403
|
create.raw = raw;
|
|
1259
1404
|
|
|
1405
|
+
// ---- Standalone async parsers ----
|
|
1406
|
+
//
|
|
1407
|
+
// `parseJsonStandalone(req, opts)` and `parseMultipartStandalone(req, opts)`
|
|
1408
|
+
// are the same parsing pipelines the middleware uses, exposed for handlers
|
|
1409
|
+
// that lazy-parse — code that decides parser shape from a route flag, or
|
|
1410
|
+
// bypasses the middleware for streaming endpoints. The middleware composes
|
|
1411
|
+
// these so there's no parallel pipeline to drift.
|
|
1412
|
+
//
|
|
1413
|
+
// Throws BodyParserError on caps / malformed shapes — operator handles in
|
|
1414
|
+
// a try/catch around `await b.parsers.json(req, ...)` /
|
|
1415
|
+
// `await b.parsers.multipart(req, ...)`. Validation tier is config-time
|
|
1416
|
+
// (throw at create on bad opts) + observable (throw on bad input — the
|
|
1417
|
+
// handler is awaiting the call, not a request lifecycle hook).
|
|
1418
|
+
|
|
1419
|
+
function _resolveStandaloneJsonOpts(opts) {
|
|
1420
|
+
opts = opts || {};
|
|
1421
|
+
var maxBytes = (opts.maxBytes !== undefined) ? opts.maxBytes : DEFAULTS.json.limit;
|
|
1422
|
+
validateOpts.optionalPositiveFinite(maxBytes, "parsers.json: opts.maxBytes",
|
|
1423
|
+
BodyParserError, "body-parser/bad-max-bytes");
|
|
1424
|
+
var strict = (opts.strict !== undefined) ? !!opts.strict : DEFAULTS.json.strict;
|
|
1425
|
+
var charset = (typeof opts.charset === "string") ? opts.charset : DEFAULTS.json.charset;
|
|
1426
|
+
return {
|
|
1427
|
+
limit: maxBytes,
|
|
1428
|
+
strict: strict,
|
|
1429
|
+
charset: charset,
|
|
1430
|
+
parseHook: (typeof opts.parseHook === "function") ? opts.parseHook : undefined,
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function _resolveStandaloneMultipartOpts(opts, ct) {
|
|
1435
|
+
opts = opts || {};
|
|
1436
|
+
var resolved = Object.assign({}, DEFAULTS.multipart);
|
|
1437
|
+
validateOpts.optionalPositiveFinite(opts.maxBytes, "parsers.multipart: opts.maxBytes",
|
|
1438
|
+
BodyParserError, "body-parser/bad-max-bytes");
|
|
1439
|
+
if (opts.maxBytes !== undefined) {
|
|
1440
|
+
resolved.totalSize = opts.maxBytes;
|
|
1441
|
+
// Per-file cap clamps to maxBytes so a single field can't exceed the
|
|
1442
|
+
// request total — operator opts in to a smaller fileSize via opts.fileSize.
|
|
1443
|
+
if (resolved.fileSize > opts.maxBytes) resolved.fileSize = opts.maxBytes;
|
|
1444
|
+
}
|
|
1445
|
+
if (opts.maxFiles !== undefined) {
|
|
1446
|
+
var mf = opts.maxFiles;
|
|
1447
|
+
var mfBad = typeof mf !== "number" || !isFinite(mf) || mf <= 0 || Math.floor(mf) !== mf;
|
|
1448
|
+
if (mfBad) {
|
|
1449
|
+
throw new BodyParserError("body-parser/bad-max-files",
|
|
1450
|
+
"parsers.multipart: opts.maxFiles must be a positive integer",
|
|
1451
|
+
true, HTTP_STATUS.BAD_REQUEST);
|
|
1452
|
+
}
|
|
1453
|
+
resolved.fileCount = mf;
|
|
1454
|
+
}
|
|
1455
|
+
// Pass-through overrides for the multipart-specific knobs the middleware
|
|
1456
|
+
// accepts. parsers.multipart is a thin wrapper, not a feature subset.
|
|
1457
|
+
["tmpDir", "fileSize", "fieldCount", "fieldSize", "mimeAllowlist",
|
|
1458
|
+
"fileFilter", "fields", "audit"].forEach(function (k) {
|
|
1459
|
+
if (opts[k] !== undefined) resolved[k] = opts[k];
|
|
1460
|
+
});
|
|
1461
|
+
// ct is the parsed Content-Type; required for the boundary parameter.
|
|
1462
|
+
if (!ct || typeof ct.type !== "string" || ct.type !== "multipart/form-data") {
|
|
1463
|
+
throw new BodyParserError("body-parser/standalone-not-multipart",
|
|
1464
|
+
"parsers.multipart: request Content-Type must be multipart/form-data, got " +
|
|
1465
|
+
JSON.stringify(ct ? ct.type : null),
|
|
1466
|
+
true, HTTP_STATUS.BAD_REQUEST);
|
|
1467
|
+
}
|
|
1468
|
+
return resolved;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
async function parseJsonStandalone(req, opts) {
|
|
1472
|
+
var resolved = _resolveStandaloneJsonOpts(opts);
|
|
1473
|
+
return _parseJson(req, resolved);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
async function parseMultipartStandalone(req, opts) {
|
|
1477
|
+
var ct = _contentType(req);
|
|
1478
|
+
var resolved = _resolveStandaloneMultipartOpts(opts, ct);
|
|
1479
|
+
// Returns { fields, files, filesRejected } — same shape the middleware
|
|
1480
|
+
// attaches to req. Handlers that already-accepted the upload wire
|
|
1481
|
+
// cleanup themselves (move file off tmp / unlink).
|
|
1482
|
+
return _parseMultipart(req, resolved, ct.params);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1260
1485
|
module.exports = {
|
|
1261
1486
|
create: create,
|
|
1262
1487
|
raw: raw,
|
|
1263
1488
|
BodyParserError: BodyParserError,
|
|
1489
|
+
// Standalone async helpers — surfaced via b.parsers.{json,multipart}.
|
|
1490
|
+
// The middleware composes these so the request-handling pipeline and
|
|
1491
|
+
// the operator-callable surface share one parsing path.
|
|
1492
|
+
parseJson: parseJsonStandalone,
|
|
1493
|
+
parseMultipart: parseMultipartStandalone,
|
|
1264
1494
|
// Internal helpers exposed for tests + the csrf-protect refactor.
|
|
1265
1495
|
_contentType: _contentType,
|
|
1266
1496
|
_hasBody: _hasBody,
|
|
@@ -41,6 +41,40 @@ var DEFAULT_BANNER_HTML = '<div role="status" data-bot-disclosure="true" ' +
|
|
|
41
41
|
'For California users: this disclosure is provided per Cal. Bus. & Prof. Code §17941.' +
|
|
42
42
|
'</div>';
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* @primitive b.middleware.botDisclose
|
|
46
|
+
* @signature b.middleware.botDisclose(opts)
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
* @related b.middleware.aiActDisclosure, b.middleware.botGuard
|
|
49
|
+
*
|
|
50
|
+
* California SB 1001 bot-disclosure (Cal. Bus. & Prof. Code §17941):
|
|
51
|
+
* automated conversation surfaces (LLM chat / IVR / SMS) used to
|
|
52
|
+
* incentivize sales or influence elections must disclose their
|
|
53
|
+
* non-human nature. Injects a disclosure banner into HTML responses,
|
|
54
|
+
* sets `X-Bot-Disclosure` for API consumers, and emits an audit
|
|
55
|
+
* event for every conversation-initiating request. Operator-supplied
|
|
56
|
+
* `bannerHtml` / `bannerJson` carry custom branding while the
|
|
57
|
+
* default copy meets the §17941(a) "clear, conspicuous, reasonably
|
|
58
|
+
* designed" bar.
|
|
59
|
+
*
|
|
60
|
+
* @opts
|
|
61
|
+
* {
|
|
62
|
+
* mountPaths: string[], // null = apply to all routes
|
|
63
|
+
* bannerHtml: string,
|
|
64
|
+
* bannerJson: object,
|
|
65
|
+
* headerName: string, // default "X-Bot-Disclosure"
|
|
66
|
+
* auditAction: string, // default "middleware.bot_disclose"
|
|
67
|
+
* audit: boolean, // default true
|
|
68
|
+
* }
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* var b = require("@blamejs/core");
|
|
72
|
+
* var app = b.router.create();
|
|
73
|
+
* app.use(b.middleware.botDisclose({
|
|
74
|
+
* mountPaths: ["/chat", "/api/chat"],
|
|
75
|
+
* bannerJson: { _bot: true, disclosure: "automated-assistant" },
|
|
76
|
+
* }));
|
|
77
|
+
*/
|
|
44
78
|
function create(opts) {
|
|
45
79
|
opts = opts || {};
|
|
46
80
|
validateOpts(opts, [
|
|
@@ -77,6 +77,45 @@ function _xffIpFor(trustProxy) {
|
|
|
77
77
|
};
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* @primitive b.middleware.botGuard
|
|
82
|
+
* @signature b.middleware.botGuard(req, res, next)
|
|
83
|
+
* @since 0.1.0
|
|
84
|
+
* @related b.middleware.fetchMetadata, b.middleware.botDisclose
|
|
85
|
+
*
|
|
86
|
+
* Cheap fingerprint-based detection of obviously-non-browser requests.
|
|
87
|
+
* Constructed via `b.middleware.botGuard(opts)`; the resulting
|
|
88
|
+
* middleware has the `(req, res, next)` shape shown above.
|
|
89
|
+
* Combines three heuristics: missing `Accept-Language`, missing
|
|
90
|
+
* `Sec-Fetch-Mode` (HTML routes), and User-Agent regex match against
|
|
91
|
+
* a default list (curl / wget / python-requests / axios / etc.). Not
|
|
92
|
+
* a substitute for proper authentication — catches drive-by scrapers
|
|
93
|
+
* and low-effort bots. In `mode: "block"` (default) the request is
|
|
94
|
+
* refused; in `mode: "tag"` `req.suspectedBot = true` is set and the
|
|
95
|
+
* request continues so the application can rate-limit suspected bots
|
|
96
|
+
* separately. Every decision is audited.
|
|
97
|
+
*
|
|
98
|
+
* @opts
|
|
99
|
+
* {
|
|
100
|
+
* mode: "block"|"tag", // default "block"
|
|
101
|
+
* onlyForHtml: boolean, // default true
|
|
102
|
+
* allowedAgents: RegExp[], // override matches
|
|
103
|
+
* blockedAgents: RegExp[], // append to defaults
|
|
104
|
+
* skipPaths: string[],
|
|
105
|
+
* statusOnBlock: number, // default 403
|
|
106
|
+
* bodyOnBlock: string,
|
|
107
|
+
* trustProxy: boolean|number,
|
|
108
|
+
* }
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* var b = require("@blamejs/core");
|
|
112
|
+
* var app = b.router.create();
|
|
113
|
+
* app.use(b.middleware.botGuard({
|
|
114
|
+
* mode: "tag",
|
|
115
|
+
* skipPaths: ["/healthz"],
|
|
116
|
+
* onlyForHtml: true,
|
|
117
|
+
* }));
|
|
118
|
+
*/
|
|
80
119
|
function create(opts) {
|
|
81
120
|
opts = opts || {};
|
|
82
121
|
validateOpts(opts, [
|
|
@@ -225,6 +225,43 @@ function _appendVary(existing, token) {
|
|
|
225
225
|
return parts.join(", ");
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
/**
|
|
229
|
+
* @primitive b.middleware.compression
|
|
230
|
+
* @signature b.middleware.compression(req, res, next)
|
|
231
|
+
* @since 0.1.0
|
|
232
|
+
* @related b.middleware.sse
|
|
233
|
+
*
|
|
234
|
+
* Brotli + gzip response compression. Constructed via
|
|
235
|
+
* `b.middleware.compression(opts)`; the resulting middleware has
|
|
236
|
+
* the `(req, res, next)` shape shown above. Intercepts the response stream
|
|
237
|
+
* and pipes it through `node:zlib`'s transform when the client
|
|
238
|
+
* supports it. Brotli is preferred (better ratio for text), gzip is
|
|
239
|
+
* the fallback. Skips small responses (below `threshold`),
|
|
240
|
+
* already-encoded responses, 204/304 status codes, server-sent
|
|
241
|
+
* events streams (chunked compression breaks SSE framing), and
|
|
242
|
+
* Content-Types outside the allowlist (image/* / video/* / archives
|
|
243
|
+
* are already entropy-dense). Operators with custom skip logic wire
|
|
244
|
+
* a `filter(req, res)` predicate.
|
|
245
|
+
*
|
|
246
|
+
* @opts
|
|
247
|
+
* {
|
|
248
|
+
* threshold: number, // default 1024 bytes
|
|
249
|
+
* encodings: string[], // default ["br", "gzip"]
|
|
250
|
+
* contentTypes: string[], // allowlist of MIME types
|
|
251
|
+
* gzipLevel: number, // 1..9, default 6
|
|
252
|
+
* brotliQuality: number, // 0..11, default 4
|
|
253
|
+
* filter: function(req, res): boolean,
|
|
254
|
+
* }
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* var b = require("@blamejs/core");
|
|
258
|
+
* var app = b.router.create();
|
|
259
|
+
* app.use(b.middleware.compression({
|
|
260
|
+
* threshold: 1024,
|
|
261
|
+
* encodings: ["br", "gzip"],
|
|
262
|
+
* contentTypes: ["text/*", "application/json"],
|
|
263
|
+
* }));
|
|
264
|
+
*/
|
|
228
265
|
function create(opts) {
|
|
229
266
|
opts = opts || {};
|
|
230
267
|
validateOpts(opts, [
|
|
@@ -47,6 +47,38 @@ function _emitAudit(audit, action, outcome, metadata) {
|
|
|
47
47
|
} catch (_e) { /* drop-silent — observability sink */ }
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* @primitive b.middleware.cookies
|
|
52
|
+
* @signature b.middleware.cookies(opts)
|
|
53
|
+
* @since 0.1.0
|
|
54
|
+
* @related b.cookies.parseSafe, b.middleware.csrfProtect
|
|
55
|
+
*
|
|
56
|
+
* Inbound `Cookie` header threat detection. Runs every request through
|
|
57
|
+
* `b.cookies.parseSafe` and surfaces header-cap / control-byte /
|
|
58
|
+
* malformed-pair / empty-name / name-cap / value-cap / duplicate-name
|
|
59
|
+
* (cookie-tossing) issues. Sets `req.cookieJar` to the parsed jar.
|
|
60
|
+
* In `mode: "enforce"` (default) high-severity issues refuse the
|
|
61
|
+
* request with HTTP 400 + JSON body; `audit-only` and `log-only`
|
|
62
|
+
* modes pass through but still emit audits.
|
|
63
|
+
*
|
|
64
|
+
* @opts
|
|
65
|
+
* {
|
|
66
|
+
* mode: "enforce"|"audit-only"|"log-only", // default "enforce"
|
|
67
|
+
* refuseOnHigh: boolean, // default true (only meaningful in enforce)
|
|
68
|
+
* maxHeaderBytes: number,
|
|
69
|
+
* maxNameBytes: number,
|
|
70
|
+
* maxValueBytes: number,
|
|
71
|
+
* audit: object,
|
|
72
|
+
* }
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* var b = require("@blamejs/core");
|
|
76
|
+
* var app = b.router.create();
|
|
77
|
+
* app.use(b.middleware.cookies({
|
|
78
|
+
* mode: "enforce",
|
|
79
|
+
* maxHeaderBytes: b.constants.BYTES.kib(8),
|
|
80
|
+
* }));
|
|
81
|
+
*/
|
|
50
82
|
function create(opts) {
|
|
51
83
|
opts = opts || {};
|
|
52
84
|
var mode = opts.mode || "enforce";
|
package/lib/middleware/cors.js
CHANGED
|
@@ -156,6 +156,46 @@ function _isSameOrigin(req, originHeader, configuredSiteOrigins, trustProxy, str
|
|
|
156
156
|
return reqOrigin !== null && reqOrigin === canonOrigin;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* @primitive b.middleware.cors
|
|
161
|
+
* @signature b.middleware.cors(req, res, next)
|
|
162
|
+
* @since 0.1.0
|
|
163
|
+
* @related b.middleware.csrfProtect, b.middleware.fetchMetadata
|
|
164
|
+
*
|
|
165
|
+
* Cross-Origin Resource Sharing handler. Constructed via
|
|
166
|
+
* `b.middleware.cors(opts)`; the resulting middleware has the
|
|
167
|
+
* `(req, res, next)` shape shown above. Allowlist matches strings
|
|
168
|
+
* (canonicalized) and RegExp entries. Handles preflights,
|
|
169
|
+
* `Access-Control-Allow-*` response headers, and
|
|
170
|
+
* `strictNullOrigin: true` (default) refuses `Origin: null` even
|
|
171
|
+
* with `Sec-Fetch-Site: same-origin` since non-browser callers can
|
|
172
|
+
* forge that header. `siteOrigin` declares the framework's own
|
|
173
|
+
* origin(s) for same-origin shortcuts. Throws at create() on
|
|
174
|
+
* unparseable origin entries — operators catch typos at boot.
|
|
175
|
+
*
|
|
176
|
+
* @opts
|
|
177
|
+
* {
|
|
178
|
+
* origins: Array<string|RegExp>,
|
|
179
|
+
* siteOrigin: string|string[],
|
|
180
|
+
* methods: string[], // default GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
|
|
181
|
+
* headers: string[], // default Content-Type,Authorization,X-Request-Id
|
|
182
|
+
* exposeHeaders: string[], // default X-Request-Id
|
|
183
|
+
* credentials: boolean,
|
|
184
|
+
* maxAgeSeconds: number, // default 600
|
|
185
|
+
* refuseUnknown: boolean, // default true
|
|
186
|
+
* strictNullOrigin: boolean, // default true
|
|
187
|
+
* trustProxy: boolean|number,
|
|
188
|
+
* }
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* var b = require("@blamejs/core");
|
|
192
|
+
* var app = b.router.create();
|
|
193
|
+
* app.use(b.middleware.cors({
|
|
194
|
+
* origins: ["https://app.example.com", /\.example\.com$/],
|
|
195
|
+
* siteOrigin: "https://example.com",
|
|
196
|
+
* methods: ["GET", "POST"],
|
|
197
|
+
* }));
|
|
198
|
+
*/
|
|
159
199
|
function create(opts) {
|
|
160
200
|
opts = opts || {};
|
|
161
201
|
|
|
@@ -224,6 +224,46 @@ function _injectNonce(cspHeader, nonce, directives, strictDynamic) {
|
|
|
224
224
|
return _serializeCsp(parts);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
+
/**
|
|
228
|
+
* @primitive b.middleware.cspNonce
|
|
229
|
+
* @signature b.middleware.cspNonce(req, res, next)
|
|
230
|
+
* @since 0.1.0
|
|
231
|
+
* @related b.middleware.securityHeaders, b.middleware.cspReport
|
|
232
|
+
*
|
|
233
|
+
* Per-request CSP nonce + render integration. Constructed via
|
|
234
|
+
* `b.middleware.cspNonce(opts)`; the resulting middleware has the
|
|
235
|
+
* `(req, res, next)` shape shown above. Generates a fresh
|
|
236
|
+
* random nonce (16 bytes / 22 chars base64 by default), attaches it
|
|
237
|
+
* to `req.cspNonce` and `res.locals.cspNonce` (auto-merged into
|
|
238
|
+
* template data), and patches the existing Content-Security-Policy
|
|
239
|
+
* header to append `'nonce-XYZ'` to the configured directives
|
|
240
|
+
* (default: script-src + style-src). With `strictDynamic: true`,
|
|
241
|
+
* appends `'strict-dynamic'` so nonced scripts can load dependencies
|
|
242
|
+
* without origin allowlisting (recommended for SPA hydration). Mount
|
|
243
|
+
* after `securityHeaders`. Below-16-byte nonces are refused at
|
|
244
|
+
* config time.
|
|
245
|
+
*
|
|
246
|
+
* @opts
|
|
247
|
+
* {
|
|
248
|
+
* directives: string[], // default ["script-src", "style-src"]
|
|
249
|
+
* nonceBytes: number, // default 16; minimum 16
|
|
250
|
+
* strictDynamic: boolean,
|
|
251
|
+
* headerName: string, // default "Content-Security-Policy"
|
|
252
|
+
* property: string, // default "cspNonce"
|
|
253
|
+
* always: boolean,
|
|
254
|
+
* placeholder: string,
|
|
255
|
+
* }
|
|
256
|
+
*
|
|
257
|
+
* @example
|
|
258
|
+
* var b = require("@blamejs/core");
|
|
259
|
+
* var app = b.router.create();
|
|
260
|
+
* app.use(b.middleware.securityHeaders());
|
|
261
|
+
* app.use(b.middleware.cspNonce({
|
|
262
|
+
* directives: ["script-src", "style-src"],
|
|
263
|
+
* nonceBytes: 16,
|
|
264
|
+
* strictDynamic: true,
|
|
265
|
+
* }));
|
|
266
|
+
*/
|
|
227
267
|
function create(opts) {
|
|
228
268
|
opts = opts || {};
|
|
229
269
|
validateOpts(opts, [
|
|
@@ -82,6 +82,40 @@ function _normalizeOne(reportLike) {
|
|
|
82
82
|
return null;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @primitive b.middleware.cspReport
|
|
87
|
+
* @signature b.middleware.cspReport(req, res, next)
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
* @related b.middleware.cspNonce, b.middleware.securityHeaders
|
|
90
|
+
*
|
|
91
|
+
* Reporting-API endpoint for CSP / COEP / COOP / Permissions-Policy
|
|
92
|
+
* violations. Constructed via `b.middleware.cspReport(opts)`; the
|
|
93
|
+
* resulting middleware has the `(req, res, next)` shape shown above. Accepts `application/reports+json` (modern) and the
|
|
94
|
+
* legacy `application/csp-report` body shapes. Refuses non-POST
|
|
95
|
+
* (HTTP 405), oversized bodies (HTTP 413, default 64 KiB cap), and
|
|
96
|
+
* non-JSON (HTTP 400). Each report is normalized to a uniform shape
|
|
97
|
+
* (`type`, `url`, `body.{documentURL, blockedURL, effectiveDirective,
|
|
98
|
+
* sample, sourceFile, lineNumber}`), audited with action
|
|
99
|
+
* `csp.violation`, and forwarded to the operator's `onReport`
|
|
100
|
+
* callback for metrics or alerting.
|
|
101
|
+
*
|
|
102
|
+
* @opts
|
|
103
|
+
* {
|
|
104
|
+
* onReport: function(report): void,
|
|
105
|
+
* maxBytes: number, // default 64 KiB
|
|
106
|
+
* audit: boolean, // default true
|
|
107
|
+
* }
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* var b = require("@blamejs/core");
|
|
111
|
+
* var app = b.router.create();
|
|
112
|
+
* app.post("/csp-report", b.middleware.cspReport({
|
|
113
|
+
* maxBytes: b.constants.BYTES.kib(64),
|
|
114
|
+
* onReport: function (report) {
|
|
115
|
+
* console.log("csp violation", report.body.effectiveDirective);
|
|
116
|
+
* },
|
|
117
|
+
* }));
|
|
118
|
+
*/
|
|
85
119
|
function create(opts) {
|
|
86
120
|
opts = opts || {};
|
|
87
121
|
validateOpts(opts, ["audit", "onReport", "maxBytes"], "middleware.cspReport");
|
|
@@ -226,6 +226,49 @@ function _writeReject(res, message) {
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
/**
|
|
230
|
+
* @primitive b.middleware.csrfProtect
|
|
231
|
+
* @signature b.middleware.csrfProtect(req, res, next)
|
|
232
|
+
* @since 0.1.0
|
|
233
|
+
* @related b.middleware.cors, b.middleware.fetchMetadata
|
|
234
|
+
*
|
|
235
|
+
* Issues CSRF tokens to safe-method requests and rejects state-
|
|
236
|
+
* changing requests whose submitted token doesn't match. Constructed
|
|
237
|
+
* via `b.middleware.csrfProtect(opts)`; the resulting middleware
|
|
238
|
+
* has the `(req, res, next)` shape shown above. Two
|
|
239
|
+
* storage modes (mutually exclusive, exactly one required):
|
|
240
|
+
* (a) cookie-stored double-submit (default — `__Host-csrf` over
|
|
241
|
+
* HTTPS, SameSite=Lax) where the framework issues + reads the
|
|
242
|
+
* cookie; (b) operator-supplied `tokenLookup(req)` for session-
|
|
243
|
+
* stored tokens. Submitted-token sources: header (default
|
|
244
|
+
* `X-CSRF-Token`) then body field (default `_csrf`). Refuses with
|
|
245
|
+
* HTTP 403 + audits `auth.csrf.denied` on mismatch. Mount AFTER
|
|
246
|
+
* `attachUser` (session lookup) and `bodyParser` (form-field read).
|
|
247
|
+
*
|
|
248
|
+
* @opts
|
|
249
|
+
* {
|
|
250
|
+
* cookie: boolean | { name, sameSite, secure, path, httpOnly },
|
|
251
|
+
* tokenLookup: function(req): string|null,
|
|
252
|
+
* fieldName: string, // default "_csrf"
|
|
253
|
+
* headerName: string, // default "X-CSRF-Token"
|
|
254
|
+
* methods: string[], // default POST/PUT/DELETE/PATCH
|
|
255
|
+
* checkOrigin: boolean,
|
|
256
|
+
* allowedOrigins: string[],
|
|
257
|
+
* requireOrigin: boolean,
|
|
258
|
+
* requireJsonContentType: boolean,
|
|
259
|
+
* trustProxy: boolean|number,
|
|
260
|
+
* audit: boolean,
|
|
261
|
+
* }
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* var b = require("@blamejs/core");
|
|
265
|
+
* var app = b.router.create();
|
|
266
|
+
* app.use(b.middleware.csrfProtect({
|
|
267
|
+
* cookie: true,
|
|
268
|
+
* checkOrigin: true,
|
|
269
|
+
* requireOrigin: true,
|
|
270
|
+
* }));
|
|
271
|
+
*/
|
|
229
272
|
function create(opts) {
|
|
230
273
|
opts = opts || {};
|
|
231
274
|
|