Support for * in the origin list for partial matches

This commit is contained in:
Romain Prieto 2014-06-03 13:19:10 +10:00 committed by Romain
parent 2f91e63eff
commit 97a01584c3
5 changed files with 73 additions and 38 deletions

View file

@ -16,20 +16,39 @@
## Usage
```js
var corsMiddleware = require('restify-cors-middleware');
const corsMiddleware = require('restify-cors-middleware')
var cors = corsMiddleware({
const cors = corsMiddleware({
preflightMaxAge: 5, //Optional
origins: ['http://api.myapp.com', 'http://web.myapp.com'],
allowHeaders: ['API-Token'],
exposeHeaders: ['API-Token-Expiry']
});
})
server.pre(cors.preflight);
server.use(cors.actual);
server.pre(cors.preflight)
server.use(cors.actual)
```
## Allowed origins
You can specify the full list of domains and subdomains allowed in your application:
```js
origins: [
'http://myapp.com',
'http://*.myapp.com'
]
```
For added security, this middleware sets `Access-Control-Allow-Origin` to the origin that matched, not the configured wildcard.
This means callers won't know about other domains that are supported.
Setting `origins: ['*']` is also valid, although it comes with obvious security implications. Note that it will still return a customised response (matching Origin), so any caching layer (reverse proxy or CDN) will grow in size accordingly.
## Troubleshooting
As per the spec, requests without an `Origin` will not receive any headers. Requests with a matching `Origin` will receive the appropriate response headers. Always be careful that any reverse proxies (e.g. Varnish) very their cache depending on the origin, so you don't serve CORS headers to the wrong request.
## Compliance to the spec
See [unit tests](https://github.com/TabDigital/restify-cors-middleware/tree/master/test)
for examples of preflight and actual requests.
See [unit tests](https://github.com/TabDigital/restify-cors-middleware/tree/master/test) for examples of preflight and actual requests.

View file

@ -7,7 +7,7 @@ exports.handler = function (options) {
// If either no origin was set, or the origin isn't supported, continue
// without setting any headers
if (!originHeader || !origin.match(originHeader, options.origins)) {
if (!originHeader || !origin.allowed(options.origins || [], originHeader)) {
return next()
}

View file

@ -1,16 +1,16 @@
exports.match = function (incomingOrigin, origins) {
if (!incomingOrigin) {
return null
}
for (var i = 0; i < origins.length; i++) {
var origin = origins[i]
if ((origin instanceof RegExp && origin.test(incomingOrigin)) ||
(typeof origin === 'string' && origin === incomingOrigin) ||
(origin === '*')) {
return incomingOrigin
exports.allowed = function (list, requestOrigin) {
function match (origin) {
if (origin.indexOf('*') !== -1) {
var regex = '^' + origin.replace('.', '\\.').replace('*', '.*') + '$'
return requestOrigin.match(regex)
} else {
return requestOrigin === origin
}
}
return null
if (requestOrigin) {
return list.some(match)
} else {
return false
}
}

View file

@ -7,7 +7,7 @@ exports.handler = function (options) {
// 6.2.1 and 6.2.2
var originHeader = req.headers['origin']
if (origin.match(originHeader, options.origins) === false) return next()
if (origin.allowed(options.origins, originHeader) === false) return next()
// 6.2.3
var requestedMethod = req.headers[constants['AC_REQ_METHOD']]

View file

@ -3,28 +3,44 @@ require('should')
var origin = require('../src/origin')
describe('Origin list', function () {
var list = [
'http://api.myapp.com',
'http://www.myapp.com'
]
it('returns null if the origin is not in the list', function () {
var o = origin.match('http://random-website.com', list);
(o === null).should.eql(true)
it('returns false if the request has no origin', function () {
var list = ['http://api.myapp.com', 'http://www.myapp.com']
origin.allowed(list, null).should.eql(false)
})
it('does not do partial matches', function () {
var o = origin.match('api.myapp.com', list);
(o === null).should.eql(true)
it('returns false if the origin is not in the list', function () {
var list = ['http://api.myapp.com', 'http://www.myapp.com']
origin.allowed(list, 'http://random-website.com').should.eql(false)
})
it('returns the origin if it matched', function () {
var o = origin.match('http://api.myapp.com', list)
o.should.eql('http://api.myapp.com')
it('returns true if the origin matched', function () {
var list = ['http://api.myapp.com', 'http://www.myapp.com']
origin.allowed(list, 'http://api.myapp.com').should.eql(true)
})
it('returns the origin if the list contains *', function () {
var o = origin.match('http://random-website.com', ['*'])
o.should.eql('http://random-website.com')
it('does not do partial matches by default', function () {
var list = ['http://api.myapp.com', 'http://www.myapp.com']
origin.allowed(list, 'api.myapp.com').should.eql(false)
})
it('always matches if the list contains *', function () {
var list = ['*']
origin.allowed(list, 'http://random-website.com').should.eql(true)
})
it('supports * for partial matches', function () {
var list = ['http://*.myapp.com', 'http://other-website.com']
origin.allowed(list, 'http://api.myapp.com').should.eql(true)
})
it('escapes the partial regex properly', function () {
// the "." should be a real dot, not mean "[any character]myapp"
var list = ['http://*.myapp.com', 'http://other-website.com']
origin.allowed(list, 'http://xmyapp.com').should.eql(false)
})
it('returns false if there was no partial match', function () {
var list = ['http://*.myapp.com']
origin.allowed(list, 'http://random-website.com').should.eql(false)
})
})