Mongoose password hashing

前端 未结 10 952
盖世英雄少女心
盖世英雄少女心 2020-12-04 07:28

I am looking for a good way to save an Account to MongoDB using mongoose.

My problem is: The password is hashed asynchronously. A setter wont work here because it on

相关标签:
10条回答
  • 2020-12-04 07:56

    I guess it would be better to use the hook, after some research i found

    http://mongoosejs.com/docs/middleware.html

    where it says:

    Use Cases:

    asynchronous defaults

    I prefer this solution because i can encapsulate this and ensure that an account can only be saved with a password.

    0 讨论(0)
  • 2020-12-04 07:58

    TL;DR - Typescript solution

    I have arrived here when I was looking for the same solution but using typescript. So for anyone interested in TS solution to the above problem, here is an example of what I ended up using.

    imports && contants:

    import mongoose, { Document, Schema, HookNextFunction } from 'mongoose';
    import bcrypt from 'bcryptjs';
    
    const HASH_ROUNDS = 10;
    

    simple user interface and schema definition:

    export interface IUser extends Document {
        name: string;
        email: string;
        password: string;
        validatePassword(password: string): boolean;
    }
    
    const userSchema = new Schema({
        name: { type: String, required: true },
        email: { type: String, required: true, unique: true },
        password: { type: String, required: true },
    });
    

    user schema pre-save hook implementation

    userSchema.pre('save', async function (next: HookNextFunction) {
        // here we need to retype 'this' because by default it is 
        // of type Document from which the 'IUser' interface is inheriting 
        // but the Document does not know about our password property
        const thisObj = this as IUser;
    
        if (!this.isModified('password')) {
            return next();
        }
    
        try {
            const salt = await bcrypt.genSalt(HASH_ROUNDS);
            thisObj.password = await bcrypt.hash(thisObj.password, salt);
            return next();
        } catch (e) {
            return next(e);
        }
    });
    

    password validation method

    userSchema.methods.validatePassword = async function (pass: string) {
        return bcrypt.compare(pass, this.password);
    };
    

    and the default export

    export default mongoose.model<IUser>('User', userSchema);
    

    note: don't forget to install type packages (@types/mongoose, @types/bcryptjs)

    0 讨论(0)
  • 2020-12-04 07:59

    The Mongoose official solution requires the model to be saved before using the verifyPass method, which can cause confusion. Would the following work for you? (I am using scrypt instead of bcrypt).

    userSchema.virtual('pass').set(function(password) {
        this._password = password;
    });
    
    userSchema.pre('save', function(next) {
        if (this._password === undefined)
            return next();
    
        var pwBuf = new Buffer(this._password);
        var params = scrypt.params(0.1);
        scrypt.hash(pwBuf, params, function(err, hash) {
            if (err)
                return next(err);
            this.pwHash = hash;
            next();
        });
    });
    
    userSchema.methods.verifyPass = function(password, cb) {
        if (this._password !== undefined)
            return cb(null, this._password === password);
    
        var pwBuf = new Buffer(password);
        scrypt.verify(this.pwHash, pwBuf, function(err, isMatch) {
            return cb(null, !err && isMatch);
        });
    };
    
    0 讨论(0)
  • 2020-12-04 08:00

    I think this is a good way by user Mongoose and bcrypt!

    User Model

    /**
     * Module dependences
    */
    
    const mongoose = require('mongoose');
    const Schema = mongoose.Schema;
    const bcrypt = require('bcrypt');
    const SALT_WORK_FACTOR = 10;
    
    // define User Schema
    const UserSchema = new Schema({
        username: {
            type: String,
            unique: true,
            index: {
                unique: true
            }
        },
        hashed_password: {
            type: String,
            default: ''
        }
    });
    
    // Virtuals
    UserSchema
        .virtual('password')
        // set methods
        .set(function (password) {
            this._password = password;
        });
    
    UserSchema.pre("save", function (next) {
        // store reference
        const user = this;
        if (user._password === undefined) {
            return next();
        }
        bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) {
            if (err) console.log(err);
            // hash the password using our new salt
            bcrypt.hash(user._password, salt, function (err, hash) {
                if (err) console.log(err);
                user.hashed_password = hash;
                next();
            });
        });
    });
    
    /**
     * Methods
    */
    UserSchema.methods = {
        comparePassword: function(candidatePassword, cb) {
            bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
                if (err) return cb(err);
                cb(null, isMatch);
            });
        };
    }
    
    module.exports = mongoose.model('User', UserSchema);
    

    Usage

    signup: (req, res) => {
        let newUser = new User({
            username: req.body.username,
            password: req.body.password
        });
        // save user
        newUser.save((err, user) => {
            if (err) throw err;
            res.json(user);
        });
    }
    

    Result

    Result

    0 讨论(0)
提交回复
热议问题