From 86743ce7a5c5cb367e04007016557d4e6253daeb Mon Sep 17 00:00:00 2001 From: alxndrsn Date: Tue, 19 Nov 2024 05:30:45 +0000 Subject: [PATCH] options.credentials: accept function closes #336 --- README.md | 4 +++- lib/index.js | 9 +++++---- test/test.js | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 37ab8cf..df8ea48 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,9 @@ app.listen(80, function () { * `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`). * `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header. * `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. -* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. +* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Possible values: + - `Boolean` - set `credentials` to `true` to pass the header, otherwise it is omitted. + - `Function` - set `credentials` to a function implementing some custom logic. The function takes the request as the only parameter. Return `true` from this function to pass the header, otherwise it is omitted. * `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted. * `preflightContinue`: Pass the CORS preflight response to the next handler. * `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`. diff --git a/lib/index.js b/lib/index.js index ad899ca..9955bd0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -81,8 +81,9 @@ }; } - function configureCredentials(options) { - if (options.credentials === true) { + function configureCredentials(options, req) { + if (options.credentials === true || + (typeof options.credentials === 'function' && options.credentials(req) === true)) { return { key: 'Access-Control-Allow-Credentials', value: 'true' @@ -163,7 +164,7 @@ if (method === 'OPTIONS') { // preflight headers.push(configureOrigin(options, req)); - headers.push(configureCredentials(options)) + headers.push(configureCredentials(options, req)) headers.push(configureMethods(options)) headers.push(configureAllowedHeaders(options, req)); headers.push(configureMaxAge(options)) @@ -182,7 +183,7 @@ } else { // actual response headers.push(configureOrigin(options, req)); - headers.push(configureCredentials(options)) + headers.push(configureCredentials(options, req)) headers.push(configureExposedHeaders(options)) applyHeaders(headers, res); next(); diff --git a/test/test.js b/test/test.js index 34ddb41..27853ea 100644 --- a/test/test.js +++ b/test/test.js @@ -586,6 +586,45 @@ var util = require('util') cors()(req, res, next); }); + describe('credentials configured with function', function () { + function conditionalCredentials(req) { + console.log('conditionalCredentials()', 'origin:', req.headers.origin); + var match = req.headers.origin === 'http://example.com'; + console.log('conditionalCredentials()', 'match:', match); + return match; + } + + it('includes credentials if provided function returns true', function (done) { + var cb = after(1, done) + var req = fakeRequest('OPTIONS') + var res = fakeResponse() + + res.on('finish', function () { + assert.equal(res.getHeader('Access-Control-Allow-Credentials'), 'true') + cb() + }) + + cors({ credentials: conditionalCredentials })(req, res, function (err) { + cb(err || new Error('should not be called')) + }) + }); + + it('does not include credentials if provided function returns false', function (done) { + var cb = after(1, done) + var req = fakeRequest('OPTIONS', { origin: 'http://unmatched.example.com' }); + var res = fakeResponse(); + + res.on('finish', function () { + assert.equal(res.getHeader('Access-Control-Allow-Credentials'), undefined) + cb() + }) + + cors({ credentials: conditionalCredentials })(req, res, function (err) { + cb(err || new Error('should not be called')) + }) + }); + }); + it('includes maxAge when specified', function (done) { var cb = after(1, done) var req = new FakeRequest('OPTIONS')