From e440de71970722135fcb9c953fcb7defa42128aa 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 - npm/Docs: Add jsdoc with script, along with static server and open-cli to open - npm: Update devDeps - npm: Add `test-one` script --- .eslintignore | 1 + .eslintrc.js | 11 +- .gitignore | 2 +- Makefile | 4 + README.md | 28 +- 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 | 540 ++++++++- package.json | 15 +- 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 +- 30 files changed, 3414 insertions(+), 479 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 8b4dc4ae..597a2f2c 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/**'], @@ -61,9 +69,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 0cf4fbc1..4e8e94b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -docs/ +docs/jsdoc reports/ 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 39a2f69b..ed347866 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,15 @@ application must be configured. ```javascript passport.use(new LocalStrategy( - function (username, password, done) { - User.findOne({ username }, function (err, user) { - if (err) { return done(err); } - if (!user) { return done(null, false); } - if (!user.verifyPassword(password)) { return done(null, false); } - return done(null, user); - }); + async function (username, password) { + let user; + try { + user = await User.findOne({ username }); + } catch (err) { + throw err; + } + if (!user || !user.verifyPassword(password)) { return false; } + return user; } )); ``` @@ -88,14 +90,13 @@ as simple as serializing the user ID, and finding the user by ID when deserializing. ```javascript -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; }); ``` @@ -110,7 +111,6 @@ middleware must also be used. ```javascript const app = express(); app.use(require('serve-static')(path.join(__dirname, '/../../public'))); -app.use(require('cookie-parser')()); app.use(require('body-parser').urlencoded({ extended: true })); app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true })); app.use(passport.initialize()); diff --git a/docs/jsdoc-config.js b/docs/jsdoc-config.js new file mode 100644 index 00000000..f3382c1a --- /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 e0f9b443..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 {Function} middleware + * @returns {InitializeMiddleware} middleware * @public */ initialize(options) { @@ -167,9 +179,9 @@ class Authenticator { * }); * * @param {string} strategy - * @param {object} options - * @param {Function} callback - * @returns {Function} 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 {Function} callback - * @returns {Function} 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 {Function} 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 5b6a229d..7f9fdacb 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 ab2d25d9..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 {Function} 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 b9fa6c42..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 {Function} callback - * @returns {Function} + * @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 2dbea522..10895796 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,12 @@ "js-tokens": "^4.0.0" } }, + "@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", + "dev": true + }, "@babel/runtime": { "version": "7.4.4", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz", @@ -180,6 +186,12 @@ "sprintf-js": "~1.0.2" } }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, "array-includes": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", @@ -190,6 +202,12 @@ "es-abstract": "^1.7.0" } }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, "assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -220,6 +238,12 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "bluebird": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", + "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -259,6 +283,25 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "caniuse-db": { "version": "1.0.30000967", "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000967.tgz", @@ -271,6 +314,15 @@ "integrity": "sha512-rUBIbap+VJfxTzrM4akJ00lkvVb5/n5v3EGXfWzSH5zT8aJmGzjA8HWhJ4U6kCpzxozUSnB+yvAYDRPY6mRpgQ==", "dev": true }, + "catharsis": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.10.tgz", + "integrity": "sha512-l2OUaz/3PU3MZylspVFJvwHCVfWyvcduPq4lv3AzZ2pJzZCo7kNKFNyatwujD7XgvGkNAE/Jhhbh2uARNwNkfw==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -394,6 +446,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colors": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", + "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", + "dev": true + }, "comment-parser": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.5.4.tgz", @@ -425,6 +483,15 @@ "which": "^1.2.9" } }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, "debug": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", @@ -448,6 +515,24 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, "deep-eql": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", @@ -508,6 +593,12 @@ "once": "^1.4.0" } }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -641,9 +732,9 @@ } }, "eslint-config-ash-nazg": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint-config-ash-nazg/-/eslint-config-ash-nazg-6.0.1.tgz", - "integrity": "sha512-kar5/5XS6WBnuGCBgLgAiRU7h89BS8YsIvcZLnY3kVE5xr5yuup2VE/yDK465IBPvkBXgOwSYKjXjCQDUrjeiQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-ash-nazg/-/eslint-config-ash-nazg-7.0.1.tgz", + "integrity": "sha512-ehn/qLall1uvXzyQlaU0qDlIMQNdP4n8HjnGDA10W4srGyvSw8D7FcWLWVhqYVXuLf6XvUqEQJ4kduv2Y2yCjA==", "dev": true }, "eslint-config-standard": { @@ -802,13 +893,13 @@ } }, "eslint-plugin-jsdoc": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-7.0.2.tgz", - "integrity": "sha512-iqPdJsBy88FLrr8ru69Ab+SKPrjNEQwK2vicGXrBbDA6aia8IRVz2J2LPCfwdyuSxrI91bd4Za1Z57TC5tUp2w==", + "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", - "jsdoctypeparser": "3.1.0", + "jsdoctypeparser": "4.0.0", "lodash": "^4.17.11" } }, @@ -879,9 +970,9 @@ "dev": true }, "eslint-plugin-unicorn": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-8.0.2.tgz", - "integrity": "sha512-Ik2/Bt/PvPnf1lZgUnNFK2310XoRn/4LYiP5gkEPVDa4w9HCoii7I6SeKh2X5Rdp2WLy4eUiLcYtiBUp+q2IRw==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-9.1.0.tgz", + "integrity": "sha512-OknjtY1daSxHd60KsXBxP9d2LExA3JZ2XuPOtqSZbLjwGaKjZXYMCaPI2xg70Qc791rXoi0pW4UEzVCnwlRi+Q==", "dev": true, "requires": { "clean-regexp": "^1.0.0", @@ -893,6 +984,7 @@ "lodash.snakecase": "^4.0.1", "lodash.topairs": "^4.3.0", "lodash.upperfirst": "^4.2.0", + "regexpp": "^2.0.1", "reserved-words": "^0.1.2", "safe-regex": "^2.0.1" } @@ -1055,6 +1147,12 @@ "flat-cache": "^2.0.1" } }, + "file-type": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-11.1.0.tgz", + "integrity": "sha512-rM0UO7Qm9K7TWTtA6AShI/t7H5BPjDeGVDaNyg9BjHAj3PysKy7+8C8D137R88jnR3rFJZQB/tFgydl5sN5m7g==", + "dev": true + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -1126,6 +1224,12 @@ "integrity": "sha1-mYR1wXhEVobQsyJG2l3428++jqM=", "dev": true }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", @@ -1237,6 +1341,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1442,6 +1552,12 @@ "integrity": "sha512-T3FlsX8rCHAH8e7RE7PfOPZVFQlcV3XRF9eOOBQ1uf70OxO7CjjSOjeImMPCADBdYWcStAbVbYvJ1m2D3tb+EA==", "dev": true }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1476,10 +1592,61 @@ "esprima": "^4.0.0" } }, + "js2xmlparser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.0.tgz", + "integrity": "sha512-WuNgdZOXVmBk5kUPMcTcVUpbGRzLfNkv7+7APq7WiDihpXVKrgxo6wwRpRl9OQeEBgKCVk9mR7RbzrnNWC8oBw==", + "dev": true, + "requires": { + "xmlcreate": "^2.0.0" + } + }, + "jsdoc": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.6.2.tgz", + "integrity": "sha512-S2vzg99C5+gb7FWlrK4TVdyzVPGGkdvpDkCEJH1JABi2PKzPeLu5/zZffcJUifgWUJqXWl41Hoc+MmuM2GukIg==", + "dev": true, + "requires": { + "@babel/parser": "^7.4.4", + "bluebird": "^3.5.4", + "catharsis": "^0.8.10", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.0", + "klaw": "^3.0.0", + "markdown-it": "^8.4.2", + "markdown-it-anchor": "^5.0.2", + "marked": "^0.6.2", + "mkdirp": "^0.5.1", + "requizzle": "^0.2.2", + "strip-json-comments": "^3.0.1", + "taffydb": "2.6.2", + "underscore": "~1.9.1" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + } + } + }, "jsdoctypeparser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-3.1.0.tgz", - "integrity": "sha512-JNbkKpDFqbYjg+IU3FNo7qjX7Opy7CwjHywT32zgAcz/d4lX6Umn5jOHVETUdnNNgGrMk0nEx1gvP0F4M0hzlQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-4.0.0.tgz", + "integrity": "sha512-Bh6AW8eJ1bVdofhYUuqgFOVo0FE9qII+a+Go+juEnAfaDS5lZAiIqBAFm9gDu80OqBcQ1UI3v/8cP+3D5IGVww==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema-traverse": { @@ -1494,6 +1661,15 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -1513,6 +1689,15 @@ "type-check": "~0.3.2" } }, + "linkify-it": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.1.0.tgz", + "integrity": "sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==", + "dev": true, + "requires": { + "uc.micro": "^1.0.1" + } + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -1604,12 +1789,39 @@ "chalk": "^2.0.1" } }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, + "make-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", + "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.1.tgz", + "integrity": "sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ==", + "dev": true + } + } + }, "make-node": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/make-node/-/make-node-0.4.6.tgz", @@ -1625,12 +1837,43 @@ "p-defer": "^1.0.0" } }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, "markdown-escapes": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", "dev": true }, + "markdown-it": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-8.4.2.tgz", + "integrity": "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "entities": "~1.1.1", + "linkify-it": "^2.0.0", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + } + }, + "markdown-it-anchor": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-5.2.3.tgz", + "integrity": "sha512-KjEKPZNYoanmTECbh9x1bBsXGJ6dNUTxIFg5YhdBxYkx/+27LNVUzh7Ctlb7jxadgGCWMX9tt46Aaby7Af8xSg==", + "dev": true + }, + "marked": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.6.2.tgz", + "integrity": "sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==", + "dev": true + }, "mdn-browser-compat-data": { "version": "0.0.72", "resolved": "https://registry.npmjs.org/mdn-browser-compat-data/-/mdn-browser-compat-data-0.0.72.tgz", @@ -1640,6 +1883,12 @@ "extend": "3.0.2" } }, + "mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", + "dev": true + }, "mem": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", @@ -1659,6 +1908,104 @@ } } }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", @@ -1680,6 +2027,16 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, "mkdirp": { "version": "0.5.1", "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -1837,6 +2194,17 @@ "semver": "^5.3.0" } }, + "node-static": { + "version": "0.7.11", + "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.11.tgz", + "integrity": "sha512-zfWC/gICcqb74D9ndyvxZWaI1jzcoHmf4UTHWQchBNuNMxdBLJMDiUgZ1tjGLEIe/BMhj2DxKD8HOuc2062pDQ==", + "dev": true, + "requires": { + "colors": ">=0.6.0", + "mime": "^1.2.9", + "optimist": ">=0.3.4" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -1934,6 +2302,46 @@ "mimic-fn": "^1.0.0" } }, + "open": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.3.0.tgz", + "integrity": "sha512-6AHdrJxPvAXIowO/aIaeHZ8CeMdDf7qCyRNq8NwJpinmCdXhz+NZR7ie1Too94lpciCDsG+qHGO9Mt0svA4OqA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "open-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/open-cli/-/open-cli-5.0.0.tgz", + "integrity": "sha512-Y2KQDS6NqNtk+PSXzSgwH41vTDMRndwFgVWsfgMhXv7lNe1cImLCe19Vo8oKwMsL7WeNsGmmbX7Ml74Ydj61Cg==", + "dev": true, + "requires": { + "file-type": "^11.0.0", + "get-stdin": "^7.0.0", + "meow": "^5.0.0", + "open": "^6.3.0", + "temp-write": "^4.0.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -2148,6 +2556,12 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -2169,6 +2583,16 @@ "read-pkg": "^2.0.0" } }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, "regenerator-runtime": { "version": "0.13.2", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", @@ -2176,9 +2600,9 @@ "dev": true }, "regexp-tree": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.6.tgz", - "integrity": "sha512-LFrA98Dw/heXqDojz7qKFdygZmFoiVlvE1Zp7Cq2cvF+ZA+03Gmhy0k0PQlsC1jvHPiTUSs+pDHEuSWv6+6D7w==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.10.tgz", + "integrity": "sha512-K1qVSbcedffwuIslMwpe6vGlj+ZXRnGkvjAtFHfDZZZuEdA/h0dxljAPu9vhUo6Rrx2U2AwJ+nSQ6hK+lrP5MQ==", "dev": true }, "regexpp": { @@ -2240,6 +2664,15 @@ "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", "dev": true }, + "requizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.2.tgz", + "integrity": "sha512-oJ6y7JcUJkblRGhMByGNcszeLgU0qDxNKFCiUZR1XyzHyVsev+Mxb1tyygxLd1ORsKee1SA5BInFdUwY64GE/A==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, "reserved-words": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz", @@ -2432,6 +2865,12 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -2487,6 +2926,39 @@ } } }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "temp-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", + "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "dev": true + }, + "temp-write": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-4.0.0.tgz", + "integrity": "sha512-HIeWmj77uOOHb0QX7siN3OtwV3CTntquin6TNVg6SHOqCP3hYKmox90eeFOGaY1MqJ9WYDDjkyZrW6qS5AWpbw==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "is-stream": "^2.0.0", + "make-dir": "^3.0.0", + "temp-dir": "^1.0.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2514,6 +2986,12 @@ "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", "dev": true }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, "trim-trailing-lines": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.1.tgz", @@ -2557,9 +3035,21 @@ "dev": true }, "typescript": { - "version": "3.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.4.5.tgz", - "integrity": "sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", + "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "dev": true + }, + "uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true + }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==", "dev": true }, "unherit": { @@ -2634,6 +3124,12 @@ "punycode": "^2.1.0" } }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -2802,6 +3298,12 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, + "xmlcreate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.1.tgz", + "integrity": "sha512-MjGsXhKG8YjTKrDCXseFo3ClbMGvUD4en29H2Cev1dv4P/chlpw6KdYmlCWDkhosBVKRDjM836+3e3pm1cBNJA==", + "dev": true + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 7854b97f..901850f4 100644 --- a/package.json +++ b/package.json @@ -39,26 +39,33 @@ "chai-connect-middleware": "0.x.x", "eslint": "^5.16.0", "eslint-config-airbnb-base": "13.x.x", - "eslint-config-ash-nazg": "^6.0.1", + "eslint-config-ash-nazg": "^7.0.1", "eslint-config-standard": "^12.0.0", "eslint-plugin-compat": "^3.1.1", "eslint-plugin-eslint-comments": "^3.1.1", "eslint-plugin-import": "^2.17.3", - "eslint-plugin-jsdoc": "^7.0.2", + "eslint-plugin-jsdoc": "^8.0.0", "eslint-plugin-markdown": "^1.0.0", "eslint-plugin-no-use-extend-native": "^0.4.0", "eslint-plugin-node": "^9.1.0", "eslint-plugin-promise": "^4.1.1", "eslint-plugin-standard": "^4.0.0", - "eslint-plugin-unicorn": "^8.0.2", + "eslint-plugin-unicorn": "^9.1.0", + "jsdoc": "^3.6.2", "make-node": "^0.4.6", "mocha": "6.x.x", - "typescript": "^3.4.5" + "node-static": "^0.7.11", + "open-cli": "^5.0.0", + "typescript": "^3.5.1" }, "engines": { "node": ">=8.0.0" }, "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", + "test-one": "mocha --reporter spec --require test/bootstrap/node", "spec": "mocha --reporter spec --require test/bootstrap/node test/*.test.js test/**/*.test.js", "lint": "eslint --ext js,md . --max-warnings 0", "lintfix": "eslint --ext js,md . --fix", 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 98555629..2f91308d 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 47afd5c8..d7034fa4 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 8dd7c9a1..3c4663b1 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;