Node.jsのPassport.jsを使用してログインシステムを作成します。すべて問題ありませんが、ユーザーがパスワードを忘れた場合や変更したい場合に、ユーザーパスワードをリセットする方法がわかりません。
MongoDBのユーザーモデル
var UserSchema = new Schema({
email: String,
username: String,
provider: String,
hashed_password: String,
salt: String,
});
特に多くのアクションのトークンを作成して検証したい場合は、データベースにアクセスしてトークンを保存するというアイデアはあまり好きではありませんでした。
代わりに、私はどのようにコピーすることにしました Djangoがそれを行います :
today
としてtimestamp_todayをbase36に変換しますident
としてbase36に変換しますhash
を含む::ident
/:today
-:hash
Req.params.timestampをテストして、今日有効かどうかを簡単にテストします。最初に最も安価なテストを行います。最初に失敗します。
次に、ユーザーを見つけます。存在しない場合は失敗します。
次に、上からハッシュを再度生成しますが、タイムスタンプはreq.paramsからです。
次の場合、リセットリンクは無効になります。
こちらです:
Matt617が提案したように、node-password-resetを使用しようとしましたが、実際には気にしませんでした。現在検索で出てきているのはそれだけです。
そのため、何時間か調べてみると、これを自分で実装する方が簡単であることがわかりました。結局、すべてのルート、UI、電子メール、およびすべてが機能するようになるまでに約1日かかりました。私はまだセキュリティを少し強化する必要があります(乱用を防ぐためにカウンターをリセットするなど)が、基本は機能しました:
トークンを生成するための私のコードは次のとおりです(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です。ユーザーオブジェクト全体をセッションに保持していることに注意してください。将来的にはcouchbaseなどに移行する予定です。
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');
}
});
});
dBにランダムなリセットキーを作成し、タイムスタンプを付けて保持します。次に、リセットキーを受け入れる新しいルートを作成します。パスワードをルートから新しいパスワードに変更する前に、タイムスタンプを確認してください。
これを試したことはありませんが、しばらく前にこれに遭遇しました。これは、必要なものと似ています: https://github.com/substack/node-password-reset
多分この記事は役立つかもしれません:
ここで 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;
}
});
私のアプリケーションでは、このアカウントモデルでpassport-localを使用しています
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);