shekohex

Best UserModel

Aug 14th, 2017
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import * as mongoose from 'mongoose'
  2. import * as timestaps from 'mongoose-timestamp'
  3. import * as bcrypt from 'bcrypt'
  4. const Schema = mongoose.Schema
  5. const SALT_WORK_FACTOR = 12
  6. const MAX_LOGIN_ATTEMPTS = 10, LOCK_TIME = 1 * 60 * 60 * 1000 // 1H
  7.  
  8. //Schema of the user colloction
  9. const userSchema = new Schema({
  10.   username: { type: String, required: true, index: { unique: true } },
  11.   email: { type: String, required: true, index: { unique: true } },
  12.   password: { type: String, required: true },
  13.   bio: String,
  14.   avatarUrl: String,
  15.   hidden: Boolean,
  16.   admin: Boolean,
  17.   loginAttempts: { type: Number, required: true, default: 0 },
  18.   lockUntil: { type: Number }
  19. })
  20. //adding timestaps [createdAt, updatedAt] to user colloction
  21. userSchema.plugin(timestamps)
  22.  
  23. // For Password Securty
  24. userSchema.pre('save', next => {
  25.   let user = this
  26.   // only hash the password if it has been modified (or is new)
  27.     if (!user.isModified('password')) return next()
  28.   // generate a salt
  29.   bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
  30.     if (err) return next(err)
  31.     // hash the password along with our new salt
  32.     bcrypt.hash(user.password, salt, (err, hash) =>{
  33.       if (err) return next(err)
  34.       // override the cleartext password with the hashed one
  35.             user.password = hash
  36.             next()
  37.     })
  38.   })
  39. })
  40.  
  41. /*
  42. * Methods
  43. */
  44.  
  45. // For Comparing passwords
  46. userSchema.methods.comparePassword = (candidatePassword, callBack) => {
  47.     bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
  48.         if (err) return callBack(err)
  49.         callBack(null, isMatch)
  50.     })
  51. }
  52. userSchema.methods.increaseLoginAttempts = (callBack) =>  {
  53.     // if we have a previous lock that has expired, restart at 1
  54.     if (this.lockUntil && this.lockUntil < Date.now()) {
  55.         return this.update({
  56.             $set: { loginAttempts: 1 },
  57.             $unset: { lockUntil: 1 }
  58.         }, callBack)
  59.     }
  60.     // otherwise we're incrementing
  61.     let updates = { $inc: { loginAttempts: 1 } }
  62.     // lock the account if we've reached max attempts and it's not locked already
  63.     if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) {
  64.         updates.$set = { lockUntil: Date.now() + LOCK_TIME }
  65.     }
  66.     return this.update(updates, callBack)
  67. }
  68.  
  69. /*
  70. * Statics
  71. */
  72.  
  73. // error type
  74. let reasons = userSchema.statics.failedLogin = {
  75.     NOT_FOUND: 0,
  76.     PASSWORD_INCORRECT: 1,
  77.     MAX_ATTEMPTS: 2
  78. }
  79.  
  80. userSchema.statics.getAuthenticated = (username, password, callBack) => {
  81.     this.findOne({ username: username }, (err, user) => {
  82.         if (err) return callBack(err)
  83.  
  84.         // make sure the user exists
  85.         if (!user) {
  86.             return callBack(null, null, reasons.NOT_FOUND)
  87.         }
  88.  
  89.         // check if the account is currently locked
  90.         if (user.isLocked) {
  91.             // just increment login attempts if account is already locked
  92.             return user.incLoginAttempts(err => {
  93.                 if (err) return callBack(err)
  94.                 return callBack(null, null, reasons.MAX_ATTEMPTS)
  95.             })
  96.         }
  97.  
  98.         // test for a matching password
  99.         user.comparePassword(password, (err, isMatch) => {
  100.             if (err) return callBack(err)
  101.  
  102.             // check if the password was a match
  103.             if (isMatch) {
  104.                 // if there's no lock or failed attempts, just return the user
  105.                 if (!user.loginAttempts && !user.lockUntil) return callBack(null, user)
  106.                 // reset attempts and lock info
  107.                 var updates = {
  108.                     $set: { loginAttempts: 0 },
  109.                     $unset: { lockUntil: 1 }
  110.                 }
  111.                 return user.update(updates, err => {
  112.                     if (err) return callBack(err)
  113.                     return callBack(null, user)
  114.                 })
  115.             }
  116.  
  117.             // password is incorrect, so increment login attempts before responding
  118.             user.incLoginAttempts(err => {
  119.                 if (err) return callBack(err)
  120.                 return callBack(null, null, reasons.PASSWORD_INCORRECT)
  121.             })
  122.         })
  123.     })
  124. }
  125. //get the isLocked For this user
  126. userSchema.virtual('isLocked').get(() => {
  127.     // check for a future lockUntil timestamp
  128.     return !!(this.lockUntil && this.lockUntil > Date.now())
  129. })
  130.  
  131. export const UserModel = mongoose.model('users', userSchema)
Advertisement
Add Comment
Please, Sign In to add comment