问题
I use Passport.js in Node.js to create a login system. Everything is ok, but I do not know how to reset user password when they forget their password or they want to change it.
User model in MongoDB
var UserSchema = new Schema({
email: String,
username: String,
provider: String,
hashed_password: String,
salt: String,
});
回答1:
Didn't really like the idea of hitting my database to store tokens, especially when you want to be creating and verifying tokens for many actions.
Instead I decided to copy how Django does it:
- convert timestamp_today to base36 as
today
- convert user.id to base36 as
ident
- create
hash
containing:- timestamp_today
- user.id
- user.last_login
- user.password
- user.email
- salt the hash with a hidden secret
- create a route like : /change-password/
:ident
/:today
-:hash
We test the req.params.timestamp in order to simply test if it's valid for today, cheapest test first. fail first.
Then we find the user, fail if it doesn't exist.
Then we generate the hash again from above, but with the timestamp from req.params
The reset link becomes invalid if :
- they remember their password and login (last_login changes)
- they're actually still logged in and:
- just change their password (password changes)
- just change their email (email changes)
- tomorrow arrives (timestamp changes too much)
This way:
- you're not storing these ephemeral things in your database
- when the purpose of the token is to change the state of a thing, and that things state changed, then the purpose of the token is no longer securely relevant.
回答2:
I tried to use node-password-reset as Matt617 suggested but didn't really care for it. It's about the only thing that's coming up in searches currently.
So some hours digging around, I found it easier to implement this on my own. In the end it took me about a day to get all the routes, UI, emails and everything working. I still need to enhance security a bit (reset counters to prevent abuse, etc.) but got the basics working:
- Created two new routes, /forgot and /reset, which don't require the user to be logged in to access.
- A GET on /forgot displays a UI with one input for email.
- A POST on /forgot checks that there is a user with that address and generates a random token.
- Update the user's record with the token and expiry date
- Send an email with a link to /reset/{token}
- A GET on /reset/{token} checks that there is a user with that token which hasn't expired then shows the UI with new password entry.
- A POST on /reset (sends new pwd and token) checks that there is a user with that token which hasn't expired.
- Update the user's password.
- Set the user's token and expiry date to null
Here's my code for generating a token (taken from 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;
}
Hope this helps.
EDIT: Here's the app.js. Note I'm keeping the entire user object in the session. I plan on moving to couchbase or similar in the future.
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'));
});
EDIT: Here are the routes.
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');
}
});
});
回答3:
create a random reset key in your DB, persist it with a timestamp. then create a new route that accepts the reset key. verify the timestamp before changing the password to the new password from the route.
never tried this but i ran across this some time ago which is similar to what you need: https://github.com/substack/node-password-reset
回答4:
Maybe this article could help:
Password Reset Emails In Your React App Made Easy with Nodemailer
来源:https://stackoverflow.com/questions/20277020/how-to-reset-change-password-in-node-js-with-passport-js