Advertisement
Guest User

Untitled

a guest
Sep 25th, 2016
106
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. var crypto = require('crypto');
  2. var LocalStrategy = require('passport-local').Strategy;
  3. var errors = require('./lib/errors.js');
  4. var scmp = require('scmp');
  5. var semver = require('semver');
  6.  
  7. var pbkdf2DigestSupport = semver.gte(process.version, '0.12.0');
  8.  
  9. module.exports = function(schema, options) {
  10.   options = options || {};
  11.   options.saltlen = options.saltlen || 32;
  12.   options.iterations = options.iterations || 25000;
  13.   options.keylen = options.keylen || 512;
  14.   options.encoding = options.encoding || 'hex';
  15.   options.digestAlgorithm = options.digestAlgorithm || 'sha256'; // To get a list of supported hashes use crypto.getHashes()
  16.   options.passwordValidator = options.passwordValidator || function(password, cb) { cb(null); };
  17.  
  18.   // Populate field names with defaults if not set
  19.   options.usernameField = options.usernameField || 'username';
  20.   options.usernameUnique = options.usernameUnique === undefined ? true : options.usernameUnique;
  21.  
  22.   // Populate username query fields with defaults if not set,
  23.   // otherwise add username field to query fields.
  24.   if (options.usernameQueryFields) {
  25.     options.usernameQueryFields.push(options.usernameField);
  26.   } else {
  27.     options.usernameQueryFields = [options.usernameField];
  28.   }
  29.  
  30.   // option to convert username to lowercase when finding
  31.   options.usernameLowerCase = options.usernameLowerCase || false;
  32.  
  33.   options.hashField = options.hashField || 'hash';
  34.   options.saltField = options.saltField || 'salt';
  35.  
  36.   if (options.limitAttempts) {
  37.     options.lastLoginField = options.lastLoginField || 'last';
  38.     options.attemptsField = options.attemptsField || 'attempts';
  39.     options.interval = options.interval || 100; // 100 ms
  40.     options.maxInterval = options.maxInterval || 300000; // 5 min
  41.     options.maxAttempts = options.maxAttempts || Infinity;
  42.   }
  43.  
  44.   options.errorMessages = options.errorMessages || {};
  45.   options.errorMessages.MissingPasswordError = options.errorMessages.MissingPasswordError || 'No password was given';
  46.   options.errorMessages.AttemptTooSoonError = options.errorMessages.AttemptTooSoonError || 'Account is currently locked. Try again later';
  47.   options.errorMessages.TooManyAttemptsError = options.errorMessages.TooManyAttemptsError || 'Account locked due to too many failed login attempts';
  48.   options.errorMessages.NoSaltValueStoredError = options.errorMessages.NoSaltValueStoredError || 'Authentication not possible. No salt value stored';
  49.   options.errorMessages.IncorrectPasswordError = options.errorMessages.IncorrectPasswordError || 'Password or username are incorrect';
  50.   options.errorMessages.IncorrectUsernameError = options.errorMessages.IncorrectUsernameError || 'Password or username are incorrect';
  51.   options.errorMessages.MissingUsernameError = options.errorMessages.MissingUsernameError|| 'No username was given';
  52.   options.errorMessages.UserExistsError = options.errorMessages.UserExistsError|| 'A user with the given username is already registered';
  53.  
  54.   var pbkdf2 = function(password, salt, callback) {
  55.     if (pbkdf2DigestSupport) {
  56.       crypto.pbkdf2(password, salt, options.iterations, options.keylen, options.digestAlgorithm, callback);
  57.     } else {
  58.       crypto.pbkdf2(password, salt, options.iterations, options.keylen, callback);
  59.     }
  60.   };
  61.  
  62.   var schemaFields = {};
  63.  
  64.   if (!schema.path(options.usernameField)) {
  65.     schemaFields[options.usernameField] = {type: String, unique: options.usernameUnique};
  66.   }
  67.   schemaFields[options.hashField] = {type: String, select: false};
  68.   schemaFields[options.saltField] = {type: String, select: false};
  69.  
  70.   if (options.limitAttempts) {
  71.     schemaFields[options.attemptsField] = {type: Number, default: 0};
  72.     schemaFields[options.lastLoginField] = {type: Date, default: Date.now};
  73.   }
  74.  
  75.   schema.add(schemaFields);
  76.  
  77.   schema.pre('save', function(next) {
  78.     // if specified, convert the username to lowercase
  79.     if (options.usernameLowerCase && this[options.usernameField]) {
  80.       this[options.usernameField] = this[options.usernameField].toLowerCase();
  81.     }
  82.  
  83.     next();
  84.   });
  85.  
  86.   schema.methods.setPassword = function(password, cb) {
  87.     if (!password) {
  88.       return cb(new errors.MissingPasswordError(options.errorMessages.MissingPasswordError));
  89.     }
  90.  
  91.     var self = this;
  92.  
  93.     options.passwordValidator(password, function(err) {
  94.       if (err) {
  95.         return cb(err);
  96.       }
  97.  
  98.       crypto.randomBytes(options.saltlen, function(randomBytesErr, buf) {
  99.         if (randomBytesErr) {
  100.           return cb(randomBytesErr);
  101.         }
  102.  
  103.         var salt = buf.toString(options.encoding);
  104.  
  105.         pbkdf2(password, salt, function(pbkdf2Err, hashRaw) {
  106.           if (pbkdf2Err) {
  107.             return cb(pbkdf2Err);
  108.           }
  109.  
  110.           self.set(options.hashField, new Buffer(hashRaw, 'binary').toString(options.encoding));
  111.           self.set(options.saltField, salt);
  112.  
  113.           cb(null, self);
  114.         });
  115.       });
  116.     });
  117.   };
  118.  
  119.   function authenticate(user, password, cb) {
  120.     if (options.limitAttempts) {
  121.       var attemptsInterval = Math.pow(options.interval, Math.log(user.get(options.attemptsField) + 1));
  122.       var calculatedInterval = (attemptsInterval < options.maxInterval) ? attemptsInterval : options.maxInterval;
  123.  
  124.       if (Date.now() - user.get(options.lastLoginField) < calculatedInterval) {
  125.         user.set(options.lastLoginField, Date.now());
  126.         user.save();
  127.         return cb(null, false, new errors.AttemptTooSoonError(options.errorMessages.AttemptTooSoonError));
  128.       }
  129.  
  130.       if (user.get(options.attemptsField) >= options.maxAttempts) {
  131.         return cb(null, false, new errors.TooManyAttemptsError(options.errorMessages.TooManyAttemptsError));
  132.       }
  133.     }
  134.  
  135.     if (!user.get(options.saltField)) {
  136.       return cb(null, false, new errors.NoSaltValueStoredError(options.errorMessages.NoSaltValueStoredError));
  137.     }
  138.  
  139.     pbkdf2(password, user.get(options.saltField), function(err, hashRaw) {
  140.       if (err) {
  141.         return cb(err);
  142.       }
  143.  
  144.       var hash = new Buffer(hashRaw, 'binary').toString(options.encoding);
  145.  
  146.       if (scmp(hash, user.get(options.hashField))) {
  147.         if (options.limitAttempts) {
  148.           user.set(options.lastLoginField, Date.now());
  149.           user.set(options.attemptsField, 0);
  150.           user.save();
  151.         }
  152.         return cb(null, user);
  153.       } else {
  154.         if (options.limitAttempts) {
  155.           user.set(options.lastLoginField, Date.now());
  156.           user.set(options.attemptsField, user.get(options.attemptsField) + 1);
  157.           user.save(function(saveErr) {
  158.             if (saveErr) { return cb(saveErr); }
  159.             if (user.get(options.attemptsField) >= options.maxAttempts) {
  160.               return cb(null, false, new errors.TooManyAttemptsError(options.errorMessages.TooManyAttemptsError));
  161.             } else {
  162.               return cb(null, false, new errors.IncorrectPasswordError(options.errorMessages.IncorrectPasswordError));
  163.             }
  164.           });
  165.         } else {
  166.           return cb(null, false, new errors.IncorrectPasswordError(options.errorMessages.IncorrectPasswordError));
  167.         }
  168.       }
  169.     });
  170.  
  171.   }
  172.  
  173.   schema.methods.authenticate = function(password, cb) {
  174.     var self = this;
  175.  
  176.     // With hash/salt marked as "select: false" - load model including the salt/hash fields form db and authenticate
  177.     if (!self.get(options.saltField)) {
  178.       self.constructor.findByUsername(self.get(options.usernameField), true, function(err, user) {
  179.         if (err) { return cb(err); }
  180.  
  181.         if (user) {
  182.           return authenticate(user, password, cb);
  183.         } else {
  184.           return cb(null, false, new errors.IncorrectUsernameError(options.errorMessages.IncorrectUsernameError));
  185.         }
  186.       });
  187.     } else {
  188.       return authenticate(self, password, cb);
  189.     }
  190.   };
  191.  
  192.   if (options.limitAttempts) {
  193.     schema.methods.resetAttempts = function(cb) {
  194.       this.set(options.attemptsField, 0);
  195.       this.save(cb);
  196.     };
  197.   }
  198.  
  199.   schema.statics.authenticate = function() {
  200.     var self = this;
  201.  
  202.     return function(username, password, cb) {
  203.       self.findByUsername(username, true, function(err, user) {
  204.         if (err) { return cb(err); }
  205.  
  206.         if (user) {
  207.           return user.authenticate(password, cb);
  208.         } else {
  209.           return cb(null, false, new errors.IncorrectUsernameError(options.errorMessages.IncorrectUsernameError));
  210.         }
  211.       });
  212.     };
  213.   };
  214.  
  215.   schema.statics.serializeUser = function() {
  216.     return function(user, cb) {
  217.       cb(null, user.get(options.usernameField));
  218.     };
  219.   };
  220.  
  221.   schema.statics.deserializeUser = function() {
  222.     var self = this;
  223.  
  224.     return function(username, cb) {
  225.       self.findByUsername(username, cb);
  226.     };
  227.   };
  228.  
  229.   schema.statics.register = function(user, password, cb) {
  230.     // Create an instance of this in case user isn't already an instance
  231.     if (!(user instanceof this)) {
  232.       user = new this(user);
  233.     }
  234.  
  235.     if (!user.get(options.usernameField)) {
  236.       return cb(new errors.MissingUsernameError(options.errorMessages.MissingUsernameError));
  237.     }
  238.  
  239.     var self = this;
  240.     self.findByUsername(user.get(options.usernameField), function(err, existingUser) {
  241.       if (err) { return cb(err); }
  242.  
  243.       if (existingUser) {
  244.         return cb(new errors.UserExistsError(options.errorMessages.UserExistsError));
  245.       }
  246.  
  247.       user.setPassword(password, function(setPasswordErr, user) {
  248.         if (setPasswordErr) {
  249.           return cb(setPasswordErr);
  250.         }
  251.  
  252.         user.save(function(saveErr) {
  253.           if (saveErr) {
  254.             return cb(saveErr);
  255.           }
  256.  
  257.           cb(null, user);
  258.         });
  259.       });
  260.     });
  261.   };
  262.  
  263.   schema.statics.findByUsername = function(username, selectHashSaltFields, cb) {
  264.     if (typeof cb === 'undefined') {
  265.       cb = selectHashSaltFields;
  266.       selectHashSaltFields = false;
  267.     }
  268.  
  269.     // if specified, convert the username to lowercase
  270.     if (username !== undefined && options.usernameLowerCase) {
  271.       username = username.toLowerCase();
  272.     }
  273.  
  274.     // Add each username query field
  275.     var queryOrParameters = [];
  276.     for (var i = 0; i < options.usernameQueryFields.length; i++) {
  277.       var parameter = {};
  278.       parameter[options.usernameQueryFields[i]] = username;
  279.       queryOrParameters.push(parameter);
  280.     }
  281.  
  282.     var query = this.findOne({$or: queryOrParameters});
  283.  
  284.     if (selectHashSaltFields) {
  285.       query.select('+' + options.hashField + " +" + options.saltField);
  286.     }
  287.  
  288.     if (options.selectFields) {
  289.       query.select(options.selectFields);
  290.     }
  291.  
  292.     if (options.populateFields) {
  293.       query.populate(options.populateFields);
  294.     }
  295.  
  296.     if (cb) {
  297.       query.exec(cb);
  298.     } else {
  299.       return query;
  300.     }
  301.   };
  302.  
  303.   schema.statics.createStrategy = function() {
  304.     return new LocalStrategy(options, this.authenticate());
  305.   };
  306. };
  307.  
  308. module.exports.errors = errors;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement