CorsHandler: Finished implementing a full CORS support
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
All checks were successful
continuous-integration/appveyor/branch AppVeyor build succeeded
This commit is contained in:
parent
7b682e8e95
commit
acb099868b
2 changed files with 397 additions and 70 deletions
53
flaska.mjs
53
flaska.mjs
|
@ -132,22 +132,59 @@ export function JsonHandler(org = {}) {
|
|||
export function CorsHandler(opts = {}) {
|
||||
const options = {
|
||||
allowedMethods: opts.allowedMethods || 'GET,HEAD,PUT,POST,DELETE,PATCH',
|
||||
allowedOrigins: opts.allowedOrigins || [],
|
||||
allowedHeaders: opts.allowedHeaders,
|
||||
openerPolicy: 'same-origin',
|
||||
resourcePolicy: 'same-origin',
|
||||
embedderPolicy: 'require-corp',
|
||||
credentials: opts.credentials || false,
|
||||
exposeHeaders: opts.exposeHeaders || '',
|
||||
maxAge: opts.maxAge || '',
|
||||
}
|
||||
|
||||
return function(ctx) {
|
||||
let origin = ctx.req.headers['origin']
|
||||
let reqHeaders = options.allowedHeaders || ctx.req.headers['access-control-request-headers']
|
||||
// Always add vary header on origin. Prevent caches from
|
||||
// accidentally caching wrong preflight request
|
||||
ctx.headers['Vary'] = 'Origin'
|
||||
|
||||
ctx.headers['Access-Control-Allow-Origin'] = origin
|
||||
ctx.headers['Access-Control-Allow-Methods'] = options.allowedMethods
|
||||
// Set status to 204 if OPTIONS. Just handy for flaska and
|
||||
// other checking.
|
||||
if (ctx.method === 'OPTIONS') {
|
||||
ctx.status = 204
|
||||
}
|
||||
|
||||
// Check origin is specified. Nothing needs to be done if
|
||||
// there is no origin or it doesn't match
|
||||
let origin = ctx.req.headers['origin']
|
||||
if (!origin || !options.allowedOrigins.includes(origin)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Set some extra headers if this is a pre-flight. Most of
|
||||
// these are not needed during a normal request.
|
||||
if (ctx.method === 'OPTIONS') {
|
||||
if (!ctx.req.headers['access-control-request-method']) {
|
||||
return
|
||||
}
|
||||
|
||||
if (options.maxAge) {
|
||||
ctx.headers['Access-Control-Max-Age'] = options.maxAge
|
||||
}
|
||||
|
||||
let reqHeaders = options.allowedHeaders
|
||||
|| ctx.req.headers['access-control-request-headers']
|
||||
if (reqHeaders && options.allowedHeaders !== false) {
|
||||
ctx.headers['Access-Control-Allow-Headers'] = reqHeaders
|
||||
}
|
||||
ctx.status = 204
|
||||
ctx.headers['Access-Control-Allow-Methods'] = options.allowedMethods
|
||||
} else {
|
||||
if (options.exposeHeaders) {
|
||||
ctx.headers['Access-Control-Expose-Headers'] = options.exposeHeaders
|
||||
}
|
||||
}
|
||||
|
||||
ctx.headers['Access-Control-Allow-Origin'] = origin
|
||||
|
||||
if (options.credentials) {
|
||||
ctx.headers['Access-Control-Allow-Credentials'] = 'true'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,15 +34,17 @@ t.describe('#CorsHandler()', function() {
|
|||
let corsHandler
|
||||
let ctx
|
||||
|
||||
t.beforeEach(function() {
|
||||
ctx = createCtx()
|
||||
})
|
||||
|
||||
t.test('should return a handler', function() {
|
||||
corsHandler = CorsHandler()
|
||||
assert.strictEqual(typeof(corsHandler), 'function')
|
||||
})
|
||||
|
||||
t.describe('OPTIONS', function() {
|
||||
t.beforeEach(function() {
|
||||
ctx = createCtx()
|
||||
ctx.method = 'OPTIONS'
|
||||
})
|
||||
|
||||
t.test('should set status and headers', function() {
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
const assertRequestHeaders = 'asdf,foobar'
|
||||
|
@ -50,20 +52,80 @@ t.describe('#CorsHandler()', function() {
|
|||
corsHandler = CorsHandler({
|
||||
allowedOrigins: [assertOrigin],
|
||||
})
|
||||
ctx.method = 'OPTIONS'
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = assertRequestHeaders
|
||||
|
||||
assert.notOk(ctx.headers['Vary'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Methods'], 'GET,HEAD,PUT,POST,DELETE,PATCH')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Headers'], assertRequestHeaders)
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Credentials'])
|
||||
assert.notOk(ctx.headers['Access-Control-Max-Age'])
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
|
||||
t.test('should set Allow-Credentials if credentials is specified', function() {
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
const assertRequestHeaders = 'asdf,foobar'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: [assertOrigin],
|
||||
credentials: true,
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = assertRequestHeaders
|
||||
|
||||
assert.notOk(ctx.headers['Vary'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Credentials'], 'true')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Methods'], 'GET,HEAD,PUT,POST,DELETE,PATCH')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Headers'], assertRequestHeaders)
|
||||
assert.notOk(ctx.headers['Access-Control-Max-Age'])
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
|
||||
t.test('should set Max-Age if maxAge is specified', function() {
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
const assertRequestHeaders = 'asdf,foobar'
|
||||
const assertMaxAge = '600'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: [assertOrigin],
|
||||
maxAge: assertMaxAge,
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = assertRequestHeaders
|
||||
|
||||
assert.notOk(ctx.headers['Vary'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Max-Age'], assertMaxAge)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Methods'], 'GET,HEAD,PUT,POST,DELETE,PATCH')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Headers'], assertRequestHeaders)
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Credentials'])
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
|
||||
|
@ -77,7 +139,6 @@ t.describe('#CorsHandler()', function() {
|
|||
allowedMethods: assertAllowedMethods,
|
||||
allowedHeaders: assertAllowedHeaders,
|
||||
})
|
||||
ctx.method = 'OPTIONS'
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = 'asdfasdfasdfsfad'
|
||||
|
@ -88,6 +149,8 @@ t.describe('#CorsHandler()', function() {
|
|||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Credentials'])
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Methods'], assertAllowedMethods)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Headers'], assertAllowedHeaders)
|
||||
|
@ -103,7 +166,6 @@ t.describe('#CorsHandler()', function() {
|
|||
allowedMethods: assertAllowedMethods,
|
||||
allowedHeaders: false,
|
||||
})
|
||||
ctx.method = 'OPTIONS'
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = 'asdfasdfasdfsfad'
|
||||
|
@ -114,11 +176,239 @@ t.describe('#CorsHandler()', function() {
|
|||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Credentials'])
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Methods'], assertAllowedMethods)
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Headers'], undefined)
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
|
||||
t.test('should not add any headers if origin missing', function() {
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: ['https://test.com'],
|
||||
})
|
||||
ctx.req.headers['origin'] = null
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = 'asdfasdfasdfsfad'
|
||||
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
|
||||
t.test('should not add any headers if origin not found', function() {
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
const assertAllowedMethods = 'GET,HEAD'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: ['https://my.site.here'],
|
||||
allowedMethods: assertAllowedMethods,
|
||||
allowedHeaders: false,
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-method'] = 'GET'
|
||||
ctx.req.headers['access-control-request-headers'] = 'asdfasdfasdfsfad'
|
||||
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
|
||||
t.test('should not add any headers if request-method is missing', function() {
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: ['http://my.site.here'],
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
delete ctx.req.headers['access-control-request-method']
|
||||
ctx.req.headers['access-control-request-headers'] = 'asdfasdfasdfsfad'
|
||||
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.strictEqual(ctx.status, 204)
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('GET/POST/DELETE/PATCH/PUT', function() {
|
||||
let testMethods = ['GET', 'POST', 'DELETE', 'PATCH', 'PUT']
|
||||
|
||||
t.test('should set header but no status', function() {
|
||||
testMethods.forEach(function(method) {
|
||||
ctx = createCtx()
|
||||
ctx.method = method
|
||||
ctx.status = method
|
||||
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: [assertOrigin],
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
ctx.req.headers['access-control-request-headers'] = 'asdfasdfasdfsfad'
|
||||
|
||||
assert.notOk(ctx.headers['Vary'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Credentials'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.notOk(ctx.headers['Access-Control-Expose-Headers'])
|
||||
assert.strictEqual(ctx.status, method)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should set credential header if specifed', function() {
|
||||
testMethods.forEach(function(method) {
|
||||
ctx = createCtx()
|
||||
ctx.method = method
|
||||
ctx.status = method
|
||||
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: [assertOrigin],
|
||||
credentials: true,
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
|
||||
assert.notOk(ctx.headers['Vary'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Credentials'], 'true')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.notOk(ctx.headers['Access-Control-Expose-Headers'])
|
||||
assert.strictEqual(ctx.status, method)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should set expose headers if specifed', function() {
|
||||
testMethods.forEach(function(method) {
|
||||
const assertExposeHeaders = 'Some, Test, Here'
|
||||
ctx = createCtx()
|
||||
ctx.method = method
|
||||
ctx.status = method
|
||||
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: [assertOrigin],
|
||||
exposeHeaders: assertExposeHeaders,
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
|
||||
assert.notOk(ctx.headers['Vary'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.strictEqual(ctx.headers['Access-Control-Allow-Origin'], assertOrigin)
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Credentials'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.strictEqual(ctx.headers['Access-Control-Expose-Headers'], assertExposeHeaders)
|
||||
assert.strictEqual(ctx.status, method)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should not add any headers if origin missing', function() {
|
||||
testMethods.forEach(function(method) {
|
||||
ctx = createCtx()
|
||||
ctx.method = method
|
||||
ctx.status = method
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: ['https://test.com'],
|
||||
})
|
||||
ctx.req.headers['origin'] = null
|
||||
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.notOk(ctx.headers['Access-Control-Expose-Headers'])
|
||||
assert.strictEqual(ctx.status, method)
|
||||
})
|
||||
})
|
||||
|
||||
t.test('should not add any headers if origin not found', function() {
|
||||
testMethods.forEach(function(method) {
|
||||
ctx = createCtx()
|
||||
ctx.method = method
|
||||
ctx.status = method
|
||||
|
||||
const assertOrigin = 'http://my.site.here'
|
||||
const assertAllowedMethods = 'GET,HEAD'
|
||||
|
||||
corsHandler = CorsHandler({
|
||||
allowedOrigins: ['https://my.site.here'],
|
||||
allowedMethods: assertAllowedMethods,
|
||||
allowedHeaders: false,
|
||||
})
|
||||
ctx.req.headers['origin'] = assertOrigin
|
||||
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
|
||||
corsHandler(ctx)
|
||||
|
||||
assert.strictEqual(ctx.headers['Vary'], 'Origin')
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Origin'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Methods'])
|
||||
assert.notOk(ctx.headers['Access-Control-Allow-Headers'])
|
||||
assert.notOk(ctx.headers['Access-Control-Expose-Headers'])
|
||||
assert.strictEqual(ctx.status, method)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.describe('#JsonHandler()', function() {
|
||||
|
|
Loading…
Reference in a new issue