@blamejs/core 0.7.4 → 0.7.18
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 +423 -395
- package/README.md +150 -149
- package/bin/blamejs.js +0 -0
- package/index.js +308 -284
- package/lib/api-key.js +660 -663
- package/lib/api-snapshot.js +338 -338
- package/lib/app-shutdown.js +385 -385
- package/lib/app.js +365 -365
- package/lib/archive.js +250 -250
- package/lib/atomic-file.js +544 -544
- package/lib/audit-chain.js +177 -177
- package/lib/audit-sign.js +344 -344
- package/lib/audit-tools.js +677 -677
- package/lib/audit.js +766 -766
- package/lib/auth/jwt.js +311 -311
- package/lib/auth/lockout.js +436 -436
- package/lib/auth/oauth.js +721 -721
- package/lib/auth/passkey.js +181 -181
- package/lib/auth/password.js +594 -594
- package/lib/backup/bundle.js +217 -217
- package/lib/backup/crypto.js +176 -176
- package/lib/backup/index.js +515 -515
- package/lib/backup/manifest.js +282 -282
- package/lib/break-glass.js +1338 -1338
- package/lib/bundler.js +441 -441
- package/lib/cache-redis.js +256 -256
- package/lib/cache.js +1206 -1206
- package/lib/canonical-json.js +115 -115
- package/lib/chain-writer.js +234 -234
- package/lib/cli-helpers.js +206 -206
- package/lib/cli.js +2334 -2334
- package/lib/cluster-provider-db.js +317 -317
- package/lib/cluster-storage.js +226 -226
- package/lib/cluster.js +703 -703
- package/lib/codepoint-class.js +196 -0
- package/lib/config-drift.js +301 -301
- package/lib/consent.js +222 -222
- package/lib/constants.js +191 -191
- package/lib/cookies.js +315 -315
- package/lib/credential-hash.js +322 -322
- package/lib/crypto.js +266 -266
- package/lib/csv.js +275 -286
- package/lib/db-declare-row-policy.js +267 -267
- package/lib/db-declare-view.js +420 -421
- package/lib/db-query.js +406 -406
- package/lib/db-schema.js +319 -319
- package/lib/db.js +1288 -1288
- package/lib/deprecate.js +222 -222
- package/lib/dev.js +335 -335
- package/lib/dual-control.js +473 -473
- package/lib/error-page.js +420 -420
- package/lib/external-db-migrate.js +441 -441
- package/lib/external-db.js +1061 -1061
- package/lib/file-type.js +273 -273
- package/lib/file-upload.js +213 -10
- package/lib/forms.js +422 -422
- package/lib/framework-error.js +293 -215
- package/lib/framework-schema.js +717 -717
- package/lib/gate-contract.js +971 -0
- package/lib/guard-all.js +405 -0
- package/lib/guard-archive.js +739 -0
- package/lib/guard-csv.js +816 -0
- package/lib/guard-email.js +744 -0
- package/lib/guard-filename.js +724 -0
- package/lib/guard-html.js +976 -0
- package/lib/guard-json.js +729 -0
- package/lib/guard-markdown.js +586 -0
- package/lib/guard-svg.js +976 -0
- package/lib/guard-xml.js +405 -0
- package/lib/guard-yaml.js +529 -0
- package/lib/handlers.js +350 -350
- package/lib/http-client-cookie-jar.js +508 -508
- package/lib/http-client.js +1195 -1195
- package/lib/i18n.js +878 -878
- package/lib/jobs.js +185 -185
- package/lib/log-stream-cloudwatch.js +369 -369
- package/lib/log-stream-local.js +146 -146
- package/lib/log-stream-otlp-grpc.js +410 -410
- package/lib/log-stream-otlp.js +286 -286
- package/lib/log-stream-syslog.js +302 -302
- package/lib/log-stream-webhook.js +199 -199
- package/lib/log-stream.js +330 -330
- package/lib/log.js +500 -500
- package/lib/mail-bounce.js +528 -528
- package/lib/mail-dkim.js +369 -362
- package/lib/mail.js +981 -962
- package/lib/metrics.js +683 -683
- package/lib/middleware/api-encrypt.js +936 -936
- package/lib/middleware/attach-user.js +157 -157
- package/lib/middleware/body-parser.js +1170 -1091
- package/lib/middleware/bot-guard.js +178 -178
- package/lib/middleware/compression.js +452 -452
- package/lib/middleware/cors.js +314 -314
- package/lib/middleware/csp-nonce.js +348 -348
- package/lib/middleware/csrf-protect.js +316 -316
- package/lib/middleware/db-role-for.js +264 -264
- package/lib/middleware/health.js +392 -392
- package/lib/middleware/index.js +79 -79
- package/lib/middleware/rate-limit.js +358 -358
- package/lib/middleware/request-id.js +61 -61
- package/lib/middleware/request-log.js +168 -168
- package/lib/middleware/require-auth.js +104 -104
- package/lib/middleware/security-headers.js +116 -116
- package/lib/middleware/sse.js +166 -166
- package/lib/migrations.js +383 -383
- package/lib/mtls-ca.js +518 -518
- package/lib/mtls-engine-default.js +481 -481
- package/lib/network-dns.js +632 -632
- package/lib/network-heartbeat.js +290 -290
- package/lib/network-nts.js +574 -574
- package/lib/network-proxy.js +265 -265
- package/lib/network-tls.js +328 -328
- package/lib/network.js +233 -233
- package/lib/notify.js +612 -612
- package/lib/ntp-check.js +229 -229
- package/lib/numeric-bounds.js +111 -91
- package/lib/object-store/azure-blob-bucket-ops.js +349 -349
- package/lib/object-store/azure-blob.js +488 -488
- package/lib/object-store/gcs-bucket-ops.js +351 -351
- package/lib/object-store/gcs.js +519 -519
- package/lib/object-store/http-put.js +153 -153
- package/lib/object-store/index.js +197 -197
- package/lib/object-store/sigv4-bucket-ops.js +1092 -1092
- package/lib/object-store/sigv4.js +903 -903
- package/lib/observability.js +151 -151
- package/lib/otel-export.js +269 -269
- package/lib/pagination.js +464 -464
- package/lib/parsers/index.js +80 -80
- package/lib/parsers/safe-env.js +642 -642
- package/lib/parsers/safe-ini.js +292 -292
- package/lib/parsers/safe-toml.js +784 -784
- package/lib/parsers/safe-xml.js +390 -390
- package/lib/parsers/safe-yaml.js +1015 -1015
- package/lib/permissions.js +708 -708
- package/lib/pqc-agent.js +87 -87
- package/lib/pqc-gate.js +279 -279
- package/lib/protobuf-encoder.js +190 -190
- package/lib/protocol-dispatcher.js +161 -161
- package/lib/pubsub-redis.js +167 -167
- package/lib/pubsub.js +429 -429
- package/lib/queue-local.js +476 -476
- package/lib/queue-redis.js +745 -745
- package/lib/queue-sqs.js +319 -319
- package/lib/queue.js +695 -695
- package/lib/redis-client.js +519 -519
- package/lib/request-helpers.js +340 -340
- package/lib/restore-bundle.js +237 -237
- package/lib/restore-rollback.js +259 -259
- package/lib/restore.js +409 -409
- package/lib/retry.js +376 -376
- package/lib/router.js +748 -748
- package/lib/safe-async.js +735 -735
- package/lib/safe-buffer.js +237 -237
- package/lib/safe-json.js +541 -541
- package/lib/safe-schema.js +1266 -1266
- package/lib/safe-url.js +159 -159
- package/lib/scheduler.js +706 -706
- package/lib/security-assert.js +373 -373
- package/lib/seeders.js +618 -618
- package/lib/session.js +478 -478
- package/lib/slug.js +269 -269
- package/lib/ssrf-guard.js +401 -401
- package/lib/static.js +184 -4
- package/lib/storage.js +471 -471
- package/lib/subject.js +281 -281
- package/lib/template.js +791 -791
- package/lib/testing.js +798 -798
- package/lib/time.js +310 -310
- package/lib/totp.js +302 -302
- package/lib/tracing.js +494 -494
- package/lib/uuid.js +132 -132
- package/lib/validate-opts.js +340 -319
- package/lib/vault/index.js +308 -308
- package/lib/vault/rotate.js +784 -784
- package/lib/vault/wrap.js +296 -296
- package/lib/vendor/noble-ciphers.cjs +9 -9
- package/lib/webhook.js +595 -595
- package/lib/websocket.js +1048 -1048
- package/package.json +77 -77
- package/sbom.cyclonedx.json +7 -7
package/lib/middleware/cors.js
CHANGED
|
@@ -1,314 +1,314 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* CORS middleware — allow-list-only by default. No '*' wildcard support
|
|
4
|
-
* for credentialed requests (which spec disallows anyway). Operators must
|
|
5
|
-
* explicitly enumerate origins they trust.
|
|
6
|
-
*
|
|
7
|
-
* Same-origin requests pass through without consulting the allow-list:
|
|
8
|
-
* the Fetch spec instructs browsers to send an Origin header on every
|
|
9
|
-
* POST / PUT / PATCH / DELETE — even same-origin ones — so an empty
|
|
10
|
-
* allow-list would otherwise refuse the operator's own login form.
|
|
11
|
-
* Same-origin is detected by:
|
|
12
|
-
* 1. Origin === opts.siteOrigin if configured (explicit, recommended —
|
|
13
|
-
* works behind TLS-terminating proxies where the framework can't
|
|
14
|
-
* infer scheme), or
|
|
15
|
-
* 2. Origin === request's inferred scheme/host/port (from req.socket
|
|
16
|
-
* and req.headers.host — correct for direct deployments).
|
|
17
|
-
*
|
|
18
|
-
* Origin: null shortcut (opt-in via strictNullOrigin: false): browsers
|
|
19
|
-
* send `Origin: null` on form-navigation POSTs from a page whose response
|
|
20
|
-
* carries `Referrer-Policy: no-referrer` (or similarly strict policy) —
|
|
21
|
-
* the Origin is opaqued to prevent a cross-site leak. The Sec-Fetch-Site
|
|
22
|
-
* Fetch-metadata signal distinguishes a same-origin nav from a genuine
|
|
23
|
-
* cross-site post in that opaque-origin world. Default is to REFUSE the
|
|
24
|
-
* shortcut (strictNullOrigin: true) — non-browser clients can forge
|
|
25
|
-
* Sec-Fetch-Site freely, and operators using `refuseUnknown: true` as a
|
|
26
|
-
* stricter "refuse unrecognized origin" policy expect the gate to hold.
|
|
27
|
-
* Operators with a no-referrer page that legitimately produces
|
|
28
|
-
* Origin: null on same-origin POSTs flip strictNullOrigin: false.
|
|
29
|
-
*
|
|
30
|
-
* Options:
|
|
31
|
-
* {
|
|
32
|
-
* origins: [ 'https://app.example.com', /^https:\/\/.+\.example\.com$/ ]
|
|
33
|
-
* siteOrigin: 'https://wiki.example.com' // string OR array
|
|
34
|
-
* methods: [ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' ]
|
|
35
|
-
* headers: [ 'Content-Type', 'Authorization', 'X-Request-Id' ]
|
|
36
|
-
* exposeHeaders: [ 'X-Request-Id', 'X-Response-Time' ]
|
|
37
|
-
* credentials: true (sets Access-Control-Allow-Credentials: true)
|
|
38
|
-
* maxAgeSeconds: 600
|
|
39
|
-
* refuseUnknown: true (refuse cross-origin requests from unlisted
|
|
40
|
-
* origins instead of just omitting CORS headers)
|
|
41
|
-
* strictNullOrigin: true (default — refuse Origin: null even with
|
|
42
|
-
* Sec-Fetch-Site: same-origin. Set false to
|
|
43
|
-
* allow the no-referrer-page edge case.)
|
|
44
|
-
* }
|
|
45
|
-
*
|
|
46
|
-
* Audit: refuseUnknown blocks emit system.cors.block with the offending Origin.
|
|
47
|
-
*
|
|
48
|
-
* Configuration validation: opts.siteOrigin must parse as an http(s) URL,
|
|
49
|
-
* opts.origins entries must be strings or RegExp. Bad config surfaces at
|
|
50
|
-
* create() not at first cross-origin request.
|
|
51
|
-
*/
|
|
52
|
-
var C = require("../constants");
|
|
53
|
-
var lazyRequire = require("../lazy-require");
|
|
54
|
-
var audit = lazyRequire(function () { return require("../audit"); });
|
|
55
|
-
var requestHelpers = require("../request-helpers");
|
|
56
|
-
var safeUrl = require("../safe-url");
|
|
57
|
-
var validateOpts = require("../validate-opts");
|
|
58
|
-
var { defineClass } = require("../framework-error");
|
|
59
|
-
|
|
60
|
-
// CORS audit events use the proxy-aware client IP only when the
|
|
61
|
-
// operator opts in via `trustProxy`. Default refuses forwarded
|
|
62
|
-
// headers — same boundary as the rest of the v0.5.3 trustProxy sweep.
|
|
63
|
-
function _xffIpFor(trustProxy) {
|
|
64
|
-
return function (req) {
|
|
65
|
-
return requestHelpers.clientIp(req, { trustProxy: trustProxy });
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
var CorsError = defineClass("CorsError", { alwaysPermanent: true });
|
|
70
|
-
|
|
71
|
-
// allowList entries:
|
|
72
|
-
// - { kind: "string", canonical: "https://app.example.com", original: "..." }
|
|
73
|
-
// - { kind: "regex", pattern: /.../ }
|
|
74
|
-
// Both raw entry and the inbound origin run through _canonicalOrigin
|
|
75
|
-
// before equality so case differences ("https://APP" vs "https://app")
|
|
76
|
-
// and default-port differences ("https://x:443" vs "https://x") match.
|
|
77
|
-
function _matchOrigin(origin, allowList) {
|
|
78
|
-
if (!origin) return null;
|
|
79
|
-
var canon = _canonicalOrigin(origin);
|
|
80
|
-
for (var i = 0; i < allowList.length; i++) {
|
|
81
|
-
var entry = allowList[i];
|
|
82
|
-
if (entry.kind === "string") {
|
|
83
|
-
if (canon !== null && entry.canonical === canon) return origin;
|
|
84
|
-
} else if (entry.kind === "regex") {
|
|
85
|
-
// Regex entries match against the raw origin (operator wrote the
|
|
86
|
-
// pattern with whatever case / port shape they intended). Also try
|
|
87
|
-
// the canonical form so case-insensitive intent works without /i.
|
|
88
|
-
if (entry.pattern.test(origin)) return origin;
|
|
89
|
-
if (canon !== null && entry.pattern.test(canon)) return origin;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Normalize an origin string by parsing it through safeUrl and
|
|
96
|
-
// returning `protocol//host[:port]` with no trailing slash. Used for
|
|
97
|
-
// equality checks that have to handle case differences and the URL
|
|
98
|
-
// parser's auto-port-omission for default ports (80/443).
|
|
99
|
-
//
|
|
100
|
-
// safeUrl defaults to ALLOW_HTTP_TLS (https only). CORS origins
|
|
101
|
-
// legitimately include http for local dev; pass ALLOW_HTTP_ALL.
|
|
102
|
-
function _canonicalOrigin(input) {
|
|
103
|
-
if (!input || typeof input !== "string") return null;
|
|
104
|
-
try {
|
|
105
|
-
var parsed = safeUrl.parse(input, { allowedProtocols: safeUrl.ALLOW_HTTP_ALL });
|
|
106
|
-
var proto = parsed.protocol; // "http:" or "https:"
|
|
107
|
-
var host = parsed.hostname.toLowerCase();
|
|
108
|
-
var port = parsed.port; // "" when default
|
|
109
|
-
return proto + "//" + host + (port ? ":" + port : "");
|
|
110
|
-
} catch (_e) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Build the request's own origin from req when opts.siteOrigin isn't
|
|
116
|
-
// supplied. Works for direct deployments (no proxy); operators behind
|
|
117
|
-
// a TLS-terminating proxy that doesn't forward correct Host should set
|
|
118
|
-
// opts.siteOrigin explicitly.
|
|
119
|
-
function _inferRequestOrigin(req, trustProxy) {
|
|
120
|
-
if (!req || !req.headers) return null;
|
|
121
|
-
var host = req.headers.host;
|
|
122
|
-
if (!host) return null;
|
|
123
|
-
// Protocol resolution honors the operator's trustProxy opt — without
|
|
124
|
-
// it, X-Forwarded-Proto is ignored as attacker-forgeable.
|
|
125
|
-
var proto = requestHelpers.requestProtocol(req, { trustProxy: trustProxy });
|
|
126
|
-
return _canonicalOrigin(proto + "://" + host);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function _isSameOrigin(req, originHeader, configuredSiteOrigins, trustProxy, strictNullOrigin) {
|
|
130
|
-
// Origin: null arrives when a browser opaques the Origin (e.g.
|
|
131
|
-
// Referrer-Policy: no-referrer on the page). Sec-Fetch-Site can
|
|
132
|
-
// distinguish the same-origin case, but non-browser clients can forge
|
|
133
|
-
// that header freely — strictNullOrigin: true (default) refuses the
|
|
134
|
-
// shortcut so refuseUnknown holds against forged callers. Operators
|
|
135
|
-
// with a legitimate no-referrer page flip strictNullOrigin: false.
|
|
136
|
-
if (originHeader === "null") {
|
|
137
|
-
if (strictNullOrigin) return false;
|
|
138
|
-
var sfs = req && req.headers && req.headers["sec-fetch-site"];
|
|
139
|
-
if (sfs === "same-origin" || sfs === "none") return true;
|
|
140
|
-
return false;
|
|
141
|
-
}
|
|
142
|
-
var canonOrigin = _canonicalOrigin(originHeader);
|
|
143
|
-
if (!canonOrigin) return false;
|
|
144
|
-
// Operator-supplied site origins take priority — they're the
|
|
145
|
-
// authoritative source for "this request is one of mine".
|
|
146
|
-
if (configuredSiteOrigins && configuredSiteOrigins.length > 0) {
|
|
147
|
-
for (var i = 0; i < configuredSiteOrigins.length; i++) {
|
|
148
|
-
if (configuredSiteOrigins[i] === canonOrigin) return true;
|
|
149
|
-
}
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
// Fall back to inferring from the request itself. trustProxy threads
|
|
153
|
-
// through so operators behind a TLS terminator with X-Forwarded-Proto
|
|
154
|
-
// can opt in to consult the header.
|
|
155
|
-
var reqOrigin = _inferRequestOrigin(req, trustProxy);
|
|
156
|
-
return reqOrigin !== null && reqOrigin === canonOrigin;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function create(opts) {
|
|
160
|
-
opts = opts || {};
|
|
161
|
-
|
|
162
|
-
validateOpts(opts, [
|
|
163
|
-
"origins", "siteOrigin", "methods", "headers", "exposeHeaders",
|
|
164
|
-
"credentials", "maxAgeSeconds", "refuseUnknown", "trustProxy",
|
|
165
|
-
"strictNullOrigin",
|
|
166
|
-
], "middleware.cors");
|
|
167
|
-
var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
168
|
-
? opts.trustProxy : false;
|
|
169
|
-
var _xffIp = _xffIpFor(trustProxy);
|
|
170
|
-
|
|
171
|
-
// Build a canonicalized allowList at create() time. String entries
|
|
172
|
-
// get parsed through _canonicalOrigin so case + default-port
|
|
173
|
-
// differences match consistently between the configured value and
|
|
174
|
-
// the inbound Origin header. RegExp entries stay as the operator
|
|
175
|
-
// wrote them.
|
|
176
|
-
var rawOrigins = opts.origins || [];
|
|
177
|
-
var origins = [];
|
|
178
|
-
for (var oi = 0; oi < rawOrigins.length; oi++) {
|
|
179
|
-
var entry = rawOrigins[oi];
|
|
180
|
-
if (typeof entry === "string") {
|
|
181
|
-
var canonEntry = _canonicalOrigin(entry);
|
|
182
|
-
if (canonEntry === null) {
|
|
183
|
-
throw new CorsError("cors/bad-origin",
|
|
184
|
-
"origins[" + oi + "]='" + entry + "' is not a parseable http(s) URL");
|
|
185
|
-
}
|
|
186
|
-
origins.push({ kind: "string", canonical: canonEntry, original: entry });
|
|
187
|
-
} else if (entry instanceof RegExp) {
|
|
188
|
-
origins.push({ kind: "regex", pattern: entry });
|
|
189
|
-
} else {
|
|
190
|
-
throw new CorsError("cors/bad-origin",
|
|
191
|
-
"origins[" + oi + "] must be a string or RegExp (got " + typeof entry + ")");
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Throw at create() on bad opts.siteOrigin — must parse as http(s) URL.
|
|
196
|
-
// Accept string OR array of strings.
|
|
197
|
-
var siteOrigins = [];
|
|
198
|
-
if (opts.siteOrigin !== undefined && opts.siteOrigin !== null) {
|
|
199
|
-
var rawList = Array.isArray(opts.siteOrigin) ? opts.siteOrigin : [opts.siteOrigin];
|
|
200
|
-
for (var si = 0; si < rawList.length; si++) {
|
|
201
|
-
var raw = rawList[si];
|
|
202
|
-
if (typeof raw !== "string" || raw.length === 0) {
|
|
203
|
-
throw new CorsError("cors/bad-site-origin",
|
|
204
|
-
"siteOrigin[" + si + "] must be a non-empty string (got " + typeof raw + ")");
|
|
205
|
-
}
|
|
206
|
-
var canon = _canonicalOrigin(raw);
|
|
207
|
-
if (!canon) {
|
|
208
|
-
throw new CorsError("cors/bad-site-origin",
|
|
209
|
-
"siteOrigin[" + si + "]='" + raw + "' is not a parseable http(s) URL");
|
|
210
|
-
}
|
|
211
|
-
siteOrigins.push(canon);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
var methods = (opts.methods || ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).join(", ");
|
|
216
|
-
var headersList = (opts.headers || ["Content-Type", "Authorization", "X-Request-Id"]).slice();
|
|
217
|
-
var headers = headersList.join(", ");
|
|
218
|
-
// Pre-compute the lowercase allowlist once at create() time so the
|
|
219
|
-
// preflight path doesn't re-derive it per-request from the joined
|
|
220
|
-
// string. Avoids coupling allow-list validation to the wire-format
|
|
221
|
-
// serialization.
|
|
222
|
-
var allowedHeadersSet = headersList.map(function (h) { return String(h).trim().toLowerCase(); });
|
|
223
|
-
var exposeHeaders = (opts.exposeHeaders || ["X-Request-Id"]).join(", ");
|
|
224
|
-
var credentials = !!opts.credentials;
|
|
225
|
-
var maxAge = String(opts.maxAgeSeconds || (C.TIME.minutes(10) / C.TIME.seconds(1)));
|
|
226
|
-
var refuseUnknown = opts.refuseUnknown !== false;
|
|
227
|
-
// strictNullOrigin defaults true: refuse Origin: null even with
|
|
228
|
-
// Sec-Fetch-Site: same-origin (non-browser callers can forge that
|
|
229
|
-
// header). Operators with a no-referrer page producing legitimate
|
|
230
|
-
// Origin: null on same-origin POSTs flip to false explicitly.
|
|
231
|
-
var strictNullOrigin = opts.strictNullOrigin !== false;
|
|
232
|
-
|
|
233
|
-
return function cors(req, res, next) {
|
|
234
|
-
var origin = req.headers && req.headers.origin;
|
|
235
|
-
if (!origin) return next(); // not a cross-origin request
|
|
236
|
-
|
|
237
|
-
// Same-origin POST/PUT/etc. carry an Origin header per the Fetch
|
|
238
|
-
// spec but should not be subject to CORS allow-listing — they're
|
|
239
|
-
// the operator's own site talking to itself.
|
|
240
|
-
if (_isSameOrigin(req, origin, siteOrigins, trustProxy, strictNullOrigin)) return next();
|
|
241
|
-
|
|
242
|
-
var matched = _matchOrigin(origin, origins);
|
|
243
|
-
if (!matched) {
|
|
244
|
-
if (refuseUnknown) {
|
|
245
|
-
try {
|
|
246
|
-
audit().emit({
|
|
247
|
-
actor: requestHelpers.extractActorContext(req, { ip: _xffIp(req) }),
|
|
248
|
-
action: "system.cors.block",
|
|
249
|
-
outcome: "denied",
|
|
250
|
-
reason: "origin not in allow-list",
|
|
251
|
-
metadata: { origin: origin, method: req.method, path: req.pathname || req.url, requestId: req.requestId },
|
|
252
|
-
requestId: req.requestId,
|
|
253
|
-
});
|
|
254
|
-
} catch (_e) { /* audit best-effort */ }
|
|
255
|
-
if (typeof res.writeHead === "function") {
|
|
256
|
-
res.writeHead(requestHelpers.HTTP_STATUS.FORBIDDEN, { "Content-Type": "text/plain" });
|
|
257
|
-
res.end("CORS: origin not allowed");
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return next(); // permissive mode: just don't set CORS headers
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
if (typeof res.setHeader === "function") {
|
|
265
|
-
res.setHeader("Access-Control-Allow-Origin", matched);
|
|
266
|
-
// Append "Origin" to Vary instead of overwriting — compression /
|
|
267
|
-
// auth helpers may have set their own Vary tokens that the cache
|
|
268
|
-
// layer needs to keep.
|
|
269
|
-
requestHelpers.appendVary(res, "Origin");
|
|
270
|
-
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
271
|
-
res.setHeader("Access-Control-Expose-Headers", exposeHeaders);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
if (req.method === "OPTIONS" && req.headers["access-control-request-method"]) {
|
|
275
|
-
// Preflight. In refuseUnknown mode, validate the requested
|
|
276
|
-
// headers against the configured allow-list — refuse with 403
|
|
277
|
-
// if the client asks for a header we don't allow. Spec says
|
|
278
|
-
// browsers enforce, but server-side enforcement keeps the
|
|
279
|
-
// framework's strict-by-default posture consistent.
|
|
280
|
-
if (refuseUnknown) {
|
|
281
|
-
var requestedHdrs = req.headers["access-control-request-headers"];
|
|
282
|
-
if (requestedHdrs) {
|
|
283
|
-
var asked = requestHelpers.parseListHeader(requestedHdrs, { lowercase: true });
|
|
284
|
-
for (var ah = 0; ah < asked.length; ah++) {
|
|
285
|
-
if (allowedHeadersSet.indexOf(asked[ah]) === -1) {
|
|
286
|
-
if (typeof res.writeHead === "function") {
|
|
287
|
-
res.writeHead(requestHelpers.HTTP_STATUS.FORBIDDEN, { "Content-Type": "text/plain" });
|
|
288
|
-
res.end("CORS: requested header '" + asked[ah] + "' not in allow-list");
|
|
289
|
-
}
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (typeof res.setHeader === "function") {
|
|
296
|
-
res.setHeader("Access-Control-Allow-Methods", methods);
|
|
297
|
-
res.setHeader("Access-Control-Allow-Headers", headers);
|
|
298
|
-
res.setHeader("Access-Control-Max-Age", maxAge);
|
|
299
|
-
}
|
|
300
|
-
if (typeof res.writeHead === "function") {
|
|
301
|
-
res.writeHead(requestHelpers.HTTP_STATUS.NO_CONTENT);
|
|
302
|
-
res.end();
|
|
303
|
-
}
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
next();
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
module.exports = {
|
|
312
|
-
create: create,
|
|
313
|
-
CorsError: CorsError,
|
|
314
|
-
};
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CORS middleware — allow-list-only by default. No '*' wildcard support
|
|
4
|
+
* for credentialed requests (which spec disallows anyway). Operators must
|
|
5
|
+
* explicitly enumerate origins they trust.
|
|
6
|
+
*
|
|
7
|
+
* Same-origin requests pass through without consulting the allow-list:
|
|
8
|
+
* the Fetch spec instructs browsers to send an Origin header on every
|
|
9
|
+
* POST / PUT / PATCH / DELETE — even same-origin ones — so an empty
|
|
10
|
+
* allow-list would otherwise refuse the operator's own login form.
|
|
11
|
+
* Same-origin is detected by:
|
|
12
|
+
* 1. Origin === opts.siteOrigin if configured (explicit, recommended —
|
|
13
|
+
* works behind TLS-terminating proxies where the framework can't
|
|
14
|
+
* infer scheme), or
|
|
15
|
+
* 2. Origin === request's inferred scheme/host/port (from req.socket
|
|
16
|
+
* and req.headers.host — correct for direct deployments).
|
|
17
|
+
*
|
|
18
|
+
* Origin: null shortcut (opt-in via strictNullOrigin: false): browsers
|
|
19
|
+
* send `Origin: null` on form-navigation POSTs from a page whose response
|
|
20
|
+
* carries `Referrer-Policy: no-referrer` (or similarly strict policy) —
|
|
21
|
+
* the Origin is opaqued to prevent a cross-site leak. The Sec-Fetch-Site
|
|
22
|
+
* Fetch-metadata signal distinguishes a same-origin nav from a genuine
|
|
23
|
+
* cross-site post in that opaque-origin world. Default is to REFUSE the
|
|
24
|
+
* shortcut (strictNullOrigin: true) — non-browser clients can forge
|
|
25
|
+
* Sec-Fetch-Site freely, and operators using `refuseUnknown: true` as a
|
|
26
|
+
* stricter "refuse unrecognized origin" policy expect the gate to hold.
|
|
27
|
+
* Operators with a no-referrer page that legitimately produces
|
|
28
|
+
* Origin: null on same-origin POSTs flip strictNullOrigin: false.
|
|
29
|
+
*
|
|
30
|
+
* Options:
|
|
31
|
+
* {
|
|
32
|
+
* origins: [ 'https://app.example.com', /^https:\/\/.+\.example\.com$/ ]
|
|
33
|
+
* siteOrigin: 'https://wiki.example.com' // string OR array
|
|
34
|
+
* methods: [ 'GET', 'POST', 'PUT', 'DELETE', 'PATCH' ]
|
|
35
|
+
* headers: [ 'Content-Type', 'Authorization', 'X-Request-Id' ]
|
|
36
|
+
* exposeHeaders: [ 'X-Request-Id', 'X-Response-Time' ]
|
|
37
|
+
* credentials: true (sets Access-Control-Allow-Credentials: true)
|
|
38
|
+
* maxAgeSeconds: 600
|
|
39
|
+
* refuseUnknown: true (refuse cross-origin requests from unlisted
|
|
40
|
+
* origins instead of just omitting CORS headers)
|
|
41
|
+
* strictNullOrigin: true (default — refuse Origin: null even with
|
|
42
|
+
* Sec-Fetch-Site: same-origin. Set false to
|
|
43
|
+
* allow the no-referrer-page edge case.)
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* Audit: refuseUnknown blocks emit system.cors.block with the offending Origin.
|
|
47
|
+
*
|
|
48
|
+
* Configuration validation: opts.siteOrigin must parse as an http(s) URL,
|
|
49
|
+
* opts.origins entries must be strings or RegExp. Bad config surfaces at
|
|
50
|
+
* create() not at first cross-origin request.
|
|
51
|
+
*/
|
|
52
|
+
var C = require("../constants");
|
|
53
|
+
var lazyRequire = require("../lazy-require");
|
|
54
|
+
var audit = lazyRequire(function () { return require("../audit"); });
|
|
55
|
+
var requestHelpers = require("../request-helpers");
|
|
56
|
+
var safeUrl = require("../safe-url");
|
|
57
|
+
var validateOpts = require("../validate-opts");
|
|
58
|
+
var { defineClass } = require("../framework-error");
|
|
59
|
+
|
|
60
|
+
// CORS audit events use the proxy-aware client IP only when the
|
|
61
|
+
// operator opts in via `trustProxy`. Default refuses forwarded
|
|
62
|
+
// headers — same boundary as the rest of the v0.5.3 trustProxy sweep.
|
|
63
|
+
function _xffIpFor(trustProxy) {
|
|
64
|
+
return function (req) {
|
|
65
|
+
return requestHelpers.clientIp(req, { trustProxy: trustProxy });
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
var CorsError = defineClass("CorsError", { alwaysPermanent: true });
|
|
70
|
+
|
|
71
|
+
// allowList entries:
|
|
72
|
+
// - { kind: "string", canonical: "https://app.example.com", original: "..." }
|
|
73
|
+
// - { kind: "regex", pattern: /.../ }
|
|
74
|
+
// Both raw entry and the inbound origin run through _canonicalOrigin
|
|
75
|
+
// before equality so case differences ("https://APP" vs "https://app")
|
|
76
|
+
// and default-port differences ("https://x:443" vs "https://x") match.
|
|
77
|
+
function _matchOrigin(origin, allowList) {
|
|
78
|
+
if (!origin) return null;
|
|
79
|
+
var canon = _canonicalOrigin(origin);
|
|
80
|
+
for (var i = 0; i < allowList.length; i++) {
|
|
81
|
+
var entry = allowList[i];
|
|
82
|
+
if (entry.kind === "string") {
|
|
83
|
+
if (canon !== null && entry.canonical === canon) return origin;
|
|
84
|
+
} else if (entry.kind === "regex") {
|
|
85
|
+
// Regex entries match against the raw origin (operator wrote the
|
|
86
|
+
// pattern with whatever case / port shape they intended). Also try
|
|
87
|
+
// the canonical form so case-insensitive intent works without /i.
|
|
88
|
+
if (entry.pattern.test(origin)) return origin;
|
|
89
|
+
if (canon !== null && entry.pattern.test(canon)) return origin;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Normalize an origin string by parsing it through safeUrl and
|
|
96
|
+
// returning `protocol//host[:port]` with no trailing slash. Used for
|
|
97
|
+
// equality checks that have to handle case differences and the URL
|
|
98
|
+
// parser's auto-port-omission for default ports (80/443).
|
|
99
|
+
//
|
|
100
|
+
// safeUrl defaults to ALLOW_HTTP_TLS (https only). CORS origins
|
|
101
|
+
// legitimately include http for local dev; pass ALLOW_HTTP_ALL.
|
|
102
|
+
function _canonicalOrigin(input) {
|
|
103
|
+
if (!input || typeof input !== "string") return null;
|
|
104
|
+
try {
|
|
105
|
+
var parsed = safeUrl.parse(input, { allowedProtocols: safeUrl.ALLOW_HTTP_ALL });
|
|
106
|
+
var proto = parsed.protocol; // "http:" or "https:"
|
|
107
|
+
var host = parsed.hostname.toLowerCase();
|
|
108
|
+
var port = parsed.port; // "" when default
|
|
109
|
+
return proto + "//" + host + (port ? ":" + port : "");
|
|
110
|
+
} catch (_e) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Build the request's own origin from req when opts.siteOrigin isn't
|
|
116
|
+
// supplied. Works for direct deployments (no proxy); operators behind
|
|
117
|
+
// a TLS-terminating proxy that doesn't forward correct Host should set
|
|
118
|
+
// opts.siteOrigin explicitly.
|
|
119
|
+
function _inferRequestOrigin(req, trustProxy) {
|
|
120
|
+
if (!req || !req.headers) return null;
|
|
121
|
+
var host = req.headers.host;
|
|
122
|
+
if (!host) return null;
|
|
123
|
+
// Protocol resolution honors the operator's trustProxy opt — without
|
|
124
|
+
// it, X-Forwarded-Proto is ignored as attacker-forgeable.
|
|
125
|
+
var proto = requestHelpers.requestProtocol(req, { trustProxy: trustProxy });
|
|
126
|
+
return _canonicalOrigin(proto + "://" + host);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function _isSameOrigin(req, originHeader, configuredSiteOrigins, trustProxy, strictNullOrigin) {
|
|
130
|
+
// Origin: null arrives when a browser opaques the Origin (e.g.
|
|
131
|
+
// Referrer-Policy: no-referrer on the page). Sec-Fetch-Site can
|
|
132
|
+
// distinguish the same-origin case, but non-browser clients can forge
|
|
133
|
+
// that header freely — strictNullOrigin: true (default) refuses the
|
|
134
|
+
// shortcut so refuseUnknown holds against forged callers. Operators
|
|
135
|
+
// with a legitimate no-referrer page flip strictNullOrigin: false.
|
|
136
|
+
if (originHeader === "null") {
|
|
137
|
+
if (strictNullOrigin) return false;
|
|
138
|
+
var sfs = req && req.headers && req.headers["sec-fetch-site"];
|
|
139
|
+
if (sfs === "same-origin" || sfs === "none") return true;
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
var canonOrigin = _canonicalOrigin(originHeader);
|
|
143
|
+
if (!canonOrigin) return false;
|
|
144
|
+
// Operator-supplied site origins take priority — they're the
|
|
145
|
+
// authoritative source for "this request is one of mine".
|
|
146
|
+
if (configuredSiteOrigins && configuredSiteOrigins.length > 0) {
|
|
147
|
+
for (var i = 0; i < configuredSiteOrigins.length; i++) {
|
|
148
|
+
if (configuredSiteOrigins[i] === canonOrigin) return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
// Fall back to inferring from the request itself. trustProxy threads
|
|
153
|
+
// through so operators behind a TLS terminator with X-Forwarded-Proto
|
|
154
|
+
// can opt in to consult the header.
|
|
155
|
+
var reqOrigin = _inferRequestOrigin(req, trustProxy);
|
|
156
|
+
return reqOrigin !== null && reqOrigin === canonOrigin;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function create(opts) {
|
|
160
|
+
opts = opts || {};
|
|
161
|
+
|
|
162
|
+
validateOpts(opts, [
|
|
163
|
+
"origins", "siteOrigin", "methods", "headers", "exposeHeaders",
|
|
164
|
+
"credentials", "maxAgeSeconds", "refuseUnknown", "trustProxy",
|
|
165
|
+
"strictNullOrigin",
|
|
166
|
+
], "middleware.cors");
|
|
167
|
+
var trustProxy = opts.trustProxy === true || typeof opts.trustProxy === "number"
|
|
168
|
+
? opts.trustProxy : false;
|
|
169
|
+
var _xffIp = _xffIpFor(trustProxy);
|
|
170
|
+
|
|
171
|
+
// Build a canonicalized allowList at create() time. String entries
|
|
172
|
+
// get parsed through _canonicalOrigin so case + default-port
|
|
173
|
+
// differences match consistently between the configured value and
|
|
174
|
+
// the inbound Origin header. RegExp entries stay as the operator
|
|
175
|
+
// wrote them.
|
|
176
|
+
var rawOrigins = opts.origins || [];
|
|
177
|
+
var origins = [];
|
|
178
|
+
for (var oi = 0; oi < rawOrigins.length; oi++) {
|
|
179
|
+
var entry = rawOrigins[oi];
|
|
180
|
+
if (typeof entry === "string") {
|
|
181
|
+
var canonEntry = _canonicalOrigin(entry);
|
|
182
|
+
if (canonEntry === null) {
|
|
183
|
+
throw new CorsError("cors/bad-origin",
|
|
184
|
+
"origins[" + oi + "]='" + entry + "' is not a parseable http(s) URL");
|
|
185
|
+
}
|
|
186
|
+
origins.push({ kind: "string", canonical: canonEntry, original: entry });
|
|
187
|
+
} else if (entry instanceof RegExp) {
|
|
188
|
+
origins.push({ kind: "regex", pattern: entry });
|
|
189
|
+
} else {
|
|
190
|
+
throw new CorsError("cors/bad-origin",
|
|
191
|
+
"origins[" + oi + "] must be a string or RegExp (got " + typeof entry + ")");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Throw at create() on bad opts.siteOrigin — must parse as http(s) URL.
|
|
196
|
+
// Accept string OR array of strings.
|
|
197
|
+
var siteOrigins = [];
|
|
198
|
+
if (opts.siteOrigin !== undefined && opts.siteOrigin !== null) {
|
|
199
|
+
var rawList = Array.isArray(opts.siteOrigin) ? opts.siteOrigin : [opts.siteOrigin];
|
|
200
|
+
for (var si = 0; si < rawList.length; si++) {
|
|
201
|
+
var raw = rawList[si];
|
|
202
|
+
if (typeof raw !== "string" || raw.length === 0) {
|
|
203
|
+
throw new CorsError("cors/bad-site-origin",
|
|
204
|
+
"siteOrigin[" + si + "] must be a non-empty string (got " + typeof raw + ")");
|
|
205
|
+
}
|
|
206
|
+
var canon = _canonicalOrigin(raw);
|
|
207
|
+
if (!canon) {
|
|
208
|
+
throw new CorsError("cors/bad-site-origin",
|
|
209
|
+
"siteOrigin[" + si + "]='" + raw + "' is not a parseable http(s) URL");
|
|
210
|
+
}
|
|
211
|
+
siteOrigins.push(canon);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
var methods = (opts.methods || ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).join(", ");
|
|
216
|
+
var headersList = (opts.headers || ["Content-Type", "Authorization", "X-Request-Id"]).slice();
|
|
217
|
+
var headers = headersList.join(", ");
|
|
218
|
+
// Pre-compute the lowercase allowlist once at create() time so the
|
|
219
|
+
// preflight path doesn't re-derive it per-request from the joined
|
|
220
|
+
// string. Avoids coupling allow-list validation to the wire-format
|
|
221
|
+
// serialization.
|
|
222
|
+
var allowedHeadersSet = headersList.map(function (h) { return String(h).trim().toLowerCase(); });
|
|
223
|
+
var exposeHeaders = (opts.exposeHeaders || ["X-Request-Id"]).join(", ");
|
|
224
|
+
var credentials = !!opts.credentials;
|
|
225
|
+
var maxAge = String(opts.maxAgeSeconds || (C.TIME.minutes(10) / C.TIME.seconds(1)));
|
|
226
|
+
var refuseUnknown = opts.refuseUnknown !== false;
|
|
227
|
+
// strictNullOrigin defaults true: refuse Origin: null even with
|
|
228
|
+
// Sec-Fetch-Site: same-origin (non-browser callers can forge that
|
|
229
|
+
// header). Operators with a no-referrer page producing legitimate
|
|
230
|
+
// Origin: null on same-origin POSTs flip to false explicitly.
|
|
231
|
+
var strictNullOrigin = opts.strictNullOrigin !== false;
|
|
232
|
+
|
|
233
|
+
return function cors(req, res, next) {
|
|
234
|
+
var origin = req.headers && req.headers.origin;
|
|
235
|
+
if (!origin) return next(); // not a cross-origin request
|
|
236
|
+
|
|
237
|
+
// Same-origin POST/PUT/etc. carry an Origin header per the Fetch
|
|
238
|
+
// spec but should not be subject to CORS allow-listing — they're
|
|
239
|
+
// the operator's own site talking to itself.
|
|
240
|
+
if (_isSameOrigin(req, origin, siteOrigins, trustProxy, strictNullOrigin)) return next();
|
|
241
|
+
|
|
242
|
+
var matched = _matchOrigin(origin, origins);
|
|
243
|
+
if (!matched) {
|
|
244
|
+
if (refuseUnknown) {
|
|
245
|
+
try {
|
|
246
|
+
audit().emit({
|
|
247
|
+
actor: requestHelpers.extractActorContext(req, { ip: _xffIp(req) }),
|
|
248
|
+
action: "system.cors.block",
|
|
249
|
+
outcome: "denied",
|
|
250
|
+
reason: "origin not in allow-list",
|
|
251
|
+
metadata: { origin: origin, method: req.method, path: req.pathname || req.url, requestId: req.requestId },
|
|
252
|
+
requestId: req.requestId,
|
|
253
|
+
});
|
|
254
|
+
} catch (_e) { /* audit best-effort */ }
|
|
255
|
+
if (typeof res.writeHead === "function") {
|
|
256
|
+
res.writeHead(requestHelpers.HTTP_STATUS.FORBIDDEN, { "Content-Type": "text/plain" });
|
|
257
|
+
res.end("CORS: origin not allowed");
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return next(); // permissive mode: just don't set CORS headers
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (typeof res.setHeader === "function") {
|
|
265
|
+
res.setHeader("Access-Control-Allow-Origin", matched);
|
|
266
|
+
// Append "Origin" to Vary instead of overwriting — compression /
|
|
267
|
+
// auth helpers may have set their own Vary tokens that the cache
|
|
268
|
+
// layer needs to keep.
|
|
269
|
+
requestHelpers.appendVary(res, "Origin");
|
|
270
|
+
if (credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
271
|
+
res.setHeader("Access-Control-Expose-Headers", exposeHeaders);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (req.method === "OPTIONS" && req.headers["access-control-request-method"]) {
|
|
275
|
+
// Preflight. In refuseUnknown mode, validate the requested
|
|
276
|
+
// headers against the configured allow-list — refuse with 403
|
|
277
|
+
// if the client asks for a header we don't allow. Spec says
|
|
278
|
+
// browsers enforce, but server-side enforcement keeps the
|
|
279
|
+
// framework's strict-by-default posture consistent.
|
|
280
|
+
if (refuseUnknown) {
|
|
281
|
+
var requestedHdrs = req.headers["access-control-request-headers"];
|
|
282
|
+
if (requestedHdrs) {
|
|
283
|
+
var asked = requestHelpers.parseListHeader(requestedHdrs, { lowercase: true });
|
|
284
|
+
for (var ah = 0; ah < asked.length; ah++) {
|
|
285
|
+
if (allowedHeadersSet.indexOf(asked[ah]) === -1) {
|
|
286
|
+
if (typeof res.writeHead === "function") {
|
|
287
|
+
res.writeHead(requestHelpers.HTTP_STATUS.FORBIDDEN, { "Content-Type": "text/plain" });
|
|
288
|
+
res.end("CORS: requested header '" + asked[ah] + "' not in allow-list");
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (typeof res.setHeader === "function") {
|
|
296
|
+
res.setHeader("Access-Control-Allow-Methods", methods);
|
|
297
|
+
res.setHeader("Access-Control-Allow-Headers", headers);
|
|
298
|
+
res.setHeader("Access-Control-Max-Age", maxAge);
|
|
299
|
+
}
|
|
300
|
+
if (typeof res.writeHead === "function") {
|
|
301
|
+
res.writeHead(requestHelpers.HTTP_STATUS.NO_CONTENT);
|
|
302
|
+
res.end();
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
next();
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = {
|
|
312
|
+
create: create,
|
|
313
|
+
CorsError: CorsError,
|
|
314
|
+
};
|