passport-backup-codes
Version:
Backup codes authentication strategy for Passport.
142 lines (122 loc) • 3.49 kB
JavaScript
/**
* Module dependencies.
*/
var passport = require('passport-strategy')
, util = require('util');
/**
* `Strategy` constructor.
*
* The backup codes strategy authentication strategy authenticates requests based on the
* single use code value submitted through an HTML-based form.
*
* Applications must supply a `setup` callback which accepts `user`, and then
* calls the `done` callback.
*
* Optionally, `options` can be used to change the fields in which the
* credentials are found.
*
* Options:
* - `codeField` field name where the single use code value is found, defaults to _code_
* - `window` size of time step delay window, defaults to 6
*
* Examples:
*
* passport.use(new BackupCodesStrategy(
* function (user, done) {
* if (user.codes) {
* return done(null, user.codes);
* }
* return done(null, false);
* },
* function (user, code, done) {
* user.codes = user.codes.filter((value) => {
* return code !== value;
* });
*
* return done();
* });
* ));
*
* @param {Object} options
* @param {Function} setup
* @param {Function} tearDown
* @api public
*/
function Strategy(options, setup, tearDown) {
if (typeof options == 'function') {
tearDown = setup;
setup = options;
options = {};
}
if (!setup) { throw new TypeError('BackupCodesStrategy requires a setup callback'); }
if (!tearDown) { throw new TypeError('BackupCodesStrategy requires a tearDown callback'); }
this._codeField = options.codeField || 'code';
this._passReqToCallback = options.passReqToCallback || false;
passport.Strategy.call(this);
this._setup = setup;
this._tearDown = tearDown;
this.name = 'backup-codes';
}
/**
* Inherit from `passport.Strategy`.
*/
util.inherits(Strategy, passport.Strategy);
/**
* Authenticate request based on single use code values.
*
* @param {Object} req
* @api protected
*/
Strategy.prototype.authenticate = function(req, options) {
const value = lookup(req.body, this._codeField) || lookup(req.query, this._codeField);
const self = this;
function verified(err, codes) {
if (err) { return self.error(err); }
if (!Array.isArray(codes)) {
return self.error('The `codes` provided must be an array');
}
if (!codes.includes(value)) {
return self.fail('Invalid code');
}
console.log('OK');
if (self._passReqToCallback) {
return self._tearDown(req, req.user, value, tearDownFunc);
} else {
return self._tearDown(req.user, value, tearDownFunc);
}
}
/**
* Function called to remove the code after using it
* @param err
* @param codes
* @returns {Test|undefined|void|*}
*/
function tearDownFunc(err, codes) {
if (err) { return self.error(err); }
return self.success(req.user);
}
try {
if (self._passReqToCallback) {
this._setup(req, req.user, verified);
} else {
this._setup(req.user, verified);
}
} catch (ex) {
return self.error(ex);
}
function lookup(obj, field) {
if (!obj) { return null; }
var chain = field.split(']').join('').split('[');
for (var i = 0, len = chain.length; i < len; i++) {
var prop = obj[chain[i]];
if (typeof(prop) === 'undefined') { return null; }
if (typeof(prop) !== 'object') { return prop; }
obj = prop;
}
return null;
}
};
/**
* Expose `Strategy`.
*/
module.exports = Strategy;