From b3a5346d9e0a9a2a5aea7a4a0d04667b1e52f5ee Mon Sep 17 00:00:00 2001 From: Brett Zamir Date: Wed, 8 May 2019 23:46:53 +0800 Subject: [PATCH] - Breaking change: Stop supporting older behavior against layers of arity one or two (will now drop one with no `done`) - Breaking change: We now catch errors within layers so as to support more intuitive passing of errors (when synchronous) - Breaking change: No longer throws when login is missing a callback (as may use as Promise) - Breaking change: Only return from `serializeUser`, `deserializeUser` (and `transformAuthInfo`) when giving a synchronous serialized result or a Promise (do not do `return done()`) - Enhancement: Promises for `serializeUser`, `deserializeUser`, `transformAuthInfo` (including allowing throwing `new Error('pass')` to pass); avoid need for `done` - Enhancement: Allow `serializeUser` or `deserializeUser` to return non-`undefined` value for sync behavior - Refactoring: Return Promise from `req.logIn` and from authenticate middleware's `strategy.success` - Makefile: Add missing `clean-cov` - Testing: Add tests with `req.logIn` and `SessionStrategy` returning without `done` (synchronously or with Promise) - Docs: Show example of a `LocalStrategy` using async/await - Docs: Avoid showing connect `cookieParser` with `session`; use current `expressSession` over `connect.session` - Linting: jsdoc - Git: Ignore .github/docs/jsdoc - npm/Docs: Add jsdoc with script, along with static server and open-cli to open - npm: Update devDeps - npm: Add `test-one` script; remove self from contributors --- .eslintignore | 1 + .eslintrc.js | 11 +- .gitignore | 3 +- Makefile | 4 + README.md | 3 +- docs/jsdoc-config.js | 32 + lib/authenticator.js | 339 ++++-- lib/errors/authenticationerror.js | 7 +- lib/framework/connect.js | 17 +- lib/http/request.js | 68 +- lib/index.js | 44 +- lib/middleware/authenticate.js | 231 ++-- lib/middleware/initialize.js | 41 +- lib/sessionmanager.js | 81 +- lib/strategies/session.js | 66 +- package-lock.json | 248 +++- package.json | 13 +- templates/package.json.j2 | 9 +- test/authenticator.middleware.test.js | 12 +- test/authenticator.promise.test.js | 1013 +++++++++++++++++ test/authenticator.sync.test.js | 1011 ++++++++++++++++ test/authenticator.test.js | 96 +- test/http/request.test.js | 76 +- test/middleware/authenticate.redirect.test.js | 3 +- .../authenticate.success.flash.test.js | 61 +- .../authenticate.success.info.test.js | 21 +- .../authenticate.success.message.test.js | 13 +- .../authenticate.success.multi.test.js | 7 +- test/middleware/authenticate.success.test.js | 29 +- test/middleware/authenticate.test.js | 4 +- test/strategies/session.test.js | 20 +- 31 files changed, 3080 insertions(+), 504 deletions(-) create mode 100644 docs/jsdoc-config.js create mode 100644 test/authenticator.promise.test.js create mode 100644 test/authenticator.sync.test.js diff --git a/.eslintignore b/.eslintignore index 4e3ef646..43c87dab 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,3 +2,4 @@ coverage/ node_modules/ var/ +docs/jsdoc diff --git a/.eslintrc.js b/.eslintrc.js index 77d0cdee..497799d3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -8,6 +8,14 @@ module.exports = { // Override ash-nazg's current preference for ESM 'plugin:node/recommended-script' ], + settings: { + polyfills: [ + // Needing these for some reason to avoid an error + "Promise", + "Promise.reject", + "Promise.resolve" + ] + }, overrides: [ { files: ['test/**'], @@ -60,9 +68,8 @@ module.exports = { 'no-underscore-dangle': 0, 'no-param-reassign': 0, - // Disable until implementing promises and Node version supporting + // Disable as middleware approach requires some callbacks 'promise/prefer-await-to-callbacks': 0, - 'promise/prefer-await-to-then': 0, // Disable until ready to tackle 'require-jsdoc': 0, diff --git a/.gitignore b/.gitignore index 7447c334..b69f7a91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ reports/ -docs/ +docs/jsdoc +.github/docs/jsdoc var/ diff --git a/Makefile b/Makefile index d096498a..268261b3 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ LCOVFILE = ./reports/coverage/lcov.info MOCHAFLAGS = --require ./test/bootstrap/node +COVDIR = ./var/cov/* view-docs: open ./docs/index.html @@ -15,6 +16,9 @@ view-docs: view-cov: open ./var/cov/index.html +clean-cov: + -rm -r $(COVDIR) + clean: clean-docs clean-cov -rm -r $(REPORTSDIR) diff --git a/README.md b/README.md index 2aaf6ea5..34e4aeba 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Passport-Next/Passport +# Passport-Next/Passport Status: [![NPM version](https://img.shields.io/npm/v/@passport-next/passport.svg)](https://www.npmjs.com/package/@passport-next/passport) @@ -44,4 +44,3 @@ We support all [node versions](https://github.com/nodejs/Release) supported by t ## Contributing Please see [CONTRIBUTING.md](https://github.com/passport-next/passport/blob/master/CONTRIBUTING.md) - diff --git a/docs/jsdoc-config.js b/docs/jsdoc-config.js new file mode 100644 index 00000000..ff215d33 --- /dev/null +++ b/docs/jsdoc-config.js @@ -0,0 +1,32 @@ +'use strict'; + +module.exports = { + recurseDepth: 10, + source: { + exclude: [ + 'node_modules', + 'dist', + 'var', + 'coverage', + 'test' + ] + // excludePattern: '' + }, + sourceType: 'module', + tags: { + allowUnknownTags: false + }, + templates: { + cleverLinks: true, + monospaceLinks: false /* , + default: { + layoutFile: 'docs/layout.tmpl' + } */ + }, + opts: { + recurse: true, + verbose: true, + destination: 'docs/jsdoc' + // tutorials: 'docs/tutorials' + } +}; diff --git a/lib/authenticator.js b/lib/authenticator.js index 3213d221..3d40856b 100644 --- a/lib/authenticator.js +++ b/lib/authenticator.js @@ -1,20 +1,28 @@ +/* eslint-disable promise/prefer-await-to-then */ +/* eslint no-unused-expressions: ["error", {allowShortCircuit: true}] */ 'use strict'; /** - * Module dependencies. + * @file Module dependencies. */ const SessionStrategy = require('./strategies/session'); const SessionManager = require('./sessionmanager'); const connect = require('./framework/connect'); +const isThenable = (obj) => { + return obj && typeof obj.then === 'function'; +}; /** * The `Authenticator` constructor. - * * @public */ class Authenticator { + /** + * Sets up initial framework (via {@link GetConnectExpress}) and with + * {@link SessionStrategy} and {@link SessionManager}. + */ constructor() { this._key = 'passport'; this._strategies = {}; @@ -28,7 +36,8 @@ class Authenticator { } /** - * Initialize authenticator. + * Initialize authenticator. Sets framework internally with return of + * {@link GetConnectExpress} though this can be overridden. * @returns {void} * @protected */ @@ -39,7 +48,7 @@ class Authenticator { } /** - * Utilize the given `strategy` with optional `name`, overridding the strategy's + * Utilize the given `strategy` with optional `name`, overriding the strategy's * default name. * * @example @@ -48,8 +57,11 @@ class Authenticator { * * passport.use('api', new http.BasicStrategy(...args)); * - * @param {string|Strategy} name - * @param {Strategy} strategy + * @param {string|Strategy} [name] + * @param {Strategy} strategy The framework supplied by default will augment an + * instance made from this strategy instance or prototype with the methods, + * `success`, `fail`, `redirect`, `pass`, and `error`. See the source of + * {@link authenticate}. * @returns {Authenticator} for chaining * @public */ @@ -103,7 +115,7 @@ class Authenticator { * * passport.framework(require('hapi-passport')()); * - * @param {object} fw + * @param {ConnectExpress} fw * @returns {Authenticator} for chaining * @public */ @@ -113,7 +125,7 @@ class Authenticator { } /** - * @typedef {Object} AuthenticatorInitializeOptions + * @typedef {AuthenticateOptions} AuthenticatorInitializeOptions * @property {string} [userProperty="user"] Property to set on `req` upon login */ @@ -130,7 +142,7 @@ class Authenticator { * app.use(passport.initialize({ userProperty: 'currentUser' })); * * @param {AuthenticatorInitializeOptions} options - * @returns {GenericCallback} middleware + * @returns {InitializeMiddleware} middleware * @public */ initialize(options) { @@ -167,9 +179,9 @@ class Authenticator { * }); * * @param {string} strategy - * @param {object} options - * @param {GenericCallback} callback - * @returns {GenericCallback} middleware + * @param {AuthenticateOptions} options + * @param {AuthenticateCallback} callback + * @returns {AuthenticateMiddleware} middleware * @public */ authenticate(strategy, options, callback) { @@ -192,9 +204,9 @@ class Authenticator { * passport.authorize('twitter-authz', { failureRedirect: '/account' }); * * @param {string} strategy - * @param {object} options - * @param {GenericCallback} callback - * @returns {GenericCallback} middleware + * @param {AuthenticateOptions} options + * @param {AuthenticateCallback} callback + * @returns {AuthenticateMiddleware} middleware * @public */ authorize(strategy, options, callback) { @@ -225,13 +237,12 @@ class Authenticator { * * @example * - * app.use(connect.cookieParser()); - * app.use(connect.session({ secret: 'keyboard cat' })); + * app.use(expressSession({ secret: 'keyboard cat' })); * app.use(passport.initialize()); * app.use(passport.session()); * - * @param {object} options - * @returns {GenericCallback} middleware + * @param {AuthenticateOptions} options + * @returns {AuthenticateMiddleware} middleware * @public */ session(options) { @@ -239,33 +250,55 @@ class Authenticator { } /** - * Sets a custom SessionManager + * Sets a custom SessionManager. * * @example * * passport.sessionManager = new CustomSessionManager(); * * @public + * @param {SessionManager} mgr + * @returns {Authenticator} */ - sessionManager(mgr) { this._sm = mgr; return this; } + /** + * @typedef {PlainObject} User + */ + + /** + * @callback SerializeUserDoneCallback + * @param {null|Error} err + * @param {0|string} serializedUser + * @returns {void} + */ + + /** + * @callback SerializeUserMiddleware + * @param {Request} req + * @param {User} user + * @param {SerializeUserDoneCallback} serialized + * @returns {void|"pass"|string|Error|Promise} + */ + /** * Registers a function used to serialize user objects into the session. * * @example * - * passport.serializeUser(function(user, done) { + * passport.serializeUser(function (user, done) { * done(null, user.id); * }); * * @public + * @param {SerializeUserMiddleware} fn + * @param {Request} req + * @param {SerializeUserDoneCallback} [done] + * @returns {void|"pass"|string|Error|Promise} */ - - // eslint-disable-next-line consistent-return serializeUser(fn, req, done) { if (typeof fn === 'function') { return this._serializers.push(fn); @@ -282,54 +315,111 @@ class Authenticator { } const stack = this._serializers; - // eslint-disable-next-line consistent-return - (function pass(i, err, obj) { + + // Todo: Refactor to use promises exclusively + // eslint-disable-next-line require-await + return (async function pass(i, err, serializedUser) { // serializers use 'pass' as an error to skip processing - if (err === 'pass') { + if (err === 'pass' || (err && err.message === 'pass')) { err = undefined; } // an error or serialized object was obtained, done - if (err || obj || obj === 0) { return done(err, obj); } + if (err || serializedUser || serializedUser === 0) { + done && done(err, serializedUser); + if (!done && err) { + throw err; + } + return err || serializedUser; + } const layer = stack[i]; if (!layer) { - return done(new Error('Failed to serialize user into session')); + const error = new Error('Failed to serialize user into session'); + done && done(error); + if (!done && error) { + throw error; + } + return error; } - - // eslint-disable-next-line jsdoc/require-jsdoc + let serializedRet, res; + /** + * + * @param {Error} [e] + * @param {void|"pass"|string} [o] + * @returns {void|"pass"|string|Error|Promise} + */ function serialized(e, o) { - pass(i + 1, e, o); + serializedRet = pass(i + 1, e, o); + if (res) { + res(serializedRet); + } + return serializedRet; } try { - const arity = layer.length; - if (arity === 3) { - layer(req, user, serialized); - } else { - layer(user, serialized); + let ret; + try { + ret = layer(req, user, serialized); + } catch (serializeError) { + return serialized(serializeError); + } + if (isThenable(ret)) { + return ret.then((serializedObject) => { + return serialized(null, serializedObject); + }).catch((serializeError) => { + return serialized(serializeError); + }); + } + if (ret !== undefined) { + return serialized(null, ret); } } catch (e) { - return done(e); + return serialized(e); } + // eslint-disable-next-line promise/avoid-new + return new Promise((resolve) => { + if (serializedRet) { + resolve(serializedRet); + return; + } + res = resolve; + }); }(0)); } + /** + * @callback DeserializeUserDoneCallback + * @param {null|Error} err + * @param {User} user + * @returns {void} + */ + + /** + * @callback DeserializeUserMiddleware + * @param {Request} req + * @param {string} obj + * @param {DeserializeUserDoneCallback} deserialized + * @returns {void|null|false|"pass"|Error|Promise} + */ + /** * Registers a function used to deserialize user objects out of the session. * * @example * - * passport.deserializeUser(function(id, done) { + * passport.deserializeUser(function (id, done) { * User.findById(id, function (err, user) { * done(err, user); * }); * }); * * @public + * @param {DeserializeUserMiddleware} fn + * @param {Request} req + * @param {DeserializeUserDoneCallback} [done] + * @returns {Promise} */ - - // eslint-disable-next-line consistent-return deserializeUser(fn, req, done) { if (typeof fn === 'function') { return this._deserializers.push(fn); @@ -346,42 +436,104 @@ class Authenticator { } const stack = this._deserializers; - // eslint-disable-next-line consistent-return - (function pass(i, err, user) { + + // Todo: Refactor to use promises exclusively + // eslint-disable-next-line require-await + return (async function pass(i, err, user) { // deserializers use 'pass' as an error to skip processing - if (err === 'pass') { + if (err === 'pass' || (err && err.message === 'pass')) { err = undefined; } // an error or deserialized user was obtained, done - if (err || user) { return done(err, user); } + if (err || user) { + done && done(err, user); + if (!done && err) { + throw err; + } + return err || user; + } // a valid user existed when establishing the session, but that user has // since been removed - if (user === null || user === false) { return done(null, false); } + if (user === null || user === false) { + done && done(null, false); + return false; + } const layer = stack[i]; if (!layer) { - return done(new Error('Failed to deserialize user out of session')); + const error = new Error('Failed to deserialize user out of session'); + done && done(error); + if (!done && error) { + throw error; + } + return error; } - - // eslint-disable-next-line jsdoc/require-jsdoc + let deserializedRet, res; + /** + * + * @param {Error} [e] + * @param {User|void|"pass"|false} [u] + * @returns {User|void|null|false|Error|Promise} + */ function deserialized(e, u) { - pass(i + 1, e, u); + deserializedRet = pass(i + 1, e, u); + if (res) { + res(deserializedRet); + } + return deserializedRet; } try { - const arity = layer.length; - if (arity === 3) { - layer(req, obj, deserialized); - } else { - layer(obj, deserialized); + let ret; + try { + ret = layer(req, obj, deserialized); + } catch (deserializeError) { + return deserialized(deserializeError); + } + if (isThenable(ret)) { + return ret.then((u) => { + return deserialized(null, u); + }).catch((deserializeError) => { + return deserialized(deserializeError); + }); + } + if (ret !== undefined) { + return deserialized(null, ret); } } catch (e) { - return done(e); + return deserialized(e); } + // eslint-disable-next-line promise/avoid-new + return new Promise((resolve) => { + if (deserializedRet) { + resolve(deserializedRet); + return; + } + res = resolve; + }); }(0)); } + /** + * @typedef {PlainObject} AuthInfo + */ + + /** + * @callback TransformAuthDoneCallback + * @param {null|Error} err + * @param {AuthInfo} obj + * @returns {void} + */ + + /** + * @callback TransformAuthMiddleware + * @param {Request} req + * @param {AuthInfo} tinfo + * @param {TransformAuthDoneCallback} transformed + * @returns {void|"pass"|AuthInfo|Error|Promise} + */ + /** * Registers a function used to transform auth info. * @@ -411,7 +563,7 @@ class Authenticator { * * @example * - * passport.transformAuthInfo(function(info, done) { + * passport.transformAuthInfo(function (info, done) { * Client.findById(info.clientID, function (err, client) { * info.client = client; * done(err, info); @@ -419,9 +571,11 @@ class Authenticator { * }); * * @public + * @param {TransformAuthMiddleware} fn + * @param {Request} req + * @param {TransformAuthDoneCallback} [done] + * @returns {Promise} */ - - // eslint-disable-next-line consistent-return transformAuthInfo(fn, req, done) { if (typeof fn === 'function') { return this._infoTransformers.push(fn); @@ -438,42 +592,75 @@ class Authenticator { } const stack = this._infoTransformers; - // eslint-disable-next-line consistent-return - (function pass(i, err, tinfo) { + + // Todo: Refactor to use promises exclusively + // eslint-disable-next-line require-await + return (async function pass(i, err, tinfo) { // transformers use 'pass' as an error to skip processing - if (err === 'pass') { + if (err === 'pass' || (err && err.message === 'pass')) { err = undefined; } // an error or transformed info was obtained, done - if (err || tinfo) { return done(err, tinfo); } + if (err || tinfo) { + done && done(err, tinfo); + if (!done && err) { + throw err; + } + return err || tinfo; + } const layer = stack[i]; if (!layer) { // if no transformers are registered (or they all pass), the default // behavior is to use the un-transformed info as-is - return done(null, info); + done && done(null, info); + return info; } - // eslint-disable-next-line jsdoc/require-jsdoc + let transformedRet, res; + /** + * + * @param {Error} [e] + * @param {AuthInfo|void|"pass"} [t] + * @returns {AuthInfo|void|Promise} + */ function transformed(e, t) { - pass(i + 1, e, t); + transformedRet = pass(i + 1, e, t); + if (res) { + res(transformedRet); + } + return transformedRet; } try { - const arity = layer.length; - if (arity === 1) { - // sync - const t = layer(info); - transformed(null, t); - } else if (arity === 3) { - layer(req, info, transformed); - } else { - layer(info, transformed); + let ret; + try { + ret = layer(req, info, transformed); + } catch (transformError) { + return transformed(transformError); + } + if (isThenable(ret)) { + return ret.then((t) => { + return transformed(null, t); + }).catch((transformError) => { + return transformed(transformError); + }); + } + if (ret !== undefined) { + return transformed(null, ret); } } catch (e) { - return done(e); + return transformed(e); } + // eslint-disable-next-line promise/avoid-new + return new Promise((resolve) => { + if (transformedRet) { + resolve(transformedRet); + return; + } + res = resolve; + }); }(0)); } diff --git a/lib/errors/authenticationerror.js b/lib/errors/authenticationerror.js index e8daf169..11b521f3 100644 --- a/lib/errors/authenticationerror.js +++ b/lib/errors/authenticationerror.js @@ -2,10 +2,15 @@ /** * The `AuthenticationError` error. - * + * @class AuthenticationError * @private */ class AuthenticationError extends Error { + /** + * + * @param {string} message + * @param {Integer} status + */ constructor(message, status) { super(message); Error.captureStackTrace(this, AuthenticationError); diff --git a/lib/framework/connect.js b/lib/framework/connect.js index 78e8aab0..ed08f7d0 100644 --- a/lib/framework/connect.js +++ b/lib/framework/connect.js @@ -1,24 +1,31 @@ 'use strict'; /** - * Module dependencies. + * @file Module dependencies. */ const initialize = require('../middleware/initialize'); const authenticate = require('../middleware/authenticate'); +/** +* @typedef {PlainObject} ConnectExpress +* @property {initialize} initialize +* @property {authenticate} authenticate +* @property {authenticate} [authorize] +*/ + +/* eslint-disable no-multi-assign, func-names */ /** * Framework support for Connect/Express. * * This module provides support for using Passport with Express. It exposes * middleware that conform to the `fn(req, res, next)` signature. - * - * @return {Object} + * @callback GetConnectExpress + * @returns {ConnectExpress} * @protected */ - -// eslint-disable-next-line no-multi-assign, func-names exports = module.exports = function () { + /* eslint-enable no-multi-assign, func-names */ return { initialize, authenticate diff --git a/lib/http/request.js b/lib/http/request.js index 0f26c754..bfc91c52 100644 --- a/lib/http/request.js +++ b/lib/http/request.js @@ -1,37 +1,46 @@ /** - * Module dependencies. + * @file Module dependencies. */ 'use strict'; -// const http = require('http') -// , req = http.IncomingMessage.prototype; - /* eslint-disable no-multi-assign */ +/** + * @namespace {PlainObject} HttpRequest + */ const req = exports = module.exports = {}; /* eslint-enable no-multi-assign */ /** -* @typedef {Object} LogInOptions -* @property {boolean} [session] Save login state in session, defaults to _true_ +* @callback LoginDoneCallback +* @param {Error} [error] +*/ + +/** +* @typedef {PlainObject} LogInOptions +* @property {boolean} [session=true] Save login state in session. */ /** * Initiate a login session for `user`. * - * + * @function HttpRequest#logIn * @example * * req.logIn(user, { session: false }); * - * req.logIn(user, (err) => { - * if (err) { throw err; } - * // session saved - * }); + * (async () => { + * try { + * await req.logIn(user); + * } catch (err) { + * throw err; + * } + * // session saved + * })(); * * @param {User} user * @param {LogInOptions} options - * @param {GenericCallback} done - * @returns {void} + * @param {LoginDoneCallback} done + * @returns {Promise} * @public */ req.logIn = function logIn(user, options, done) { @@ -50,23 +59,34 @@ req.logIn = function logIn(user, options, done) { this[property] = user; if (session) { if (!this._passport) { throw new Error('passport.initialize() middleware not in use'); } - if (typeof done !== 'function') { throw new TypeError('req#login requires a callback function'); } - // eslint-disable-next-line consistent-return - this._passport.instance._sm.logIn(this, user, (err) => { - if (err) { this[property] = null; return done(err); } - done(); - }); - } else { - // eslint-disable-next-line no-unused-expressions - done && done(); + // We don't use async above in order to be able to throw early there + return (async () => { + try { + // `this._passport.instance` (and `_sm`) set by `initialize` + await this._passport.instance._sm.logIn(this, user); + // eslint-disable-next-line no-unused-expressions + done && done(); + } catch (err) { + this[property] = null; + if (done) { + done(err); + } else { + throw err; + } + } + })(); + } else if (done) { + done(); } + return Promise.resolve(); }; req.login = req.logIn; /** * Terminate an existing login session. + * @function HttpRequest#logOut * @returns {void} * @public */ @@ -86,7 +106,7 @@ req.logout = req.logOut; /** * Test if request is authenticated. - * + * @function HttpRequest#isAuthenticated * @returns {boolean} * @public */ @@ -101,7 +121,7 @@ req.isAuthenticated = function isAuthenticated() { /** * Test if request is unauthenticated. - * + * @function HttpRequest#isUnauthenticated * @returns {boolean} * @public */ diff --git a/lib/index.js b/lib/index.js index 14415cc2..8ee9d4b0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,13 +1,47 @@ +/* eslint-disable no-multi-assign */ /** * Module dependencies. */ -/* eslint-disable no-multi-assign */ 'use strict'; const Passport = require('./authenticator'); const SessionStrategy = require('./strategies/session'); +/** + * Middleware function passed `req`, `res`, and `next` + * @external ConnectMiddleware + * @see https://github.com/senchalabs/connect#appusefn +*/ + +/** +* @external HttpIncomingMessage +* @see https://nodejs.org/docs/latest/api/http.html#http_class_http_incomingmessage +*/ + +/** +* @external HttpServerResponse +* @see https://nodejs.org/docs/latest/api/http.html#http_class_http_serverresponse +*/ + +/** +* @typedef {external:HttpIncomingMessage} Request +*/ + +/** +* @typedef {external:HttpServerResponse} Response +*/ + +/** + * This middleware conforms to Connect/Express middleware by + * the arguments it accepts. + * @see Conforms to {@link external:ConnectMiddleware} + * @callback ConnectMiddleware + * @param {Request} req + * @param {Response} res + * @param {Function} next + * @returns {void} +*/ /** * Export default singleton. @@ -16,14 +50,10 @@ const SessionStrategy = require('./strategies/session'); */ exports = module.exports = new Passport(); -/** - * Expose constructors. - */ +// Expose constructors. exports.Passport = exports.Authenticator = Passport; exports.Strategy = require('@passport-next/passport-strategy'); -/** - * Expose strategies. - */ +// Expose strategies. exports.strategies = {}; exports.strategies.SessionStrategy = SessionStrategy; diff --git a/lib/middleware/authenticate.js b/lib/middleware/authenticate.js index 1ea3f63a..c2be1165 100644 --- a/lib/middleware/authenticate.js +++ b/lib/middleware/authenticate.js @@ -2,12 +2,61 @@ 'use strict'; /** - * Module dependencies. + * @file Module dependencies. */ const http = require('http'); const AuthenticationError = require('../errors/authenticationerror'); +/** +* @callback AuthenticateMiddleware +* @type {ConnectMiddleware} +*/ + +/** +* @callback AuthenticateCallback +* @param {Error|null} error +* @param {false|User} user Set to the authenticated user on a successful +* authentication attempt, or `false` otherwise. +* @param {*} [info] Contains additional details provided by the strategy's verify +* callback - this could be information about a successful authentication or a +* challenge message for a failed authentication. +* @param {*} [status] Passed when authentication fails - this could +* be an HTTP response code for a remote authentication failure or similar. +* @returns {void} +* @example +* app.get('/protected', function (req, res, next) { +* passport.authenticate('local', function(err, user, info, status) { +* if (err) { return next(err) } +* if (!user) { return res.redirect('/signin') } +* res.redirect('/account'); +* })(req, res, next); +* }); +*/ + +/** + * @typedef {PlainObject} AuthenticateOptions + * @property {string} [successRedirect] After successful login, redirect to given URL + * @property {boolean|string} [successMessage] True to store success message in + * `req.session.messages`, or a string to use as override message for success. + * @property {boolean|string} [successFlash] True to flash success messages or a string + * to use as a flash message for success (overrides any from the strategy itself). + * @property {string} [failureRedirect] After failed login, redirect to given URL + * @property {boolean|string} [failureMessage] True to store failure message in + * `req.session.messages`, or a string to use as override + * message for failure. + * @property {boolean|string} [failureFlash] True to flash failure messages or a string to + * use as a flash message for failures (overrides any from the strategy itself). + * @property {boolean} [failWithError] Passes on an {@link AuthenticationError} + * @property {string} [assignProperty] Assign the object provided by the verify callback + * to given property + * @property {string} [successReturnToOrRedirect] Redirect URL; overridden if + * `req.session.returnTo` is truthy + * @property {boolean} [authInfo=true] Set to `false` to disable setting of `autoInfo` + * on `req` through `transformAuthInfo` +*/ + +// Todo: Reenable after this may be merged https://github.com/gajus/eslint-plugin-jsdoc/pull/270 /** * Authenticates requests. * @@ -17,44 +66,17 @@ const AuthenticationError = require('../errors/authenticationerror'); * established by default. If authentication fails, an unauthorized response * will be sent. * - * Options: - * - `session` Save login state in session, defaults to _true_ - * - `successRedirect` After successful login, redirect to given URL - * - `successMessage` True to store success message in - * req.session.messages, or a string to use as override - * message for success. - * - `successFlash` True to flash success messages or a string to use as a flash - * message for success (overrides any from the strategy itself). - * - `failureRedirect` After failed login, redirect to given URL - * - `failureMessage` True to store failure message in - * req.session.messages, or a string to use as override - * message for failure. - * - `failureFlash` True to flash failure messages or a string to use as a flash - * message for failures (overrides any from the strategy itself). - * - `assignProperty` Assign the object provided by the verify callback to given property - * * An optional `callback` can be supplied to allow the application to override - * the default manner in which authentication attempts are handled. The - * callback has the following signature, where `user` will be set to the - * authenticated user on a successful authentication attempt, or `false` - * otherwise. An optional `info` argument will be passed, containing additional - * details provided by the strategy's verify callback - this could be information about - * a successful authentication or a challenge message for a failed authentication. - * An optional `status` argument will be passed when authentication fails - this could - * be a HTTP response code for a remote authentication failure or similar. - * - * app.get('/protected', function(req, res, next) { - * passport.authenticate('local', function(err, user, info, status) { - * if (err) { return next(err) } - * if (!user) { return res.redirect('/signin') } - * res.redirect('/account'); - * })(req, res, next); - * }); + * the default manner in which authentication attempts are handled. * * Note that if a callback is supplied, it becomes the application's * responsibility to log-in the user, establish a session, and otherwise perform * the desired operations. * + * Note that its redirecting behavior relies on `res.redirect` (available in + * Express but not Connect). + * + * @callback authenticate * @example * * passport.authenticate('local', { successRedirect: '/', failureRedirect: '/login' }); @@ -64,10 +86,10 @@ const AuthenticationError = require('../errors/authenticationerror'); * passport.authenticate('twitter'); * * @param {Authenticator} passport - * @param {string|Array} name - * @param {object} options - * @param {GenericCallback} callback - * @returns {GenericCallback} + * @param {string|string[]} name + * @param {AuthenticateOptions} options + * @param {AuthenticateCallback} [callback] + * @returns {AuthenticateMiddleware} * @public */ module.exports = function authenticate(passport, name, options, callback) { @@ -94,27 +116,38 @@ module.exports = function authenticate(passport, name, options, callback) { multi = false; } + // Do not refactor to return an async method as is middleware calling `next` return function authenticate(req, res, next) { // accumulator for failures from each strategy in the chain const failures = []; - // eslint-disable-next-line jsdoc/require-jsdoc + /** + * @param {string} url + * @returns {void} + * @see https://expressjs.com/en/api.html#res.redirect + */ function redirect(url) { if (req.session && req.session.save && typeof req.session.save === 'function') { - return req.session.save(() => res.redirect(url)); + req.session.save(() => res.redirect(url)); + return; } - return res.redirect(url); + res.redirect(url); } - // eslint-disable-next-line consistent-return, jsdoc/require-jsdoc + /** + * + * @returns {void} + */ function allFailed() { if (callback) { if (!multi) { - return callback(null, false, failures[0].challenge, failures[0].status); + callback(null, false, failures[0].challenge, failures[0].status); + return; } const challenges = failures.map(f => f.challenge); const statuses = failures.map(f => f.status); - return callback(null, false, challenges, statuses); + callback(null, false, challenges, statuses); + return; } // Strategies are ordered by priority. For the purpose of flashing a @@ -148,7 +181,8 @@ module.exports = function authenticate(passport, name, options, callback) { } } if (options.failureRedirect) { - return redirect(options.failureRedirect); + redirect(options.failureRedirect); + return; } // When failure handling is not delegated to the application, the default @@ -170,25 +204,37 @@ module.exports = function authenticate(passport, name, options, callback) { res.setHeader('WWW-Authenticate', rchallenge); } if (options.failWithError) { - return next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus)); + next(new AuthenticationError(http.STATUS_CODES[res.statusCode], rstatus)); + return; } res.end(http.STATUS_CODES[res.statusCode]); } - // eslint-disable-next-line consistent-return (function attempt(i) { const layer = name[i]; // If no more strategies exist in the chain, authentication has failed. - if (!layer) { return allFailed(); } + if (!layer) { + allFailed(); + return; + } // Get the strategy, which will be used as prototype from which to create // a new instance. Action functions will then be bound to the strategy // within the context of the HTTP request/response pair. const prototype = passport._strategy(layer); - if (!prototype) { return next(new Error(`Unknown authentication strategy "${layer}"`)); } + if (!prototype) { + next(new Error(`Unknown authentication strategy "${layer}"`)); + return; + } const strategy = Object.create(prototype); + /** + * @typedef {PlainObject} SuccessInfo + * @property {string} [type="success"] + * @property {string} [message] + */ + // ----- BEGIN STRATEGY AUGMENTATION ----- // Augment the new strategy instance with action functions. These action @@ -207,15 +253,15 @@ module.exports = function authenticate(passport, name, options, callback) { * useful for third-party authentication strategies to pass profile * details. * - * @param {Object} user - * @param {Object} info + * @param {User} user + * @param {SuccessInfo} [info] * @public + * @returns {void} */ - - // eslint-disable-next-line consistent-return - strategy.success = function success(user, info) { + strategy.success = async function success(user, info) { if (callback) { - return callback(null, user, info); + callback(null, user, info); + return; } info = info || {}; @@ -248,40 +294,51 @@ module.exports = function authenticate(passport, name, options, callback) { } if (options.assignProperty) { req[options.assignProperty] = user; - return next(); + next(); + return; } - // eslint-disable-next-line consistent-return - req.logIn(user, options, (err) => { - if (err) { return next(err); } - - // eslint-disable-next-line consistent-return, jsdoc/require-jsdoc - function complete() { - if (options.successReturnToOrRedirect) { - let url = options.successReturnToOrRedirect; - if (req.session && req.session.returnTo) { - url = req.session.returnTo; - delete req.session.returnTo; - } - return redirect(url); - } - if (options.successRedirect) { - return redirect(options.successRedirect); + try { + await req.logIn(user, options); + } catch (err) { + next(err); + return; + } + + /** + * + * @returns {void} + */ + function complete() { + if (options.successReturnToOrRedirect) { + let url = options.successReturnToOrRedirect; + if (req.session && req.session.returnTo) { + url = req.session.returnTo; + delete req.session.returnTo; } - next(); + redirect(url); + return; } + if (options.successRedirect) { + redirect(options.successRedirect); + return; + } + next(); + } - if (options.authInfo !== false) { - // eslint-disable-next-line consistent-return - passport.transformAuthInfo(info, req, (err, tinfo) => { - if (err) { return next(err); } - req.authInfo = tinfo; - complete(); - }); - } else { + if (options.authInfo !== false) { + try { + const tinfo = await passport.transformAuthInfo(info, req); + req.authInfo = tinfo; complete(); + } catch (err) { + next(err); + // eslint-disable-next-line no-useless-return + return; } - }); + } else { + complete(); + } }; /** @@ -290,8 +347,8 @@ module.exports = function authenticate(passport, name, options, callback) { * * Strategies should call this function to fail an authentication attempt. * - * @param {string} challenge - * @param {number} status + * @param {string} [challenge] + * @param {number} [status] * @returns {void} * @public */ @@ -314,7 +371,7 @@ module.exports = function authenticate(passport, name, options, callback) { * user agent) to a third-party website for authentication. * * @param {string} url - * @param {number} status + * @param {number} [status=302] * @returns {void} * @public */ @@ -359,12 +416,12 @@ module.exports = function authenticate(passport, name, options, callback) { * * @param {Error} err * @public + * @returns {void} */ - - // eslint-disable-next-line consistent-return strategy.error = function error(err) { if (callback) { - return callback(err); + callback(err); + return; } next(err); diff --git a/lib/middleware/initialize.js b/lib/middleware/initialize.js index 1d288846..d7f0d109 100644 --- a/lib/middleware/initialize.js +++ b/lib/middleware/initialize.js @@ -1,5 +1,12 @@ 'use strict'; +const IncomingMessageExt = require('../http/request'); + +/** +* @callback InitializeMiddleware +* @type {ConnectMiddleware} +*/ + /** * Passport initialization. * @@ -21,30 +28,38 @@ * entirely stateless (not using sessions), this middleware is not necessary, * but its use will not have any adverse impact. * + * Sets the following on `request` (first four from {@link HttpRequest}): + * 1. `logIn` (and `login`) + * 2. `logOut` (and `logout`) + * 3. `isAuthenticated` + * 4. `isUnauthenticated` + * 5. `_passport` (with `instance` property set to `passport` and + * `session` property set to `req.session[passport._key]`, as potentially + * set by {@link SessionManager#logIn}). + * + * As per #5, `request` may also have `session` set later. + * + * @callback initialize * @example * - * app.use(connect.cookieParser()); - * app.use(connect.session({ secret: 'keyboard cat' })); + * app.use(expressSession({ secret: 'keyboard cat' })); * app.use(passport.initialize()); * app.use(passport.session()); * - * passport.serializeUser(function(user, done) { - * done(null, user.id); + * passport.serializeUser(function (user) { + * return user.id; * }); * - * passport.deserializeUser(function(id, done) { - * User.findById(id, function (err, user) { - * done(err, user); - * }); + * passport.deserializeUser(async function (id) { + * const user = await User.findById(id); + * return user; * }); - * - * @return {Function} + * @param {Authenticator} passport + * @returns {InitializeMiddleware} * @public */ - -const IncomingMessageExt = require('../http/request'); - module.exports = function initialize(passport) { + // Do not refactor to return an async method as is middleware calling `next` /* eslint-disable-next-line no-shadow */ return function initialize(req, res, next) { req._passport = {}; diff --git a/lib/sessionmanager.js b/lib/sessionmanager.js index acf588ae..2328d26f 100644 --- a/lib/sessionmanager.js +++ b/lib/sessionmanager.js @@ -1,6 +1,23 @@ 'use strict'; +/** +* @typedef {PlainObject} SessionManagerOptions +* @property {string} [key="passport"] +*/ + +/** +* @callback SessionManagerSerializeUser +* @param {User} user +* @param {Request} req +* @param {SerializeUserDoneCallback} done +*/ + class SessionManager { + /** + * + * @param {SessionManagerOptions} [options] + * @param {SessionManagerSerializeUser} serializeUser + */ constructor(options, serializeUser) { if (typeof options === 'function') { serializeUser = options; @@ -12,26 +29,58 @@ class SessionManager { this._serializeUser = serializeUser; } - logIn(req, user, cb) { - // eslint-disable-next-line consistent-return - this._serializeUser(user, req, (err, obj) => { - if (err) { - return cb(err); - } - if (!req._passport.session) { - req._passport.session = {}; - } - req._passport.session.user = obj; - if (!req.session) { - req.session = {}; + /** + * @callback LogInCallback + * @param {Error} [err] + */ + + /** + * Will set: + * 1. `request._passport.session` object if not (with `.user` object as subproperty) + * 2. `request.session` (with `_key` subproperty set to `request._passport.session`). + * @param {Request} req + * @param {User} user + * @param {LogInCallback} [cb] + * @returns {Promise} + */ + async logIn(req, user, cb) { + let obj; + try { + obj = await this._serializeUser(user, req); + } catch (err) { + // eslint-disable-next-line no-unused-expressions, callback-return + cb && cb(err); + if (!cb) { + throw err; } - req.session[this._key] = req._passport.session; - cb(); - }); + return; + } + if (!req._passport.session) { + req._passport.session = {}; + } + req._passport.session.user = obj; + if (!req.session) { + req.session = {}; + } + req.session[this._key] = req._passport.session; + // eslint-disable-next-line no-unused-expressions + cb && cb(); } - // eslint-disable-next-line class-methods-use-this + /** + * @callback LogoutCallback + * @returns {void} + */ + + /* eslint-disable class-methods-use-this */ + /** + * + * @param {Request} req + * @param {LogoutCallback} [cb] + * @returns {void} + */ logOut(req, cb) { + /* eslint-enable class-methods-use-this */ if (req._passport && req._passport.session) { delete req._passport.session.user; } diff --git a/lib/strategies/session.js b/lib/strategies/session.js index 51b612fc..7f17c972 100644 --- a/lib/strategies/session.js +++ b/lib/strategies/session.js @@ -1,16 +1,33 @@ 'use strict'; /** - * Module dependencies. + * @file Module dependencies. */ const Strategy = require('@passport-next/passport-strategy'); +/** + * Not presently in use + * @typedef {PlainObject} SessionStrategyOptions +*/ + +/** +* @callback SessionStrategyDeserializeUser +* @param {string} user +* @param {Request} req +* @returns {User} +*/ + /** * The `SessionStrategy` constructor. * * @public */ class SessionStrategy extends Strategy { + /** + * + * @param {SessionStrategyOptions} [options] + * @param {SessionStrategyDeserializeUser} deserializeUser + */ constructor(options, deserializeUser) { if (typeof options === 'function') { deserializeUser = options; @@ -23,6 +40,12 @@ class SessionStrategy extends Strategy { this._deserializeUser = deserializeUser; } + /** + * Not currently in use. + * @typedef {PlainObject} SessionStrategyAuthenticateOptions + */ + + /* eslint-disable no-unused-vars */ /** * Authenticate request based on the current session state. * @@ -32,14 +55,16 @@ class SessionStrategy extends Strategy { * * This strategy is registered automatically by Passport. * - * @param {Object} req - * @param {Object} options + * @param {Request} req + * @param {SessionStrategyAuthenticateOptions} options * @protected + * @returns {Promise} May return value of `this.error()` */ - - // eslint-disable-next-line consistent-return, no-unused-vars - authenticate(req, options) { - if (!req._passport) { return this.error(new Error('passport.initialize() middleware not in use')); } + async authenticate(req, options) { + /* eslint-enable no-unused-vars */ + if (!req._passport) { + return this.error(new Error('passport.initialize() middleware not in use')); + } options = options || {}; let su; @@ -49,21 +74,24 @@ class SessionStrategy extends Strategy { } if (su || su === 0) { - // eslint-disable-next-line consistent-return - this._deserializeUser(su, req, (err, user) => { - if (err) { return this.error(err); } - if (!user) { - delete req._passport.session.user; - } else { - // TODO: Remove instance access - const property = req._passport.instance._userProperty || 'user'; - req[property] = user; - } - this.pass(); - }); + let user; + try { + user = await this._deserializeUser(su, req); + } catch (err) { + return this.error(err); + } + if (!user) { + delete req._passport.session.user; + } else { + // TODO: Remove instance access (set by `initialize`) + const property = req._passport.instance._userProperty || 'user'; + req[property] = user; + } + this.pass(); } else { this.pass(); } + return undefined; } } diff --git a/package-lock.json b/package-lock.json index b58fbde5..8732aa3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -219,19 +219,22 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "dev": true, + "optional": true }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "dev": true, + "optional": true }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "dev": true, + "optional": true }, "array-includes": { "version": "3.0.3", @@ -247,7 +250,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "optional": true }, "asap": { "version": "2.0.6", @@ -265,7 +269,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "dev": true, + "optional": true }, "ast-metadata-inferer": { "version": "0.1.1", @@ -290,7 +295,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "dev": true, + "optional": true }, "bail": { "version": "1.0.4", @@ -309,6 +315,7 @@ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, + "optional": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -324,6 +331,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.0" } @@ -333,6 +341,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -342,6 +351,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -351,6 +361,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -381,6 +392,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, + "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -399,6 +411,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -427,6 +440,7 @@ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, + "optional": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -550,6 +564,7 @@ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, + "optional": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -562,6 +577,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -620,6 +636,7 @@ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, + "optional": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -650,7 +667,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", @@ -668,7 +686,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -717,7 +736,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "dev": true, + "optional": true }, "deep-eql": { "version": "3.0.1", @@ -748,6 +768,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -758,6 +779,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -767,6 +789,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -776,6 +799,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -1097,9 +1121,9 @@ } }, "eslint-plugin-jsdoc": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-8.0.1.tgz", - "integrity": "sha512-kzZHxDjTgNSdDpwULMqSBZr6wKaX+vvIrg/D+UppH8rd87bh+sPzs8PEJNjsQ94tx2TSQebc3P2cXcmkIVsyeA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-8.0.0.tgz", + "integrity": "sha512-9y2HYDDS5xBVjnZZaiGYyAN48cpEPjZMgqMQq4a/PWY9B1pwyRdZZU199IwTVhzs9azc1rF4F6noXAi5o++6DQ==", "dev": true, "requires": { "comment-parser": "^0.5.4", @@ -1291,6 +1315,7 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, + "optional": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -1306,6 +1331,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "optional": true, "requires": { "ms": "2.0.0" } @@ -1315,6 +1341,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -1324,6 +1351,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -1332,7 +1360,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "dev": true, + "optional": true } } }, @@ -1347,6 +1376,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, + "optional": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -1357,6 +1387,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, + "optional": true, "requires": { "is-plain-object": "^2.0.4" } @@ -1379,6 +1410,7 @@ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, + "optional": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -1395,6 +1427,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.0" } @@ -1404,6 +1437,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -1413,6 +1447,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -1422,6 +1457,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -1431,6 +1467,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -1486,6 +1523,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -1498,6 +1536,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -1543,13 +1582,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "dev": true, + "optional": true }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, + "optional": true, "requires": { "map-cache": "^0.2.2" } @@ -1580,7 +1621,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -1601,12 +1643,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1621,17 +1665,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -1748,7 +1795,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -1760,6 +1808,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1774,6 +1823,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1781,12 +1831,14 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -1805,6 +1857,7 @@ "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -1885,7 +1938,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -1897,6 +1951,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -1982,7 +2037,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -2018,6 +2074,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2037,6 +2094,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2080,12 +2138,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -2132,7 +2192,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "dev": true, + "optional": true }, "glob": { "version": "7.1.4", @@ -2215,6 +2276,7 @@ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, + "optional": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -2226,6 +2288,7 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -2235,13 +2298,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -2362,6 +2427,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -2370,13 +2436,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -2432,6 +2500,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -2440,13 +2509,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -2470,6 +2541,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -2480,7 +2552,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "dev": true, + "optional": true } } }, @@ -2488,13 +2561,15 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "dev": true, + "optional": true }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "optional": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -2542,6 +2617,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -2550,13 +2626,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -2584,6 +2662,7 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, + "optional": true, "requires": { "isobject": "^3.0.1" } @@ -2638,7 +2717,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "dev": true, + "optional": true }, "is-word-character": { "version": "1.0.2", @@ -2662,7 +2742,8 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "dev": true, + "optional": true }, "js-tokens": { "version": "4.0.0", @@ -2708,7 +2789,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true }, "lcid": { "version": "2.0.0", @@ -2845,13 +2927,15 @@ "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "dev": true, + "optional": true }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, + "optional": true, "requires": { "object-visit": "^1.0.0" } @@ -2895,6 +2979,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -2933,10 +3018,11 @@ "dev": true }, "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", "dev": true, + "optional": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -2947,6 +3033,7 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, + "optional": true, "requires": { "is-plain-object": "^2.0.4" } @@ -3091,6 +3178,7 @@ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -3298,6 +3386,7 @@ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, + "optional": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -3309,6 +3398,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -3317,13 +3407,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -3341,6 +3433,7 @@ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, + "optional": true, "requires": { "isobject": "^3.0.0" } @@ -3372,6 +3465,7 @@ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, + "optional": true, "requires": { "isobject": "^3.0.1" } @@ -3503,7 +3597,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "dev": true, + "optional": true }, "path-dirname": { "version": "1.0.2", @@ -3576,7 +3671,8 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "dev": true, + "optional": true }, "prelude-ls": { "version": "1.1.2", @@ -3694,6 +3790,7 @@ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, + "optional": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -3704,6 +3801,7 @@ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, + "optional": true, "requires": { "ret": "~0.1.10" } @@ -3756,7 +3854,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true + "dev": true, + "optional": true }, "repeat-string": { "version": "1.6.1", @@ -3813,7 +3912,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "dev": true, + "optional": true }, "restore-cursor": { "version": "2.0.0", @@ -3829,7 +3929,8 @@ "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "dev": true, + "optional": true }, "rimraf": { "version": "2.6.3", @@ -3862,7 +3963,8 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "optional": true }, "safe-regex": { "version": "2.0.2", @@ -3896,6 +3998,7 @@ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -3908,6 +4011,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -3951,6 +4055,7 @@ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, + "optional": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -3967,6 +4072,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, + "optional": true, "requires": { "ms": "2.0.0" } @@ -3976,6 +4082,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -3985,6 +4092,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -3993,7 +4101,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4002,6 +4111,7 @@ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, + "optional": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -4013,6 +4123,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.0" } @@ -4022,6 +4133,7 @@ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -4031,6 +4143,7 @@ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -4040,6 +4153,7 @@ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -4053,6 +4167,7 @@ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, + "optional": true, "requires": { "kind-of": "^3.2.0" }, @@ -4061,13 +4176,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -4078,13 +4195,15 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "dev": true, + "optional": true }, "source-map-resolve": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, + "optional": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -4097,7 +4216,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "dev": true, + "optional": true }, "spdx-correct": { "version": "3.1.0", @@ -4136,6 +4256,7 @@ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, + "optional": true, "requires": { "extend-shallow": "^3.0.0" } @@ -4157,6 +4278,7 @@ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, + "optional": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -4167,6 +4289,7 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -4295,6 +4418,7 @@ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" }, @@ -4303,13 +4427,15 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "optional": true }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "optional": true, "requires": { "is-buffer": "^1.1.5" } @@ -4321,6 +4447,7 @@ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, + "optional": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -4333,6 +4460,7 @@ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, + "optional": true, "requires": { "ret": "~0.1.10" } @@ -4344,6 +4472,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -4426,6 +4555,7 @@ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, + "optional": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -4438,6 +4568,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -4447,6 +4578,7 @@ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -4500,6 +4632,7 @@ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, + "optional": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -4510,6 +4643,7 @@ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, + "optional": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -4521,6 +4655,7 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "dev": true, + "optional": true, "requires": { "isarray": "1.0.0" } @@ -4531,7 +4666,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "dev": true, + "optional": true } } }, @@ -4555,13 +4691,15 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "dev": true, + "optional": true }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "dev": true, + "optional": true }, "util-deprecate": { "version": "1.0.2", diff --git a/package.json b/package.json index 12868c73..2e680cb8 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,7 @@ "url": "https://github.com/orgs/passport-next/people" }, "homepage": "https://github.com/passport-next/passport", - "contributors": [ - "Brett Zamir" - ], + "contributors": [], "bugs": { "url": "https://github.com/passport-next/passport/issues" }, @@ -34,9 +32,12 @@ "eslint-plugin-promise": "^4.1.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-unicorn": "^9.1.0", + "jsdoc": "^3.6.2", "make-node": "^0.4.6", "mocha": "6.x.x", - "nunjucks": "^3.2.0" + "node-static": "^0.7.11", + "nunjucks": "^3.2.0", + "open-cli": "^5.0.0" }, "engines": { "node": ">=8.0.0" @@ -55,10 +56,14 @@ "url": "https://github.com/passport-next/passport" }, "scripts": { + "start": "static -p 8003", + "build-docs": "rm -rf docs/jsdoc/*;jsdoc --pedantic -c docs/jsdoc-config.js lib", + "open-docs": "open-cli http://localhost:8003/docs/jsdoc/ && npm start", "templates": "node ./templates/gen.js", "init-new-project": "node ./templates/gen --init", "lint": "eslint --max-warnings 0 --ext js,md .", "lintfix": "eslint --ext js,md . --fix", + "test-one": "mocha --reporter spec --require test/bootstrap/node", "test": "mocha --reporter spec --require test/bootstrap/node test/*.test.js test/**/*.test.js" } } diff --git a/templates/package.json.j2 b/templates/package.json.j2 index dff7a86a..97da2758 100644 --- a/templates/package.json.j2 +++ b/templates/package.json.j2 @@ -31,9 +31,12 @@ "eslint-plugin-promise": "^4.1.1", "eslint-plugin-standard": "^4.0.0", "eslint-plugin-unicorn": "^9.1.0", + "jsdoc": "^3.6.2", "make-node": "^0.4.6", "mocha": "6.x.x", - "nunjucks": "^3.2.0" + "node-static": "^0.7.11", + "nunjucks": "^3.2.0", + "open-cli": "^5.0.0" }, "engines": { "node": ">=8.0.0" @@ -52,10 +55,14 @@ "url": "{{github}}" }, "scripts": { + "start": "static -p 8003", + "build-docs": "rm -rf docs/jsdoc/*;jsdoc --pedantic -c docs/jsdoc-config.js lib", + "open-docs": "open-cli http://localhost:8003/docs/jsdoc/ && npm start", "templates": "node ./templates/gen.js", "init-new-project": "node ./templates/gen --init", "lint": "eslint --max-warnings 0 --ext js,md .", "lintfix": "eslint --ext js,md . --fix", + "test-one": "mocha --reporter spec --require test/bootstrap/node", "test": "mocha --reporter spec --require test/bootstrap/node test/*.test.js test/**/*.test.js", } } diff --git a/test/authenticator.middleware.test.js b/test/authenticator.middleware.test.js index 519ca5db..3a0b4bbf 100644 --- a/test/authenticator.middleware.test.js +++ b/test/authenticator.middleware.test.js @@ -1,5 +1,3 @@ -/* eslint-disable no-shadow */ - 'use strict'; const chai = require('chai'); @@ -128,9 +126,8 @@ describe('Authenticator', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -184,9 +181,8 @@ describe('Authenticator', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -227,8 +223,8 @@ describe('Authenticator', () => { describe('handling a request', () => { const passport = new Authenticator(); - passport.deserializeUser((user, done) => { - done(null, { id: user }); + passport.deserializeUser((req, user) => { + return { id: user }; }); let request; diff --git a/test/authenticator.promise.test.js b/test/authenticator.promise.test.js new file mode 100644 index 00000000..8a94141f --- /dev/null +++ b/test/authenticator.promise.test.js @@ -0,0 +1,1013 @@ +'use strict'; + +const Authenticator = require('../lib/authenticator'); + + +describe('Authenticator (Promise return)', () => { + describe('#sessionManager', () => { + it('should set custom session manager', () => { + const passport = new Authenticator(); + const sessionManager = {}; + passport.sessionManager(sessionManager); + + expect(passport._sm).to.equal(sessionManager); + }); + }); + + describe('#use', () => { + describe('with instance name', () => { + class Strategy { + constructor() { + this.name = 'default'; + } + + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use(new Strategy()); + + it('should register strategy', () => { + expect(authenticator._strategies.default).to.be.an('object'); + }); + }); + + describe('with registered name', () => { + class Strategy { + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use('foo', new Strategy()); + + it('should register strategy', () => { + expect(authenticator._strategies.foo).to.be.an('object'); + }); + }); + + describe('with registered name overriding instance name', () => { + class Strategy { + constructor() { + this.name = 'default'; + } + + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use('bar', new Strategy()); + + it('should register strategy', () => { + expect(authenticator._strategies.bar).to.be.an('object'); + // eslint-disable-next-line no-unused-expressions + expect(authenticator._strategies.default).to.be.undefined; + }); + }); + + it('should throw if lacking a name', () => { + class Strategy { + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + expect(() => { + const authenticator = new Authenticator(); + authenticator.use(new Strategy()); + }).to.throw(Error, 'Authentication strategies must have a name'); + }); + }); + + + describe('#unuse', () => { + class Strategy { + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use('one', new Strategy()); + authenticator.use('two', new Strategy()); + + expect(authenticator._strategies.one).to.be.an('object'); + expect(authenticator._strategies.two).to.be.an('object'); + + authenticator.unuse('one'); + + it('should unregister strategy', () => { + // eslint-disable-next-line no-unused-expressions + expect(authenticator._strategies.one).to.be.undefined; + expect(authenticator._strategies.two).to.be.an('object'); + }); + }); + + + describe('#serializeUser', () => { + describe('without serializers', () => { + const authenticator = new Authenticator(); + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to serialize user into session'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((req, user) => { + return Promise.resolve(user.id); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('1'); + }); + }); + + describe('with one serializer that serializes to 0', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve(0); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal(0); + }); + }); + + describe('with one serializer that serializes to false', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve(false); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to serialize user into session'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer that serializes to null', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve(null); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to serialize user into session'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer that encounters an error', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return Promise.reject(new Error('something went wrong')); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went wrong'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer that throws an exception', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser(() => { + return Promise.reject(new Error('something went horribly wrong')); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went horribly wrong'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with three serializers, the first of which passes and the second of which serializes', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser(() => { + throw new Error('pass'); + }); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve('two'); + }); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve('three'); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('two'); + }); + }); + + describe('with three serializers, the first of which passes and the second of which does not serialize by no argument', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.serializeUser((req, user, done) => { + done(null); + }); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve('three'); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('three'); + }); + }); + + describe('with three serializers, the first of which passes and the second of which does not serialize by undefined', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.serializeUser((req, user, done) => { + done(null, undefined); + }); + authenticator.serializeUser((/* req, user */) => { + return Promise.resolve('three'); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('three'); + }); + }); + + describe('with one serializer that takes request as argument', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((req, user) => { + if (req.url !== '/foo') { + return Promise.reject(new Error('incorrect req argument')); + } + return Promise.resolve(user.id); + }); + + let error; + let obj; + + before((done) => { + const req = { url: '/foo' }; + + authenticator.serializeUser({ id: '1', username: 'jared' }, req, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('1'); + }); + }); + }); + + + describe('#deserializeUser', () => { + describe('without deserializers', () => { + const authenticator = new Authenticator(); + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to deserialize user out of session'); + }); + + it('should not deserialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.undefined; + }); + }); + + describe('with one deserializer', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((req, obj) => { + return Promise.resolve(obj.username); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('jared'); + }); + }); + + describe('with one deserializer that deserializes to false', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve(false); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with one deserializer that deserializes to null', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve(null); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with one deserializer that encounters an error', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.reject(new Error('something went wrong')); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went wrong'); + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.undefined; + }); + }); + + describe('with one deserializer that throws an exception', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser(() => { + return Promise.reject(new Error('something went horribly wrong')); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went horribly wrong'); + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.undefined; + }); + }); + + describe('with three deserializers, the first of which passes and the second of which deserializes', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve('two'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve('three'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('two'); + }); + }); + + describe('with three deserializers, the first of which passes and the second of which does not deserialize by no argument', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((req, obj, done) => { + done(null); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve('three'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('three'); + }); + }); + + describe('with three deserializers, the first of which passes and the second of which does not deserialize by undefined', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((req, obj, done) => { + done(null, undefined); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve('three'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('three'); + }); + }); + + describe('with three deserializers, the first of which passes and the second of which invalidates session by false', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve(false); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve('three'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with three deserializers, the first of which passes and the second of which invalidates session by null', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve(null); + }); + authenticator.deserializeUser((/* req, obj */) => { + return Promise.resolve('three'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with one deserializer that takes request as argument', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((req, obj) => { + if (req.url !== '/foo') { + return Promise.reject(new Error('incorrect req argument')); + } + return Promise.resolve(obj.username); + }); + + let error; + let user; + + before((done) => { + const req = { url: '/foo' }; + + authenticator.deserializeUser({ id: '1', username: 'jared' }, req, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('jared'); + }); + }); + }); + + + describe('#transformAuthInfo', () => { + describe('without transforms', () => { + const authenticator = new Authenticator(); + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.scope).to.equal('write'); + }); + }); + + describe('with one transform', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((req, info) => { + return Promise.resolve({ clientId: info.clientId, client: { name: 'Foo' } }); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Foo'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + + describe('with one transform that encounters an error', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((/* req, info */) => { + return Promise.reject(new Error('something went wrong')); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went wrong'); + }); + + it('should not transform info', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one transform that throws an exception', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo(() => { + return Promise.reject(new Error('something went horribly wrong')); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went horribly wrong'); + }); + + it('should not transform info', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one sync transform', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((req, info) => { + return Promise.resolve({ clientId: info.clientId, client: { name: 'Foo' } }); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Foo'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + + describe('with three transform, the first of which passes and the second of which transforms', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.transformAuthInfo((req, info) => { + return Promise.resolve({ clientId: info.clientId, client: { name: 'Two' } }); + }); + authenticator.transformAuthInfo((req, info) => { + return Promise.resolve({ clientId: info.clientId, client: { name: 'Three' } }); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Two'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + + describe('with one transform that takes request as argument', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((req, info) => { + if (req.url !== '/foo') { + return Promise.reject(new Error('incorrect req argument')); + } + return Promise.resolve({ clientId: info.clientId, client: { name: 'Foo' } }); + }); + + let error; + let obj; + + before((done) => { + const req = { url: '/foo' }; + + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, req, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Foo'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + }); +}); diff --git a/test/authenticator.sync.test.js b/test/authenticator.sync.test.js new file mode 100644 index 00000000..90a01f77 --- /dev/null +++ b/test/authenticator.sync.test.js @@ -0,0 +1,1011 @@ +'use strict'; + +const Authenticator = require('../lib/authenticator'); + + +describe('Authenticator (Sync return)', () => { + describe('#sessionManager', () => { + it('should set custom session manager', () => { + const passport = new Authenticator(); + const sessionManager = {}; + passport.sessionManager(sessionManager); + + expect(passport._sm).to.equal(sessionManager); + }); + }); + + describe('#use', () => { + describe('with instance name', () => { + class Strategy { + constructor() { + this.name = 'default'; + } + + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use(new Strategy()); + + it('should register strategy', () => { + expect(authenticator._strategies.default).to.be.an('object'); + }); + }); + + describe('with registered name', () => { + class Strategy { + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use('foo', new Strategy()); + + it('should register strategy', () => { + expect(authenticator._strategies.foo).to.be.an('object'); + }); + }); + + describe('with registered name overriding instance name', () => { + class Strategy { + constructor() { + this.name = 'default'; + } + + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use('bar', new Strategy()); + + it('should register strategy', () => { + expect(authenticator._strategies.bar).to.be.an('object'); + // eslint-disable-next-line no-unused-expressions + expect(authenticator._strategies.default).to.be.undefined; + }); + }); + + it('should throw if lacking a name', () => { + class Strategy { + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + expect(() => { + const authenticator = new Authenticator(); + authenticator.use(new Strategy()); + }).to.throw(Error, 'Authentication strategies must have a name'); + }); + }); + + + describe('#unuse', () => { + class Strategy { + // eslint-disable-next-line class-methods-use-this + authenticate() {} + } + + const authenticator = new Authenticator(); + authenticator.use('one', new Strategy()); + authenticator.use('two', new Strategy()); + + expect(authenticator._strategies.one).to.be.an('object'); + expect(authenticator._strategies.two).to.be.an('object'); + + authenticator.unuse('one'); + + it('should unregister strategy', () => { + // eslint-disable-next-line no-unused-expressions + expect(authenticator._strategies.one).to.be.undefined; + expect(authenticator._strategies.two).to.be.an('object'); + }); + }); + + + describe('#serializeUser', () => { + describe('without serializers', () => { + const authenticator = new Authenticator(); + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to serialize user into session'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((req, user) => { + return user.id; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('1'); + }); + }); + + describe('with one serializer that serializes to 0', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return 0; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal(0); + }); + }); + + describe('with one serializer that serializes to false', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return false; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to serialize user into session'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer that serializes to null', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + return null; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to serialize user into session'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer that encounters an error', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, user */) => { + throw new Error('something went wrong'); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went wrong'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one serializer that throws an exception', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser(() => { + throw new Error('something went horribly wrong'); + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went horribly wrong'); + }); + + it('should not serialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with three serializers, the first of which passes and the second of which serializes', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.serializeUser((/* req, user */) => { + return 'two'; + }); + authenticator.serializeUser((/* req, user */) => { + return 'three'; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('two'); + }); + }); + + describe('with three serializers, the first of which passes and the second of which does not serialize by no argument', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.serializeUser((req, user, done) => { + done(null); + }); + authenticator.serializeUser((/* req, user */) => { + return 'three'; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('three'); + }); + }); + + describe('with three serializers, the first of which passes and the second of which does not serialize by undefined', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.serializeUser((req, user, done) => { + done(null, undefined); + }); + authenticator.serializeUser((/* req, user */) => { + return 'three'; + }); + + let error; + let obj; + + before((done) => { + authenticator.serializeUser({ id: '1', username: 'jared' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('three'); + }); + }); + + describe('with one serializer that takes request as argument', () => { + const authenticator = new Authenticator(); + authenticator.serializeUser((req, user) => { + if (req.url !== '/foo') { + throw new Error('incorrect req argument'); + } + return user.id; + }); + + let error; + let obj; + + before((done) => { + const req = { url: '/foo' }; + + authenticator.serializeUser({ id: '1', username: 'jared' }, req, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should serialize user', () => { + expect(obj).to.equal('1'); + }); + }); + }); + + + describe('#deserializeUser', () => { + describe('without deserializers', () => { + const authenticator = new Authenticator(); + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('Failed to deserialize user out of session'); + }); + + it('should not deserialize user', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.undefined; + }); + }); + + describe('with one deserializer', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((req, obj) => { + return obj.username; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('jared'); + }); + }); + + describe('with one deserializer that deserializes to false', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + return false; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with one deserializer that deserializes to null', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + return null; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with one deserializer that encounters an error', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('something went wrong'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went wrong'); + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.undefined; + }); + }); + + describe('with one deserializer that throws an exception', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser(() => { + throw new Error('something went horribly wrong'); + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went horribly wrong'); + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.undefined; + }); + }); + + describe('with three deserializers, the first of which passes and the second of which deserializes', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return 'two'; + }); + authenticator.deserializeUser((/* req, obj */) => { + return 'three'; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('two'); + }); + }); + + describe('with three deserializers, the first of which passes and the second of which does not deserialize by no argument', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((req, obj, done) => { + done(null); + }); + authenticator.deserializeUser((/* req, obj */) => { + return 'three'; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('three'); + }); + }); + + describe('with three deserializers, the first of which passes and the second of which does not deserialize by undefined', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((req, obj, done) => { + done(null, undefined); + }); + authenticator.deserializeUser((/* req, obj */) => { + return 'three'; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('three'); + }); + }); + + describe('with three deserializers, the first of which passes and the second of which invalidates session by false', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return false; + }); + authenticator.deserializeUser((/* req, obj */) => { + return 'three'; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with three deserializers, the first of which passes and the second of which invalidates session by null', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.deserializeUser((/* req, obj */) => { + return null; + }); + authenticator.deserializeUser((/* req, obj */) => { + return 'three'; + }); + + let error; + let user; + + before((done) => { + authenticator.deserializeUser({ id: '1', username: 'jared' }, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should invalidate session', () => { + // eslint-disable-next-line no-unused-expressions + expect(user).to.be.false; + }); + }); + + describe('with one deserializer that takes request as argument', () => { + const authenticator = new Authenticator(); + authenticator.deserializeUser((req, obj) => { + if (req.url !== '/foo') { + throw new Error('incorrect req argument'); + } + return obj.username; + }); + + let error; + let user; + + before((done) => { + const req = { url: '/foo' }; + + authenticator.deserializeUser({ id: '1', username: 'jared' }, req, (err, u) => { + error = err; + user = u; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should deserialize user', () => { + expect(user).to.equal('jared'); + }); + }); + }); + + + describe('#transformAuthInfo', () => { + describe('without transforms', () => { + const authenticator = new Authenticator(); + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.scope).to.equal('write'); + }); + }); + + describe('with one transform', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((req, info) => { + return { clientId: info.clientId, client: { name: 'Foo' } }; + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Foo'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + + describe('with one transform that encounters an error', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((/* req, info */) => { + throw new Error('something went wrong'); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went wrong'); + }); + + it('should not transform info', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one transform that throws an exception', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo(() => { + throw new Error('something went horribly wrong'); + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should error', () => { + expect(error).to.be.an.instanceOf(Error); + expect(error.message).to.equal('something went horribly wrong'); + }); + + it('should not transform info', () => { + // eslint-disable-next-line no-unused-expressions + expect(obj).to.be.undefined; + }); + }); + + describe('with one sync transform', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((req, info) => ({ clientId: info.clientId, client: { name: 'Foo' } })); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Foo'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + + describe('with three transform, the first of which passes and the second of which transforms', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((/* req, obj */) => { + throw new Error('pass'); + }); + authenticator.transformAuthInfo((req, info) => { + return { clientId: info.clientId, client: { name: 'Two' } }; + }); + authenticator.transformAuthInfo((req, info) => { + return { clientId: info.clientId, client: { name: 'Three' } }; + }); + + let error; + let obj; + + before((done) => { + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Two'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + + describe('with one transform that takes request as argument', () => { + const authenticator = new Authenticator(); + authenticator.transformAuthInfo((req, info) => { + if (req.url !== '/foo') { + throw new Error('incorrect req argument'); + } + return { clientId: info.clientId, client: { name: 'Foo' } }; + }); + + let error; + let obj; + + before((done) => { + const req = { url: '/foo' }; + + authenticator.transformAuthInfo({ clientId: '1', scope: 'write' }, req, (err, o) => { + error = err; + obj = o; + done(); + }); + }); + + it('should not error', () => { + // eslint-disable-next-line no-unused-expressions + expect(error).to.be.null; + }); + + it('should not transform info', () => { + expect(Object.keys(obj)).to.have.length(2); + expect(obj.clientId).to.equal('1'); + expect(obj.client.name).to.equal('Foo'); + // eslint-disable-next-line no-unused-expressions + expect(obj.scope).to.be.undefined; + }); + }); + }); +}); diff --git a/test/authenticator.test.js b/test/authenticator.test.js index 15a27bfc..95f001ac 100644 --- a/test/authenticator.test.js +++ b/test/authenticator.test.js @@ -47,7 +47,7 @@ describe('Authenticator', () => { }); }); - describe('with registered name overridding instance name', () => { + describe('with registered name overriding instance name', () => { class Strategy { constructor() { this.name = 'default'; @@ -131,7 +131,7 @@ describe('Authenticator', () => { describe('with one serializer', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, user.id); }); @@ -158,7 +158,7 @@ describe('Authenticator', () => { describe('with one serializer that serializes to 0', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, 0); }); @@ -185,7 +185,7 @@ describe('Authenticator', () => { describe('with one serializer that serializes to false', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, false); }); @@ -213,7 +213,7 @@ describe('Authenticator', () => { describe('with one serializer that serializes to null', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, null); }); @@ -241,7 +241,7 @@ describe('Authenticator', () => { describe('with one serializer that serializes to undefined', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, undefined); }); @@ -269,7 +269,7 @@ describe('Authenticator', () => { describe('with one serializer that encounters an error', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(new Error('something went wrong')); }); @@ -325,13 +325,13 @@ describe('Authenticator', () => { describe('with three serializers, the first of which passes and the second of which serializes', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done('pass'); }); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, 'two'); }); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, 'three'); }); @@ -358,13 +358,13 @@ describe('Authenticator', () => { describe('with three serializers, the first of which passes and the second of which does not serialize by no argument', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done('pass'); }); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null); }); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, 'three'); }); @@ -391,13 +391,13 @@ describe('Authenticator', () => { describe('with three serializers, the first of which passes and the second of which does not serialize by undefined', () => { const authenticator = new Authenticator(); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done('pass'); }); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, undefined); }); - authenticator.serializeUser((user, done) => { + authenticator.serializeUser((req, user, done) => { done(null, 'three'); }); @@ -425,8 +425,8 @@ describe('Authenticator', () => { describe('with one serializer that takes request as argument', () => { const authenticator = new Authenticator(); authenticator.serializeUser((req, user, done) => { - if (req.url !== '/foo') { return done(new Error('incorrect req argument')); } - return done(null, user.id); + if (req.url !== '/foo') { done(new Error('incorrect req argument')); return; } + done(null, user.id); }); let error; @@ -481,7 +481,7 @@ describe('Authenticator', () => { describe('with one deserializer', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, obj.username); }); @@ -508,7 +508,7 @@ describe('Authenticator', () => { describe('with one deserializer that deserializes to false', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, false); }); @@ -536,7 +536,7 @@ describe('Authenticator', () => { describe('with one deserializer that deserializes to null', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, null); }); @@ -564,7 +564,7 @@ describe('Authenticator', () => { describe('with one deserializer that deserializes to undefined', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, undefined); }); @@ -592,7 +592,7 @@ describe('Authenticator', () => { describe('with one deserializer that encounters an error', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(new Error('something went wrong')); }); @@ -648,13 +648,13 @@ describe('Authenticator', () => { describe('with three deserializers, the first of which passes and the second of which deserializes', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done('pass'); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, 'two'); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, 'three'); }); @@ -681,13 +681,13 @@ describe('Authenticator', () => { describe('with three deserializers, the first of which passes and the second of which does not deserialize by no argument', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done('pass'); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, 'three'); }); @@ -714,13 +714,13 @@ describe('Authenticator', () => { describe('with three deserializers, the first of which passes and the second of which does not deserialize by undefined', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done('pass'); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, undefined); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, 'three'); }); @@ -747,13 +747,13 @@ describe('Authenticator', () => { describe('with three deserializers, the first of which passes and the second of which invalidates session by false', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done('pass'); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, false); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, 'three'); }); @@ -781,13 +781,13 @@ describe('Authenticator', () => { describe('with three deserializers, the first of which passes and the second of which invalidates session by null', () => { const authenticator = new Authenticator(); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done('pass'); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, null); }); - authenticator.deserializeUser((obj, done) => { + authenticator.deserializeUser((req, obj, done) => { done(null, 'three'); }); @@ -816,8 +816,8 @@ describe('Authenticator', () => { describe('with one deserializer that takes request as argument', () => { const authenticator = new Authenticator(); authenticator.deserializeUser((req, obj, done) => { - if (req.url !== '/foo') { return done(new Error('incorrect req argument')); } - return done(null, obj.username); + if (req.url !== '/foo') { done(new Error('incorrect req argument')); return; } + done(null, obj.username); }); let error; @@ -873,7 +873,7 @@ describe('Authenticator', () => { describe('with one transform', () => { const authenticator = new Authenticator(); - authenticator.transformAuthInfo((info, done) => { + authenticator.transformAuthInfo((req, info, done) => { done(null, { clientId: info.clientId, client: { name: 'Foo' } }); }); @@ -904,7 +904,7 @@ describe('Authenticator', () => { describe('with one transform that encounters an error', () => { const authenticator = new Authenticator(); - authenticator.transformAuthInfo((info, done) => { + authenticator.transformAuthInfo((req, info, done) => { done(new Error('something went wrong')); }); @@ -960,7 +960,7 @@ describe('Authenticator', () => { describe('with one sync transform', () => { const authenticator = new Authenticator(); - authenticator.transformAuthInfo(info => ({ clientId: info.clientId, client: { name: 'Foo' } })); + authenticator.transformAuthInfo((req, info) => ({ clientId: info.clientId, client: { name: 'Foo' } })); let error; let obj; @@ -989,13 +989,13 @@ describe('Authenticator', () => { describe('with three transform, the first of which passes and the second of which transforms', () => { const authenticator = new Authenticator(); - authenticator.transformAuthInfo((info, done) => { + authenticator.transformAuthInfo((req, info, done) => { done('pass'); }); - authenticator.transformAuthInfo((info, done) => { + authenticator.transformAuthInfo((req, info, done) => { done(null, { clientId: info.clientId, client: { name: 'Two' } }); }); - authenticator.transformAuthInfo((info, done) => { + authenticator.transformAuthInfo((req, info, done) => { done(null, { clientId: info.clientId, client: { name: 'Three' } }); }); @@ -1027,8 +1027,8 @@ describe('Authenticator', () => { describe('with one transform that takes request as argument', () => { const authenticator = new Authenticator(); authenticator.transformAuthInfo((req, info, done) => { - if (req.url !== '/foo') { return done(new Error('incorrect req argument')); } - return done(null, { clientId: info.clientId, client: { name: 'Foo' } }); + if (req.url !== '/foo') { done(new Error('incorrect req argument')); return; } + done(null, { clientId: info.clientId, client: { name: 'Foo' } }); }); let error; diff --git a/test/http/request.test.js b/test/http/request.test.js index c243ad5b..83e95290 100644 --- a/test/http/request.test.js +++ b/test/http/request.test.js @@ -40,13 +40,14 @@ describe('http.ServerRequest', () => { req._passport.session = {}; let error; - before((done) => { + before(async () => { const user = { id: '1', username: 'root' }; - req.login(user, { session: false }, (err) => { + try { + await req.login(user, { session: false }); + } catch (err) { error = err; - done(); - }); + } }); it('should not error', () => { @@ -79,13 +80,14 @@ describe('http.ServerRequest', () => { passport._userProperty = 'currentUser'; let error; - before((done) => { + before(async () => { const user = { id: '1', username: 'root' }; - req.login(user, { session: false }, (err) => { + try { + await req.login(user, { session: false }); + } catch (err) { error = err; - done(); - }); + } }); it('should not error', () => { @@ -117,12 +119,12 @@ describe('http.ServerRequest', () => { }); }); - describe('not establishing a session and invoked without a callback', () => { + describe('not establishing a session and invoked without a callback', async () => { const { req } = setupPassport(); req._passport.session = {}; const user = { id: '1', username: 'root' }; - req.login(user, { session: false }); + await req.login(user, { session: false }); it('should be authenticated', () => { // eslint-disable-next-line no-unused-expressions @@ -147,13 +149,14 @@ describe('http.ServerRequest', () => { const { req } = setupPassport(); let error; - before((done) => { + before(async () => { const user = { id: '1', username: 'root' }; - req.login(user, { session: false }, (err) => { + try { + await req.login(user, { session: false }); + } catch (err) { error = err; - done(); - }); + } }); it('should not error', () => { @@ -177,18 +180,19 @@ describe('http.ServerRequest', () => { describe('establishing a session', () => { const { req, passport } = setupPassport(); - passport.serializeUser((user, done) => { + passport.serializeUser((rq, user, done) => { done(null, user.id); }); let error; - before((done) => { + before(async () => { const user = { id: '1', username: 'root' }; - req.login(user, (err) => { + try { + await req.login(user); + } catch (err) { error = err; - done(); - }); + } }); it('should not error', () => { @@ -216,20 +220,21 @@ describe('http.ServerRequest', () => { describe('establishing a session and setting custom user property', () => { const { req, passport } = setupPassport(); - passport.serializeUser((user, done) => { + passport.serializeUser((rq, user, done) => { done(null, user.id); }); passport._userProperty = 'currentUser'; let error; - before((done) => { + before(async () => { const user = { id: '1', username: 'root' }; - req.login(user, (err) => { + try { + await req.login(user); + } catch (err) { error = err; - done(); - }); + } }); it('should not error', () => { @@ -263,19 +268,20 @@ describe('http.ServerRequest', () => { describe('encountering an error when serializing to session', () => { const { req, passport } = setupPassport(); req._passport.session = {}; - passport.serializeUser((user, done) => { + passport.serializeUser((rq, user, done) => { done(new Error('something went wrong')); }); let error; - before((done) => { + before(async () => { const user = { id: '1', username: 'root' }; - req.login(user, (err) => { + try { + await req.login(user); + } catch (err) { error = err; - done(); - }); + } }); it('should error', () => { @@ -303,16 +309,16 @@ describe('http.ServerRequest', () => { describe('establishing a session, but not passing a callback argument', () => { const { req, passport } = setupPassport(); - passport.serializeUser((user, done) => { - done(null, user.id); + passport.serializeUser(() => { + return Promise.resolve(user.id); }); const user = { id: '1', username: 'root' }; - it('should throw an exception', () => { - expect(() => { - req.login(user); - }).to.throw(Error, 'req#login requires a callback function'); + it('should not throw an exception', () => { + expect(async () => { + await req.login(user); + }).to.not.throw(Error, 'req#login no longer requires a callback function'); }); }); }); diff --git a/test/middleware/authenticate.redirect.test.js b/test/middleware/authenticate.redirect.test.js index 72300793..ac14093f 100644 --- a/test/middleware/authenticate.redirect.test.js +++ b/test/middleware/authenticate.redirect.test.js @@ -72,9 +72,8 @@ describe('middleware/authenticate', () => { done(); }; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { diff --git a/test/middleware/authenticate.success.flash.test.js b/test/middleware/authenticate.success.flash.test.js index fc71e3a3..d482b9a8 100644 --- a/test/middleware/authenticate.success.flash.test.js +++ b/test/middleware/authenticate.success.flash.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ 'use strict'; const chai = require('chai'); @@ -31,9 +30,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -86,9 +84,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -141,9 +138,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -196,9 +192,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -251,9 +246,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -309,9 +303,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -364,9 +357,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -419,9 +411,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -474,9 +465,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -529,9 +519,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -587,9 +576,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -642,9 +630,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -697,9 +684,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -752,9 +738,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -807,9 +792,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -865,9 +849,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -920,9 +903,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -975,9 +957,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -1030,9 +1011,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; @@ -1085,9 +1065,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; req.flash = function flash(type, msg) { this.message = { type, msg }; diff --git a/test/middleware/authenticate.success.info.test.js b/test/middleware/authenticate.success.info.test.js index f4e00da8..548e0cba 100644 --- a/test/middleware/authenticate.success.info.test.js +++ b/test/middleware/authenticate.success.info.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ 'use strict'; const chai = require('chai'); @@ -26,9 +25,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -67,8 +65,8 @@ describe('middleware/authenticate', () => { const passport = new Passport(); passport.use('success', new Strategy()); - passport.transformAuthInfo((info, done) => { - done(null, { clientId: info.clientId, client: { name: 'Foo' }, scope: info.scope }); + passport.transformAuthInfo((req, info) => { + return { clientId: info.clientId, client: { name: 'Foo' }, scope: info.scope }; }); let request; @@ -79,9 +77,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -121,8 +118,8 @@ describe('middleware/authenticate', () => { const passport = new Passport(); passport.use('success', new Strategy()); - passport.transformAuthInfo((info, done) => { - done(new Error('something went wrong')); + passport.transformAuthInfo((/* req, info */) => { + throw new Error('something went wrong'); }); let request; @@ -133,9 +130,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -181,9 +177,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { diff --git a/test/middleware/authenticate.success.message.test.js b/test/middleware/authenticate.success.message.test.js index b88c3391..759dac57 100644 --- a/test/middleware/authenticate.success.message.test.js +++ b/test/middleware/authenticate.success.message.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ 'use strict'; const chai = require('chai'); @@ -30,9 +29,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { @@ -83,9 +81,8 @@ describe('middleware/authenticate', () => { req.session = {}; req.session.messages = ['I exist!']; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { @@ -136,9 +133,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { @@ -188,9 +184,8 @@ describe('middleware/authenticate', () => { request = req; req.session = {}; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { diff --git a/test/middleware/authenticate.success.multi.test.js b/test/middleware/authenticate.success.multi.test.js index 7f8f1484..68024304 100644 --- a/test/middleware/authenticate.success.multi.test.js +++ b/test/middleware/authenticate.success.multi.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ 'use strict'; const chai = require('chai'); @@ -32,9 +31,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -80,9 +78,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { diff --git a/test/middleware/authenticate.success.test.js b/test/middleware/authenticate.success.test.js index becfb4b7..85134b68 100644 --- a/test/middleware/authenticate.success.test.js +++ b/test/middleware/authenticate.success.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ 'use strict'; const chai = require('chai'); @@ -26,9 +25,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -74,9 +72,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { @@ -130,11 +127,12 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - // eslint-disable-next-line consistent-return - req.logIn = function logIn(user, options, done) { - if (options.scope !== 'email') { return done(new Error('invalid options')); } + req.logIn = function logIn(user, options) { + if (options.scope !== 'email') { + return Promise.reject(new Error('invalid options')); + } this.user = user; - done(); + return Promise.resolve(); }; }) .next((err) => { @@ -181,9 +179,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { @@ -230,9 +227,8 @@ describe('middleware/authenticate', () => { request = req; req.session = { returnTo: 'http://www.example.com/return' }; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { @@ -283,9 +279,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .end((res) => { @@ -331,8 +326,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { - done(new Error('something went wrong')); + req.logIn = function logIn() { + return Promise.reject(new Error('something went wrong')); }; }) .next((err) => { diff --git a/test/middleware/authenticate.test.js b/test/middleware/authenticate.test.js index fcf82039..dd2eb5e3 100644 --- a/test/middleware/authenticate.test.js +++ b/test/middleware/authenticate.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-shadow */ 'use strict'; const chai = require('chai'); @@ -22,9 +21,8 @@ describe('middleware/authenticate', () => { .req((req) => { request = req; - req.logIn = function logIn(user, options, done) { + req.logIn = function logIn(user) { this.user = user; - done(); }; }) .next((err) => { diff --git a/test/strategies/session.test.js b/test/strategies/session.test.js index 81ecdf3e..a5a8d898 100644 --- a/test/strategies/session.test.js +++ b/test/strategies/session.test.js @@ -42,8 +42,8 @@ describe('SessionStrategy', () => { }); describe('handling a request with a login session', () => { - const strategy = new SessionStrategy((user, req, done) => { - done(null, { id: user }); + const strategy = new SessionStrategy((user) => { + return { id: user }; }); let request; @@ -83,8 +83,8 @@ describe('SessionStrategy', () => { }); describe('handling a request with a login session serialized to 0', () => { - const strategy = new SessionStrategy((user, req, done) => { - done(null, { id: user }); + const strategy = new SessionStrategy((user) => { + return { id: user }; }); let request; @@ -124,8 +124,8 @@ describe('SessionStrategy', () => { }); describe('handling a request with a login session that has been invalidated', () => { - const strategy = new SessionStrategy((user, req, done) => { - done(null, false); + const strategy = new SessionStrategy((/* user, req */) => { + return false; }); let request; @@ -166,8 +166,8 @@ describe('SessionStrategy', () => { }); describe('handling a request with a login session and setting custom user property', () => { - const strategy = new SessionStrategy((user, req, done) => { - done(null, { id: user }); + const strategy = new SessionStrategy((user) => { + return { id: user }; }); let request; @@ -208,8 +208,8 @@ describe('SessionStrategy', () => { }); describe('handling a request with a login session that encounters an error when deserializing', () => { - const strategy = new SessionStrategy((user, req, done) => { - done(new Error('something went wrong')); + const strategy = new SessionStrategy(() => { + throw new Error('something went wrong'); }); let request;