-
Notifications
You must be signed in to change notification settings - Fork 148
/
cors.js
152 lines (136 loc) · 5.1 KB
/
cors.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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"use strict";
const { escapeRegExp, includes } = require("lodash");
const xml2js = require("xml2js");
const templateBuilder = require("./xml-template-builder");
function createWildcardRegExp(str, flags = "") {
const parts = str.split("*");
if (parts.length > 2)
throw new Error(`"${str}" can not have more than one wildcard.`);
return new RegExp(`^${parts.map(escapeRegExp).join(".*")}$`, flags);
}
// See https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html
module.exports = function cors(config) {
// parse and validate config
let CORSConfiguration;
if (config) {
xml2js.parseString(config, { async: false }, (err, parsed) => {
if (!err) ({ CORSConfiguration } = parsed);
});
if (!CORSConfiguration || !CORSConfiguration.CORSRule) {
throw new Error(
"The CORS configuration XML you provided was not well-formed or did not validate"
);
}
for (const rule of CORSConfiguration.CORSRule) {
if (!rule.AllowedOrigin || !rule.AllowedMethod) {
throw new Error(
"CORSRule must have at least one AllowedOrigin and AllowedMethod"
);
}
// Keep track if the rule has the plain wildcard '*' origin since S3 responds with '*'
// instead of echoing back the request origin in this case
rule.hasWildcardOrigin = rule.AllowedOrigin.includes("*");
rule.AllowedOrigin = rule.AllowedOrigin.map(o => createWildcardRegExp(o));
rule.AllowedHeader = (rule.AllowedHeader || []).map(h =>
createWildcardRegExp(h, "i")
);
}
}
return function(req, res, next) {
// Prefer the Access-Control-Request-Method header if supplied
const method = req.get("access-control-request-method") || req.method;
const matchedRule = CORSConfiguration
? CORSConfiguration.CORSRule.find(rule => {
return (
rule.AllowedOrigin.some(pattern =>
pattern.test(req.get("origin"))
) && includes(rule.AllowedMethod, method)
);
})
: null;
if (req.method === "OPTIONS") {
let template;
if (!req.get("origin")) {
template = templateBuilder.buildError(
"BadRequest",
"Insufficient information. Origin request header needed."
);
res.header("Content-Type", "application/xml");
return res.status(403).send(template);
}
if (!req.get("access-control-request-method")) {
template = templateBuilder.buildError(
"BadRequest",
"Invalid Access-Control-Request-Method: null"
);
res.header("Content-Type", "application/xml");
return res.status(403).send(template);
}
// S3 only checks if CORS is enabled *after* checking the existence of access control headers
if (!CORSConfiguration) {
template = templateBuilder.buildError(
"CORSResponse",
"CORS is not enabled for this bucket."
);
res.header("Content-Type", "application/xml");
return res.status(403).send(template);
}
const requestHeaders = (req.get("access-control-request-headers") || "")
.split(",")
.filter(h => h);
const allowedHeaders = matchedRule
? requestHeaders
.map(header => header.trim().toLowerCase())
.filter(header =>
matchedRule.AllowedHeader.some(pattern => pattern.test(header))
)
: [];
if (!matchedRule || allowedHeaders.length < requestHeaders.length) {
template = templateBuilder.buildError(
"CORSResponse",
"This CORS request is not allowed. " +
"This is usually because the evalution of Origin, " +
"request method / Access-Control-Request-Method or Access-Control-Request-Headers " +
"are not whitelisted by the resource's CORS spec."
);
res.header("Content-Type", "application/xml");
return res.status(403).send(template);
}
res.header("Access-Control-Allow-Origin", "*");
res.header(
"Access-Control-Allow-Methods",
matchedRule.AllowedMethod.join(", ")
);
if (req.get("access-control-request-headers")) {
res.header("Access-Control-Allow-Headers", allowedHeaders.join(", "));
}
res.header(
"Vary",
"Origin, Access-Control-Request-Headers, Access-Control-Request-Method"
);
return res.status(200).send();
} else if (CORSConfiguration && req.get("origin")) {
if (matchedRule) {
res.header(
"Access-Control-Allow-Origin",
matchedRule.hasWildcardOrigin ? "*" : req.get("origin")
);
if (matchedRule.ExposeHeader) {
res.header(
"Access-Control-Expose-Headers",
matchedRule.ExposeHeader.join(", ")
);
}
if (matchedRule.MaxAgeSeconds) {
res.header("Access-Control-Max-Age", matchedRule.MaxAgeSeconds[0]);
}
res.header("Access-Control-Allow-Credentials", true);
res.header(
"Vary",
"Origin, Access-Control-Request-Headers, Access-Control-Request-Method"
);
}
}
next();
};
};