-
Notifications
You must be signed in to change notification settings - Fork 8
/
index.js
128 lines (113 loc) · 4.9 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
const url = require('url');
/**
* Called when we successfully gate a request. If the
* request is an OPTION request, terminate the request here.
* Otherwise, pass on to the next middleware.
*/
function success(req, res, next) {
if (req.method === 'OPTIONS') {
// If this is an OPTIONS request, terminate here.
res.statusCode = 204;
// Safari needs a content-length for 204, see https://github.com/expressjs/cors/blob/master/lib/index.js#L176
res.setHeader('Content-Length', '0');
res.end();
} else {
next();
}
}
/**
* Gate requests based on CORS data. For requests that are not permitted via CORS, invoke the
* failure options callback, which defaults to rejecting the request.
*
* @param {Object} options
* @param {String|Function} options.origin The origin of the server - requests from this origin will always
* proceed. A function can be passed here, that will be passed the requests origin.
* @param {Boolean=} options.strict Whether to reject requests that lack an Origin header. Defaults
* to true.
* @param {Boolean|Function=} options.allowSafe Whether to enforce the strict mode for safe requests (HEAD,
* GET). Defaults to true. A function can be passed here, that will be passed req and res and should return a boolean.
* @param {Function=} options.failure A standard connect-style callback for handling failure.
* Defaults to rejecting the request with 403 Unauthorized.
*/
function corsGate(options) {
options = Object.assign(
{
strict: true,
allowSafe: true,
failure(req, res, next) {
// Set `statusCode` vs. using `res#status`, as https://github.com/expressjs/cors does, so this
// will work with any Connect-compatible server.
res.statusCode = 403;
res.end();
},
},
options
);
if (typeof options.origin !== 'string' && typeof options.origin !== 'function') {
throw new Error("Must specify the server's origin.");
}
let allowOrigin = options.origin;
if (typeof allowOrigin !== 'function') {
allowOrigin = (origin) => origin === options.origin.toLowerCase();
}
const failure = options.failure;
return function(req, res, next) {
const origin = (req.headers.origin || '').toLowerCase().trim();
if (!origin) {
const allowSafe = !!(typeof options.allowSafe === 'function'
? options.allowSafe(req, res)
: options.allowSafe);
// Fail on missing origin when in strict mode, but allow safe requests if allowSafe set.
if (options.strict && (!allowSafe || ['GET', 'HEAD'].indexOf(req.method) === -1)) {
return void failure(req, res, next);
}
return void success(req, res, next);
}
// Always allow same-origin requests.
if (allowOrigin(origin)) return void success(req, res, next);
// Now this is a cross-origin request. Check if we should allow it based on headers set by
// previous CORS middleware. Note that `getHeader` is case-insensitive.
const otherOrigin = (res.getHeader('access-control-allow-origin') || '').toLowerCase().trim();
// Two values: allow any origin, or a specific origin.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Access-Control-Allow-Origin
if (otherOrigin === '*' || origin === otherOrigin) return void success(req, res, next);
// CSRF! Abort.
failure(req, res, next);
};
}
/**
* If the Origin header is missing, fill it with the origin part of the Referer.
*
* Firefox does not send the Origin header for same-origin requests, as of version 53. This is a
* documented bug, so this middleware enables verification of the Origin in that case. Additionally,
* no browser sends the Origin header when sending a GET request to load an image. We could simply
* allow all GET requests - GET requests are safe, per HTTP - but we'd rather reject unauthorized
* cross-origin GET requests wholesale.
*
* At present, Chrome and Safari do not support the strict-origin Referrer-Policy, so we can only
* patch the Origin from the Referer on Firefox. In patching it, however, we can reject unauthorized
* cross-origin GET requests from images, and once Chrome and Safari support strict-origin, we'll
* be able to do so on all three platforms.
*
* In order to actually reject these requests, however, the patched Origin data must be visible to
* the cors middleware. This middleware is distinct because it must appear before cors and corsGate
* to perform all the described tasks.
*/
function originFallbackToReferrer() {
return function(req, res, next) {
const origin = req.headers.origin;
if (!origin) {
const ref = req.headers.referer;
if (ref) {
const parts = url.parse(ref);
req.headers.origin = url.format({
protocol: parts.protocol,
host: parts.host,
});
}
}
next();
};
}
corsGate.originFallbackToReferrer = corsGate.originFallbackToReferer = originFallbackToReferrer;
module.exports = corsGate;