Skip to content

Commit cece789

Browse files
committed
Rework CORS. Closes hapijs#2840
1 parent 7283931 commit cece789

File tree

7 files changed

+237
-322
lines changed

7 files changed

+237
-322
lines changed

API.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,13 +2143,6 @@ following options:
21432143
The array can contain any combination of fully qualified origins along with origin
21442144
strings containing a wildcard '*' character, or a single `'*'` origin string. Defaults
21452145
to any origin `['*']`.
2146-
- `matchOrigin` - if `true`, matches the value of the incoming 'Origin' header to the
2147-
list of `origin` values ('*' matches anything) and if a match is found, uses that as
2148-
the value of the 'Access-Control-Allow-Origin' response header. When false, the
2149-
`origin` config is returned as-is. Defaults to `true`.
2150-
- `isOriginExposed` - if `false`, prevents the connection from returning the full list
2151-
of non-wildcard `origin` values if the incoming origin header does not match any of
2152-
the values. Has no impact if `matchOrigin` is set to `false`. Defaults to `true`.
21532146
- `maxAge` - number of seconds the browser should cache the CORS response
21542147
('Access-Control-Max-Age'). The greater the value, the longer it will take before the
21552148
browser checks for changes in policy. Defaults to `86400` (one day).
@@ -2164,9 +2157,6 @@ following options:
21642157
`exposedHeaders`. Use this to keep the default headers in place.
21652158
- `credentials` - if `true`, allows user credentials to be sent
21662159
('Access-Control-Allow-Credentials'). Defaults to `false`.
2167-
- `override` - if `false`, preserves existing CORS headers set manually before the
2168-
response is sent. If set to `'merge'`, appends the configured values to the manually set
2169-
headers (applies only to Access-Control-Expose-Headers). Defaults to `true`.
21702160

21712161
- `ext` - defined a route-level [request extension points](#request-lifecycle) by setting
21722162
the option to an object with a key for each of the desired extension points (`'onRequest'`

lib/cors.js

Lines changed: 75 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -22,105 +22,28 @@ exports.route = function (options) {
2222
settings._headersString = settings._headers.join(',');
2323
settings._exposedHeaders = settings.exposedHeaders.concat(settings.additionalExposedHeaders).join(',');
2424

25-
if (settings.origin.length) {
25+
if (settings.origin.indexOf('*') !== -1) {
26+
Hoek.assert(settings.origin.length === 1, 'Cannot specify cors.origin * together with other values');
27+
settings._origin = true;
28+
}
29+
else {
2630
settings._origin = {
27-
any: false,
2831
qualified: [],
29-
qualifiedString: '',
3032
wildcards: []
3133
};
3234

33-
if (settings.origin.indexOf('*') !== -1) {
34-
Hoek.assert(settings.origin.length === 1, 'Cannot specify cors.origin * together with other values');
35-
settings._origin.any = true;
36-
}
37-
else {
38-
for (var c = 0, cl = settings.origin.length; c < cl; ++c) {
39-
var origin = settings.origin[c];
40-
if (origin.indexOf('*') !== -1) {
41-
settings._origin.wildcards.push(new RegExp('^' + Hoek.escapeRegex(origin).replace(/\\\*/g, '.*').replace(/\\\?/g, '.') + '$'));
42-
}
43-
else {
44-
settings._origin.qualified.push(origin);
45-
}
35+
for (var c = 0, cl = settings.origin.length; c < cl; ++c) {
36+
var origin = settings.origin[c];
37+
if (origin.indexOf('*') !== -1) {
38+
settings._origin.wildcards.push(new RegExp('^' + Hoek.escapeRegex(origin).replace(/\\\*/g, '.*').replace(/\\\?/g, '.') + '$'));
4639
}
47-
48-
Hoek.assert(settings.matchOrigin || !settings._origin.wildcards.length, 'Cannot include wildcard origin values with matchOrigin disabled');
49-
settings._origin.qualifiedString = settings._origin.qualified.join(' ');
50-
}
51-
}
52-
53-
return settings;
54-
};
55-
56-
57-
exports.headers = function (response, options) {
58-
59-
var request = response.request;
60-
var settings = options || request.route.settings.cors;
61-
if (!settings) {
62-
return;
63-
}
64-
65-
if (settings._origin &&
66-
(!response.headers['access-control-allow-origin'] || settings.override)) {
67-
68-
if (settings.matchOrigin) {
69-
response.vary('origin');
70-
if (internals.matchOrigin(request.headers.origin, settings)) {
71-
response._header('access-control-allow-origin', request.headers.origin);
72-
}
73-
else if (settings.isOriginExposed) {
74-
response._header('access-control-allow-origin', settings._origin.any ? '*' : settings._origin.qualifiedString);
40+
else {
41+
settings._origin.qualified.push(origin);
7542
}
7643
}
77-
else if (settings._origin.any) {
78-
response._header('access-control-allow-origin', '*');
79-
}
80-
else {
81-
response._header('access-control-allow-origin', settings._origin.qualifiedString);
82-
}
83-
}
84-
85-
var config = { override: !!settings.override }; // Value can be 'merge'
86-
87-
if (settings.credentials) {
88-
response._header('access-control-allow-credentials', 'true', { override: settings.override });
89-
}
90-
91-
// Appended headers
92-
93-
if (settings.override === 'merge') {
94-
config.append = true;
95-
}
96-
97-
if (settings._exposedHeaders.length !== 0) {
98-
response._header('access-control-expose-headers', settings._exposedHeaders, config);
99-
}
100-
};
101-
102-
103-
internals.matchOrigin = function (origin, settings) {
104-
105-
if (!origin) {
106-
return false;
107-
}
108-
109-
if (settings._origin.any) {
110-
return true;
111-
}
112-
113-
if (settings._origin.qualified.indexOf(origin) !== -1) {
114-
return true;
115-
}
116-
117-
for (var i = 0, il = settings._origin.wildcards.length; i < il; ++i) {
118-
if (origin.match(settings._origin.wildcards[i])) {
119-
return true;
120-
}
12144
}
12245

123-
return false;
46+
return settings;
12447
};
12548

12649

@@ -184,6 +107,12 @@ internals.handler = function (request, reply) {
184107
return reply(Boom.notFound());
185108
}
186109

110+
// Validate Origin header
111+
112+
if (!internals.matchOrigin(origin, settings)) {
113+
return reply(Boom.notFound());
114+
}
115+
187116
// Validate allowed headers
188117

189118
var headers = request.headers['access-control-request-headers'];
@@ -197,8 +126,64 @@ internals.handler = function (request, reply) {
197126
// Reply with the route CORS headers
198127

199128
var response = reply();
200-
exports.headers(response, settings);
129+
response._header('access-control-allow-origin', request.headers.origin);
201130
response._header('access-control-allow-methods', method);
202131
response._header('access-control-allow-headers', settings._headersString);
203132
response._header('access-control-max-age', settings.maxAge);
133+
134+
if (settings.credentials) {
135+
response._header('access-control-allow-credentials', 'true');
136+
}
137+
138+
if (settings._exposedHeaders) {
139+
response._header('access-control-expose-headers', settings._exposedHeaders);
140+
}
141+
};
142+
143+
144+
exports.headers = function (response) {
145+
146+
var request = response.request;
147+
var settings = request.route.settings.cors;
148+
if (!settings) {
149+
return;
150+
}
151+
152+
response.vary('origin');
153+
154+
if (!request.headers.origin ||
155+
!internals.matchOrigin(request.headers.origin, settings)) {
156+
157+
return;
158+
}
159+
160+
response._header('access-control-allow-origin', request.headers.origin);
161+
162+
if (settings.credentials) {
163+
response._header('access-control-allow-credentials', 'true');
164+
}
165+
166+
if (settings._exposedHeaders) {
167+
response._header('access-control-expose-headers', settings._exposedHeaders, { append: true });
168+
}
169+
};
170+
171+
172+
internals.matchOrigin = function (origin, settings) {
173+
174+
if (settings._origin === true) {
175+
return true;
176+
}
177+
178+
if (settings._origin.qualified.indexOf(origin) !== -1) {
179+
return true;
180+
}
181+
182+
for (var i = 0, il = settings._origin.wildcards.length; i < il; ++i) {
183+
if (origin.match(settings._origin.wildcards[i])) {
184+
return true;
185+
}
186+
}
187+
188+
return false;
204189
};

lib/defaults.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,6 @@ exports.security = {
7979

8080
exports.cors = {
8181
origin: ['*'],
82-
isOriginExposed: true, // Return the list of supported origins if incoming origin does not match
83-
matchOrigin: true, // Attempt to match incoming origin against allowed values and return narrow response
8482
maxAge: 86400, // One day
8583
headers: [
8684
'Authorization',
@@ -93,6 +91,5 @@ exports.cors = {
9391
'Server-Authorization'
9492
],
9593
additionalExposedHeaders: [],
96-
credentials: false,
97-
override: true
94+
credentials: false
9895
};

lib/schema.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,16 +76,13 @@ internals.routeBase = Joi.object({
7676
statuses: Joi.array().items(Joi.number().integer().min(200)).min(1)
7777
}),
7878
cors: Joi.object({
79-
origin: Joi.array(),
80-
matchOrigin: Joi.boolean(),
81-
isOriginExposed: Joi.boolean(),
79+
origin: Joi.array().min(1),
8280
maxAge: Joi.number(),
8381
headers: Joi.array(),
8482
additionalHeaders: Joi.array(),
8583
exposedHeaders: Joi.array(),
8684
additionalExposedHeaders: Joi.array(),
87-
credentials: Joi.boolean(),
88-
override: Joi.boolean().allow('merge')
85+
credentials: Joi.boolean()
8986
})
9087
.allow(false, true),
9188
ext: Joi.object({

0 commit comments

Comments
 (0)