mirror of
https://github.com/scratchfoundation/restify-cors-middleware.git
synced 2024-12-18 11:52:26 -05:00
Support for * in the origin list for partial matches
This commit is contained in:
parent
2f91e63eff
commit
97a01584c3
5 changed files with 73 additions and 38 deletions
33
README.md
33
README.md
|
@ -16,20 +16,39 @@
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var corsMiddleware = require('restify-cors-middleware');
|
const corsMiddleware = require('restify-cors-middleware')
|
||||||
|
|
||||||
var cors = corsMiddleware({
|
const cors = corsMiddleware({
|
||||||
preflightMaxAge: 5, //Optional
|
preflightMaxAge: 5, //Optional
|
||||||
origins: ['http://api.myapp.com', 'http://web.myapp.com'],
|
origins: ['http://api.myapp.com', 'http://web.myapp.com'],
|
||||||
allowHeaders: ['API-Token'],
|
allowHeaders: ['API-Token'],
|
||||||
exposeHeaders: ['API-Token-Expiry']
|
exposeHeaders: ['API-Token-Expiry']
|
||||||
});
|
})
|
||||||
|
|
||||||
server.pre(cors.preflight);
|
server.pre(cors.preflight)
|
||||||
server.use(cors.actual);
|
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
|
## Compliance to the spec
|
||||||
|
|
||||||
See [unit tests](https://github.com/TabDigital/restify-cors-middleware/tree/master/test)
|
See [unit tests](https://github.com/TabDigital/restify-cors-middleware/tree/master/test) for examples of preflight and actual requests.
|
||||||
for examples of preflight and actual requests.
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ exports.handler = function (options) {
|
||||||
|
|
||||||
// If either no origin was set, or the origin isn't supported, continue
|
// If either no origin was set, or the origin isn't supported, continue
|
||||||
// without setting any headers
|
// without setting any headers
|
||||||
if (!originHeader || !origin.match(originHeader, options.origins)) {
|
if (!originHeader || !origin.allowed(options.origins || [], originHeader)) {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
exports.match = function (incomingOrigin, origins) {
|
|
||||||
if (!incomingOrigin) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < origins.length; i++) {
|
exports.allowed = function (list, requestOrigin) {
|
||||||
var origin = origins[i]
|
function match (origin) {
|
||||||
if ((origin instanceof RegExp && origin.test(incomingOrigin)) ||
|
if (origin.indexOf('*') !== -1) {
|
||||||
(typeof origin === 'string' && origin === incomingOrigin) ||
|
var regex = '^' + origin.replace('.', '\\.').replace('*', '.*') + '$'
|
||||||
(origin === '*')) {
|
return requestOrigin.match(regex)
|
||||||
return incomingOrigin
|
} else {
|
||||||
|
return requestOrigin === origin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (requestOrigin) {
|
||||||
return null
|
return list.some(match)
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ exports.handler = function (options) {
|
||||||
|
|
||||||
// 6.2.1 and 6.2.2
|
// 6.2.1 and 6.2.2
|
||||||
var originHeader = req.headers['origin']
|
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
|
// 6.2.3
|
||||||
var requestedMethod = req.headers[constants['AC_REQ_METHOD']]
|
var requestedMethod = req.headers[constants['AC_REQ_METHOD']]
|
||||||
|
|
|
@ -3,28 +3,44 @@ require('should')
|
||||||
var origin = require('../src/origin')
|
var origin = require('../src/origin')
|
||||||
|
|
||||||
describe('Origin list', function () {
|
describe('Origin list', function () {
|
||||||
var list = [
|
it('returns false if the request has no origin', function () {
|
||||||
'http://api.myapp.com',
|
var list = ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
'http://www.myapp.com'
|
origin.allowed(list, null).should.eql(false)
|
||||||
]
|
|
||||||
|
|
||||||
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('does not do partial matches', function () {
|
it('returns false if the origin is not in the list', function () {
|
||||||
var o = origin.match('api.myapp.com', list);
|
var list = ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
(o === null).should.eql(true)
|
origin.allowed(list, 'http://random-website.com').should.eql(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the origin if it matched', function () {
|
it('returns true if the origin matched', function () {
|
||||||
var o = origin.match('http://api.myapp.com', list)
|
var list = ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
o.should.eql('http://api.myapp.com')
|
origin.allowed(list, 'http://api.myapp.com').should.eql(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns the origin if the list contains *', function () {
|
it('does not do partial matches by default', function () {
|
||||||
var o = origin.match('http://random-website.com', ['*'])
|
var list = ['http://api.myapp.com', 'http://www.myapp.com']
|
||||||
o.should.eql('http://random-website.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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue