Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import * as mongoose from 'mongoose'
- import * as timestaps from 'mongoose-timestamp'
- import * as bcrypt from 'bcrypt'
- const Schema = mongoose.Schema
- const SALT_WORK_FACTOR = 12
- const MAX_LOGIN_ATTEMPTS = 10, LOCK_TIME = 1 * 60 * 60 * 1000 // 1H
- //Schema of the user colloction
- const userSchema = new Schema({
- username: { type: String, required: true, index: { unique: true } },
- email: { type: String, required: true, index: { unique: true } },
- password: { type: String, required: true },
- bio: String,
- avatarUrl: String,
- hidden: Boolean,
- admin: Boolean,
- loginAttempts: { type: Number, required: true, default: 0 },
- lockUntil: { type: Number }
- })
- //adding timestaps [createdAt, updatedAt] to user colloction
- userSchema.plugin(timestamps)
- // For Password Securty
- userSchema.pre('save', next => {
- let user = this
- // only hash the password if it has been modified (or is new)
- if (!user.isModified('password')) return next()
- // generate a salt
- bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
- if (err) return next(err)
- // hash the password along with our new salt
- bcrypt.hash(user.password, salt, (err, hash) =>{
- if (err) return next(err)
- // override the cleartext password with the hashed one
- user.password = hash
- next()
- })
- })
- })
- /*
- * Methods
- */
- // For Comparing passwords
- userSchema.methods.comparePassword = (candidatePassword, callBack) => {
- bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
- if (err) return callBack(err)
- callBack(null, isMatch)
- })
- }
- userSchema.methods.increaseLoginAttempts = (callBack) => {
- // if we have a previous lock that has expired, restart at 1
- if (this.lockUntil && this.lockUntil < Date.now()) {
- return this.update({
- $set: { loginAttempts: 1 },
- $unset: { lockUntil: 1 }
- }, callBack)
- }
- // otherwise we're incrementing
- let updates = { $inc: { loginAttempts: 1 } }
- // lock the account if we've reached max attempts and it's not locked already
- if (this.loginAttempts + 1 >= MAX_LOGIN_ATTEMPTS && !this.isLocked) {
- updates.$set = { lockUntil: Date.now() + LOCK_TIME }
- }
- return this.update(updates, callBack)
- }
- /*
- * Statics
- */
- // error type
- let reasons = userSchema.statics.failedLogin = {
- NOT_FOUND: 0,
- PASSWORD_INCORRECT: 1,
- MAX_ATTEMPTS: 2
- }
- userSchema.statics.getAuthenticated = (username, password, callBack) => {
- this.findOne({ username: username }, (err, user) => {
- if (err) return callBack(err)
- // make sure the user exists
- if (!user) {
- return callBack(null, null, reasons.NOT_FOUND)
- }
- // check if the account is currently locked
- if (user.isLocked) {
- // just increment login attempts if account is already locked
- return user.incLoginAttempts(err => {
- if (err) return callBack(err)
- return callBack(null, null, reasons.MAX_ATTEMPTS)
- })
- }
- // test for a matching password
- user.comparePassword(password, (err, isMatch) => {
- if (err) return callBack(err)
- // check if the password was a match
- if (isMatch) {
- // if there's no lock or failed attempts, just return the user
- if (!user.loginAttempts && !user.lockUntil) return callBack(null, user)
- // reset attempts and lock info
- var updates = {
- $set: { loginAttempts: 0 },
- $unset: { lockUntil: 1 }
- }
- return user.update(updates, err => {
- if (err) return callBack(err)
- return callBack(null, user)
- })
- }
- // password is incorrect, so increment login attempts before responding
- user.incLoginAttempts(err => {
- if (err) return callBack(err)
- return callBack(null, null, reasons.PASSWORD_INCORRECT)
- })
- })
- })
- }
- //get the isLocked For this user
- userSchema.virtual('isLocked').get(() => {
- // check for a future lockUntil timestamp
- return !!(this.lockUntil && this.lockUntil > Date.now())
- })
- export const UserModel = mongoose.model('users', userSchema)
Advertisement
Add Comment
Please, Sign In to add comment