Skip to content

Commit

Permalink
Return only a single origin if matches (#3)
Browse files Browse the repository at this point in the history
* 🐛 Return only a single allowed origin if matches

Improve checking for given options to not return headers with empty values
Add more tests
Add Node 12 to engines in package.json

* 0.2.0
  • Loading branch information
domeq authored Nov 19, 2020
1 parent ef6e1fe commit 542dd1b
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 18 deletions.
24 changes: 17 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
const R = require('ramda');

const getCorsHeaders = ({ allowedOrigins, exposeHeaders, maxAge, credentials, allowMethods, allowHeaders } = {}) => {
const getCorsHeaders = (
{ allowedOrigins, exposeHeaders, maxAge, credentials, allowMethods, allowHeaders } = {},
event
) => {
const headers = {};

if (allowedOrigins) {
headers['access-control-allow-origin'] = allowedOrigins.join(', ');
const isOriginAllowed = R.find(
(allowedOrigin) => allowedOrigin === R.path(['headers', 'origin'], event),
allowedOrigins
);

if (isOriginAllowed) {
headers['access-control-allow-origin'] = isOriginAllowed;
}
}

if (credentials !== undefined) {
headers['access-control-allow-credentials'] = credentials;
}

if (exposeHeaders) {
if (!R.isNil(exposeHeaders) && !R.isEmpty(exposeHeaders)) {
headers['access-control-expose-headers'] = exposeHeaders.join(', ');
}
if (maxAge !== undefined) {
headers['access-control-max-age'] = maxAge;
}
if (allowMethods) {
if (!R.isNil(allowMethods) && !R.isEmpty(allowMethods)) {
headers['access-control-allow-methods'] = allowMethods.join(', ');
}
if (allowHeaders) {
if (!R.isNil(allowHeaders) && !R.isEmpty(allowHeaders)) {
headers['access-control-allow-headers'] = allowHeaders.join(', ');
}

Expand All @@ -32,7 +42,7 @@ const corsMiddleware = (opts) => ({
// eslint-disable-next-line no-param-reassign
handler.response.headers = {
...handler.response.headers,
...getCorsHeaders(opts),
...getCorsHeaders(opts, handler.event),
};
},
onError: async (handler) => {
Expand All @@ -41,7 +51,7 @@ const corsMiddleware = (opts) => ({
['headers'],
{
...R.pathOr({}, ['response', 'headers'], handler),
...getCorsHeaders(opts),
...getCorsHeaders(opts, handler.event),
},
handler.response
);
Expand Down
125 changes: 116 additions & 9 deletions index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('Does nothing if has no config', async () => {
});
});

test('Adds CORS headers on success', async () => {
test('Adds CORS headers on success from disallowed origin', async () => {
const handler = middy(async () => ({
statusCode: 200,
body: JSON.stringify({ foo: 'bar' }),
Expand All @@ -43,14 +43,63 @@ test('Adds CORS headers on success', async () => {
})
);

const response = await handler({}, {});
const response = await handler(
{
headers: {
origin: 'https://www.google.com',
},
},
{}
);
expect(response).toEqual({
statusCode: 200,
headers: {
'access-control-allow-credentials': true,
'access-control-allow-headers': 'Content-Type, Accept, X-Forwarded-For',
'access-control-allow-methods': 'GET, POST',
'access-control-expose-headers': 'x-my-header',
'access-control-max-age': 2628000,
someHeader: 'someValue',
},
body: JSON.stringify({ foo: 'bar' }),
});
});

test('Adds CORS headers on success from allowed origin', async () => {
const handler = middy(async () => ({
statusCode: 200,
body: JSON.stringify({ foo: 'bar' }),
headers: {
someHeader: 'someValue',
},
}));

handler.use(
middleware({
allowedOrigins: ['https://www.vg.no', 'https://www.tek.no'],
exposeHeaders: ['x-my-header'],
maxAge: 2628000, // 1 month
credentials: true,
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'Accept', 'X-Forwarded-For'],
})
);

const response = await handler(
{
headers: {
origin: 'https://www.tek.no',
},
},
{}
);
expect(response).toEqual({
statusCode: 200,
headers: {
'access-control-allow-credentials': true,
'access-control-allow-headers': 'Content-Type, Accept, X-Forwarded-For',
'access-control-allow-methods': 'GET, POST',
'access-control-allow-origin': 'https://www.vg.no, https://www.tek.no',
'access-control-allow-origin': 'https://www.tek.no',
'access-control-expose-headers': 'x-my-header',
'access-control-max-age': 2628000,
someHeader: 'someValue',
Expand All @@ -59,7 +108,47 @@ test('Adds CORS headers on success', async () => {
});
});

test('Adds CORS headers on error', async () => {
test('Adds CORS headers on error from disallowed origin', async () => {
const handler = middy(async () => {
throw new createError.InternalServerError('whoops');
});

handler.use(
middleware({
allowedOrigins: ['https://www.vg.no', 'https://www.tek.no'],
exposeHeaders: ['x-my-header'],
maxAge: 2628000, // 1 month
credentials: true,
allowMethods: ['GET', 'POST'],
allowHeaders: ['Content-Type', 'Accept', 'X-Forwarded-For'],
})
);

await expect(
handler(
{
headers: {
origin: 'https://www.google.com',
},
},
{}
)
).rejects.toEqual(
expect.objectContaining({
response: {
headers: {
'access-control-allow-credentials': true,
'access-control-allow-headers': 'Content-Type, Accept, X-Forwarded-For',
'access-control-allow-methods': 'GET, POST',
'access-control-expose-headers': 'x-my-header',
'access-control-max-age': 2628000,
},
},
})
);
});

test('Adds CORS headers on error from allowed origin', async () => {
const handler = middy(async () => {
throw new createError.InternalServerError('whoops');
});
Expand All @@ -75,14 +164,23 @@ test('Adds CORS headers on error', async () => {
})
);

await expect(handler({}, {})).rejects.toEqual(
await expect(
handler(
{
headers: {
origin: 'https://www.tek.no',
},
},
{}
)
).rejects.toEqual(
expect.objectContaining({
response: {
headers: {
'access-control-allow-credentials': true,
'access-control-allow-headers': 'Content-Type, Accept, X-Forwarded-For',
'access-control-allow-methods': 'GET, POST',
'access-control-allow-origin': 'https://www.vg.no, https://www.tek.no',
'access-control-allow-origin': 'https://www.tek.no',
'access-control-expose-headers': 'x-my-header',
'access-control-max-age': 2628000,
},
Expand All @@ -91,7 +189,7 @@ test('Adds CORS headers on error', async () => {
);
});

test('Keep headers already present in the response on error', async () => {
test('Keep headers already present in the response on error from disallowed origin', async () => {
const handler = middy(async () => {
throw new createError.InternalServerError('whoops');
});
Expand Down Expand Up @@ -119,14 +217,23 @@ test('Keep headers already present in the response on error', async () => {
})
);

await expect(handler({}, {})).rejects.toEqual(
await expect(
handler(
{
headers: {
origin: 'https://www.tek.no',
},
},
{}
)
).rejects.toEqual(
expect.objectContaining({
response: {
headers: {
'access-control-allow-credentials': true,
'access-control-allow-headers': 'Content-Type, Accept, X-Forwarded-For',
'access-control-allow-methods': 'GET, POST',
'access-control-allow-origin': 'https://www.vg.no, https://www.tek.no',
'access-control-allow-origin': 'https://www.tek.no',
'access-control-expose-headers': 'x-my-header',
'access-control-max-age': 2628000,
someHeader: 'someValue',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"name": "@schibsted/middy-cors",
"version": "0.1.0",
"version": "0.2.0",
"description": "Middy middleware for adding CORS headers to success response and errors",
"main": "index.js",
"engines": {
"node": "12"
},
"scripts": {
"lint": "eslint .",
"lint-fix": "eslint . --fix",
Expand Down

0 comments on commit 542dd1b

Please sign in to comment.