58

我正在寻找一种使用猫鼬将帐户保存到 MongoDB 的好方法。

我的问题是:密码是异步散列的。设置器不会在这里工作,因为它只能同步工作。

我想到了两种方法:

  • 创建模型的实例并将其保存在哈希函数的回调中。

  • 在“保存”上创建预挂钩

这个问题有什么好的解决办法吗?

4

10 回答 10

164

mongodb 博客有一篇优秀的文章详细介绍了如何实现用户身份验证。

http://blog.mongodb.org/post/32866457221/password-authentication-with-mongoose-part-1

以下内容直接从上面的链接复制:

用户模型

var mongoose = require('mongoose'),
    Schema = mongoose.Schema,
    bcrypt = require('bcrypt'),
    SALT_WORK_FACTOR = 10;
     
var UserSchema = new Schema({
    username: { type: String, required: true, index: { unique: true } },
    password: { type: String, required: true }
});
     
UserSchema.pre('save', function(next) {
    var 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, function(err, salt) {
        if (err) return next(err);

        // hash the password using our new salt
        bcrypt.hash(user.password, salt, function(err, hash) {
            if (err) return next(err);
            // override the cleartext password with the hashed one
            user.password = hash;
            next();
        });
    });
});
     
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);

用法

var mongoose = require(mongoose),
    User = require('./user-model');
     
var connStr = 'mongodb://localhost:27017/mongoose-bcrypt-test';
mongoose.connect(connStr, function(err) {
    if (err) throw err;
    console.log('Successfully connected to MongoDB');
});
     
// create a user a new user
var testUser = new User({
    username: 'jmar777',
    password: 'Password123'
});
     
// save the user to database
testUser.save(function(err) {
    if (err) throw err;
});
    
// fetch the user and test password verification
User.findOne({ username: 'jmar777' }, function(err, user) {
    if (err) throw err;
     
    // test a matching password
    user.comparePassword('Password123', function(err, isMatch) {
        if (err) throw err;
        console.log('Password123:', isMatch); // -> Password123: true
    });
     
    // test a failing password
    user.comparePassword('123Password', function(err, isMatch) {
        if (err) throw err;
        console.log('123Password:', isMatch); // -> 123Password: false
    });
});
于 2013-01-30T01:48:29.013 回答
19

对于那些愿意使用 ES6+ 语法的人可以使用这个 -

const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const { isEmail } = require('validator');

const { Schema } = mongoose;
const SALT_WORK_FACTOR = 10;

const schema = new Schema({
  email: {
    type: String,
    required: true,
    validate: [isEmail, 'invalid email'],
    createIndexes: { unique: true },
  },
  password: { type: String, required: true },
});

schema.pre('save', async function save(next) {
  if (!this.isModified('password')) return next();
  try {
    const salt = await bcrypt.genSalt(SALT_WORK_FACTOR);
    this.password = await bcrypt.hash(this.password, salt);
    return next();
  } catch (err) {
    return next(err);
  }
});

schema.methods.validatePassword = async function validatePassword(data) {
  return bcrypt.compare(data, this.password);
};

const Model = mongoose.model('User', schema);

module.exports = Model;
于 2018-11-22T13:24:51.013 回答
6

TL;DR - 打字稿解决方案

当我在寻找相同的解决方案但使用打字稿时,我来到了这里。因此,对于任何对上述问题的 TS 解决方案感兴趣的人,这里有一个我最终使用的示例。

进口&&内容:

import mongoose, { Document, Schema, HookNextFunction } from 'mongoose';
import bcrypt from 'bcryptjs';

const HASH_ROUNDS = 10;

简单的用户界面和模式定义:

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 },
});

用户模式预保存挂钩实现

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);
    }
});

密码验证方法

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

和默认导出

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

注意:不要忘记安装类型包(@types/mongoose@types/bcryptjs

于 2020-07-01T08:23:50.137 回答
4

我认为这是用户Mongoose和bcrypt的好方法!</p>

用户模型

/**
 * 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);

用法

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);
    });
}

结果

结果

于 2018-01-31T07:57:20.133 回答
2

Mongoose 官方的解决方案要求在使用 verifyPass 方法之前先保存模型,这会造成混淆。以下内容对您有用吗?(我使用的是 scrypt 而不是 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);
    });
};
于 2015-06-11T16:42:59.443 回答
1
const mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
SALT_WORK_FACTOR = 10;

const userDataModal = mongoose.Schema({
    username: {
        type: String,
        required : true,
        unique:true
    },
    password: {
        type: String,
        required : true
    }

});

userDataModal.pre('save', function(next) {
    var 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, function(err, salt) {
        if (err) return next(err);

        // hash the password using our new salt
        bcrypt.hash(user.password, salt, null, function(err, hash) {
            if (err) return next(err);

            // override the cleartext password with the hashed one
            user.password = hash;
            next();
        });
    });
});

userDataModal.methods.comparePassword = function(candidatePassword, cb) {
    bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
        if (err) return cb(err);
        cb(null, isMatch);
    });
};


// Users.index({ emaiId: "emaiId", fname : "fname", lname: "lname" });

const userDatamodal = module.exports = mongoose.model("usertemplates" , userDataModal)



//inserting document
     userDataModel.findOne({ username: reqData.username }).then(doc => {
            console.log(doc)
            if (doc == null) {
                let userDataMode = new userDataModel(reqData);
               // userDataMode.password = userDataMode.generateHash(reqData.password);
                userDataMode.save({new:true}).then(data=>{
                          let obj={
                              success:true,
                              message: "New user registered successfully",
                              data:data
                          }
                            resolve(obj)
                }).catch(err=>{
                                reject(err)
                })

            }
            else {
                resolve({
                    success: true,
                    docExists: true,
                    message: "already user registered",
                    data: doc
                }
                )
            }

        }).catch(err => {
            console.log(err)
            reject(err)
        })

//retriving and checking
      // test a matching password
                user.comparePassword(requestData.password, function(err, isMatch) {
                    if (err){ 

                        reject({
                            'status': 'Error',
                            'data': err
                        });

                        throw err;
                    } else  {
                        if(isMatch){

                            resolve({   
                                'status': true,
                                'data': user,
                                'loginStatus' : "successfully Login"
                            });

                            console.log('Password123:', isMatch); // -&gt; Password123: true

                        }
于 2019-03-25T15:55:03.010 回答
1

使用虚拟和实例方法的另一种方法:

/**
 * Virtuals
 */
schema.virtual('clean_password')
    .set(function(clean_password) {
        this._password = clean_password;
        this.password = this.encryptPassword(clean_password);
    })
    .get(function() {
        return this._password;
    });

schema.methods = {

    /**
     * Authenticate - check if the passwords are the same
     *
     * @param {String} plainText
     * @return {Boolean}
     * @api public
     */
    authenticate: function(plainPassword) {
        return bcrypt.compareSync(plainPassword, this.password);
    },

    /**
     * Encrypt password
     *
     * @param {String} password
     * @return {String}
     * @api public
     */
    encryptPassword: function(password) {
        if (!password)
            return '';

        return bcrypt.hashSync(password, 10);
    }
};

只需保存您的模型,虚拟就可以完成它的工作。

var user = {
    username: "admin",
    clean_password: "qwerty"
}

User.create(user, function(err,doc){});
于 2016-02-01T22:07:52.520 回答
1

const bcrypt = require('bcrypt');

const saltRounds = 5;
const salt = bcrypt.genSaltSync(saltRounds);

module.exports = (password) => {
  return bcrypt.hashSync(password, salt);
}

const mongoose = require('mongoose')
const Schema = mongoose.Schema
const hashPassword = require('../helpers/hashPassword')

const userSchema = new Schema({
  name: String,
  email: {
    type: String,
    match: [/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, `Please fill valid email address`],
    validate: {
      validator: function() {
        return new Promise((res, rej) =>{
          User.findOne({email: this.email, _id: {$ne: this._id}})
              .then(data => {
                  if(data) {
                      res(false)
                  } else {
                      res(true)
                  }
              })
              .catch(err => {
                  res(false)
              })
        })
      }, message: 'Email Already Taken'
    }
  },
  password: {
    type: String,
    required: [true, 'Password required']
  }
});

userSchema.pre('save', function (next) {
  if (this.password) {
      this.password = hashPassword(this.password)
  }
  next()
})

const User = mongoose.model('User', userSchema)

module.exports = User

于 2019-03-13T07:51:46.157 回答
0

经过一些研究我发现使用钩子会更好

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

它说:

用例:

异步默认值

我更喜欢这个解决方案,因为我可以封装它并确保一个帐户只能用密码保存。

于 2013-01-29T18:40:46.593 回答
0

我用.find({email})而不是.findOne({email})。

确保用于.findOne(...)获取用户。

例子:

const user = await <user>.findOne({ email });
于 2021-05-22T17:21:38.737 回答