如何使用Passport.js重置/更改Node.js中的密码?

时间:2013-11-29 01:22:45

标签: node.js passport.js reset-password

我在Node.js中使用Passport.js来创建登录系统。一切都很好,但我不知道如何在忘记密码或想要更改密码时重置用户密码。

MongoDB中的用户模型

var UserSchema = new Schema({
    email: String,
    username: String,
    provider: String,
    hashed_password: String,
    salt: String,
});

5 个答案:

答案 0 :(得分:17)

我真的不喜欢点击我的数据库存储令牌的想法,特别是当您想要为许多操作创建和验证令牌时。

相反,我决定复制Django does it

的方式
  • 将timestamp_today转换为base36为today
  • 将user.id转换为base36为ident
  • 创建hash包含:
    • timestamp_today
    • user.id
    • user.last_login
    • user.password的
    • user.email
  • 用隐藏的秘密加盐哈希
  • 创建以下路线:/ change-password / :ident / :today - :hash

我们测试req.params.timestamp,以便简单地测试它是否适用于今天,最便宜的测试。先失败了。

然后我们找到用户,如果它不存在则失败。

然后我们从上面再次生成哈希,但是使用req.params的时间戳

如果出现以下情况,重置链接将失效:

  • 他们记得他们的密码和登录(last_login更改)
  • 他们实际上仍然登录并且:
    • 只需更改密码(密码更改)
    • 只需更改电子邮件(电子邮件更改)
  • 明天到来(时间戳变化太大)

这样:

  • 您没有将这些短暂的内容存储在数据库中
  • 当令牌的目的是改变事物的状态,并且状态发生变化时,令牌的目的不再是安全相关的。

答案 1 :(得分:9)

我尝试使用node-password-reset,如Matt617建议的那样,但并没有真正关心它。这是目前搜索中唯一出现的问题。

所以花了几个小时,我发现自己更容易实现这个。最后,我花了大约一天的时间来获取所有路线,用户界面,电子邮件和所有工作。我仍然需要稍微增强安全性(重置计数器以防止滥用等),但是基本工作正常:

  1. 创建了两条新路线,/忘记和/重置,这些路线不需要用户登录才能访问。
  2. GET on / forgot显示一个带有一个电子邮件输入的UI。
  3. POST /忘记检查是否有用户具有该地址并生成随机令牌。
    • 使用令牌和到期日更新用户记录
    • 发送一封电子邮件,其中包含指向/ reset / {token}
    • 的链接
  4. GET on / reset / {token}检查是否有用户使用该令牌但未过期,然后显示带有新密码输入的用户界面。
  5. POST / reset(发送新的pwd和令牌)检查是否有用户使用该令牌但未过期。
    • 更新用户密码。
    • 将用户的令牌和有效期限设置为空
  6. 这是我生成令牌的代码(取自node-password-reset):

    function generateToken() {
        var buf = new Buffer(16);
        for (var i = 0; i < buf.length; i++) {
            buf[i] = Math.floor(Math.random() * 256);
        }
        var id = buf.toString('base64');
        return id;
    }
    

    希望这有帮助。

    编辑: 这是app.js.注意我将整个用户对象保留在会话中。我计划将来搬到沙发基地或类似的地方。

    var express = require('express');
    var path = require('path');
    var favicon = require('static-favicon');
    var flash = require('connect-flash');
    var morgan = require('morgan');
    var cookieParser = require('cookie-parser');
    var cookieSession = require('cookie-session');
    var bodyParser = require('body-parser');
    var http = require('http');
    var https = require('https');
    var fs = require('fs');
    var path = require('path');
    var passport = require('passport');
    var LocalStrategy = require('passport-local').Strategy;
    
    var app = express();
    app.set('port', 3000);
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'jade');
    
    var cookies = cookieSession({
        name: 'abc123',
        secret: 'mysecret',
        maxage: 10 * 60 * 1000
    });
    app.use(cookies);
    app.use(favicon());
    app.use(flash());
    app.use(morgan());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded());
    app.use(cookieParser());
    app.use(passport.initialize());
    app.use(passport.session());
    app.use(express.static(path.join(__dirname, 'public')));
    
    module.exports = app;
    
    passport.use(new LocalStrategy(function (username, password, done) {
        return users.validateUser(username, password, done);
    }));
    
    //KEEP ENTIRE USER OBJECT IN THE SESSION
    passport.serializeUser(function (user, done) {
        done(null, user);
    });
    passport.deserializeUser(function (user, done) {
        done(null, user);
    });
    
    //Error handling after everything else
    app.use(logErrors); //log all errors
    app.use(clientErrorHandler); //special handler for xhr
    app.use(errorHandler); //basic handler
    
    http.createServer(app).listen(app.get('port'), function () {
        console.log('Express server listening on HTTP port ' + app.get('port'));
    });
    

    编辑: 这是路线。

    app.get('/forgot', function (req, res) {
        if (req.isAuthenticated()) {
            //user is alreay logged in
            return res.redirect('/');
        }
    
        //UI with one input for email
        res.render('forgot');
    });
    
    app.post('/forgot', function (req, res) {
        if (req.isAuthenticated()) {
            //user is alreay logged in
            return res.redirect('/');
        }
        users.forgot(req, res, function (err) {
            if (err) {
                req.flash('error', err);
            }
            else {
                req.flash('success', 'Please check your email for further instructions.');
            }
            res.redirect('/');
        });
    });
    
    app.get('/reset/:token', function (req, res) {
        if (req.isAuthenticated()) {
            //user is alreay logged in
            return res.redirect('/');
        }
        var token = req.params.token;
        users.checkReset(token, req, res, function (err, data) {
            if (err)
                req.flash('error', err);
    
            //show the UI with new password entry
            res.render('reset');
        });
    });
    
    app.post('/reset', function (req, res) {
        if (req.isAuthenticated()) {
            //user is alreay logged in
            return res.redirect('/');
        }
        users.reset(req, res, function (err) {
            if (err) {
                req.flash('error', err);
                return res.redirect('/reset');
            }
            else {
                req.flash('success', 'Password successfully reset.  Please login using new password.');
                return res.redirect('/login');
            }
        });
    });
    

答案 2 :(得分:2)

在数据库中创建一个随机重置键,并使用时间戳保留它。然后创建一个接受重置键的新路由。在从路由更改密码到新密码之前验证时间戳。

从未尝试过这个但是我前段时间遇到了这个问题,这与你需要的类似: https://github.com/substack/node-password-reset

答案 3 :(得分:0)

答案 4 :(得分:0)

这里是airtonix

的实现
const base64Encode = (data) => {
    let buff = new Buffer.from(data);
    return buff.toString('base64');
}

const base64Decode = (data) => {
    let buff = new Buffer.from(data, 'base64');
    return buff.toString('ascii');
}

const sha256 = (salt, password) => {
    var hash = crypto.createHash('sha512', password);
    hash.update(salt);
    var value = hash.digest('hex');
    return value;
}

   api.post('/password-reset', (req, res) => {

        try {
            const email = req.body.email;

            // Getting the user, only if active
            let query = AccountModel.where( {username: email, active: true} );
            query.select("_id salt username lastLoginDate");
            query.findOne((err, account) => {
                if(err) {
                    writeLog("ERROR", req.url + " - Error: -1 " + err.message);
                    res.status(500).send( { error: err.message, errnum: -1 } );
                    return;
                }
                if(!account){
                    writeLog("TRACE",req.url + " - Account not found!");
                    res.status(404).send( { error: "Account not found!", errnum: -2 } );
                    return;
                }

                // Generate the necessary data for the link
                const today = base64Encode(new Date().toISOString());
                const ident = base64Encode(account._id.toString());
                const data = {
                    today: today,
                    userId: account._id,
                    lastLogin: account.lastLoginDate.toISOString(),
                    password: account.salt,
                    email: account.username
                };
                const hash = sha256(JSON.stringify(data), process.env.TOKENSECRET);

                //HERE SEND AN EMAIL TO THE ACCOUNT

                return;
            });          

        } catch (err) {
            writeLog("ERROR",req.url + " - Unexpected error during the password reset process. " + err.message);
            res.status(500).send( { error: "Unexpected error during the password reset process :| " + err.message, errnum: -99 } );
            return;
        }
    });

    api.get('/password-change/:ident/:today-:hash', (req, res) => {

        try {

            // Check if the link in not out of date
            const today = base64Decode(req.params.today); 
            const then = moment(today); 
            const now = moment().utc(); 
            const timeSince = now.diff(then, 'hours');
            if(timeSince > 2) {
                writeLog("ERROR", req.url + " - The link is invalid. Err -1");
                res.status(500).send( { error: "The link is invalid.", errnum: -1 } );
                return;
            }

            const userId = base64Decode(req.params.ident);

            // Getting the user, only if active
            let query = AccountModel.where( {_id: userId, active: true} );
            query.select("_id salt username lastLoginDate");
            query.findOne((err, account) => {
                if(err) {
                    writeLog("ERROR", req.url + " - Error: -2 " + err.message);
                    res.status(500).send( { error: err.message, errnum: -2 } );
                    return;
                }
                if(!account){
                    writeLog("TRACE", req.url + " - Account not found! Err -3");
                    res.status(404).send( { error: "Account not found!", errnum: -3 } );
                    return;
                }

                // Hash again all the data to compare it with the link
                // THe link in invalid when:
                // 1. If the lastLoginDate is changed, user has already do a login 
                // 2. If the salt is changed, the user has already changed the password
                const data = {
                    today: req.params.today,
                    userId: account._id,
                    lastLogin: account.lastLoginDate.toISOString(),
                    password: account.salt,
                    email: account.username
                };
                const hash = sha256(JSON.stringify(data), process.env.TOKENSECRET);

                if(hash !== req.params.hash) {
                    writeLog("ERROR", req.url + " - The link is invalid. Err -4");
                    res.status(500).send( { error: "The link is invalid.", errnum: -4 } );
                    return;
                }

                //HERE REDIRECT TO THE CHANGE PASSWORD FORM

            });

        } catch (err) {
            writeLog("ERROR",req.url + " - Unexpected error during the password reset process. " + err.message);
            res.status(500).send( { error: "Unexpected error during the password reset process :| " + err.message, errnum: -99 } );
            return;
        }
    });

在我的应用程序中,我使用此帐户模型使用本地护照

import mongoose from 'mongoose';
import passportLocalMongoose from 'passport-local-mongoose';

const Schema = mongoose.Schema;

let accountSchema = new Schema ({
   active: { type: Boolean, default: false },
   activationDate: { type: Date },
   signupDate: { type: Date },
   lastLoginDate: { type: Date },
   lastLogoutDate: { type: Date },
   salt: {type: String},
   hash: {type: String}
});

accountSchema.plugin(passportLocalMongoose); // attach the passport-local-mongoose plugin

module.exports = mongoose.model('Account', accountSchema);