21

Node.js で Passport.js を使用して、ログイン システムを作成します。すべて問題ありませんが、パスワードを忘れた場合や変更したい場合にユーザーパスワードをリセットする方法がわかりません。

MongoDB のユーザー モデル

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

5 に答える 5

29

特に多くのアクションのトークンを作成および検証する場合は、データベースにアクセスしてトークンを保存するという考えがあまり好きではありませんでした。

代わりに、Django が行う方法をコピーすることにしました。

  • timestamp_today を base36 に変換するtoday
  • user.id を base36 に変換します。ident
  • 以下を含む作成hash:
    • タイムスタンプ_今日
    • ユーザーID
    • user.last_login
    • 利用者パスワード
    • user.email
  • 隠された秘密でハッシュをソルトする
  • 次のようなルートを作成します: /change-password/ :ident/ :today-:hash

req.params.timestamp をテストして、今日有効かどうかを簡単にテストします。最初に最も安価なテストを行います。まず失敗。

次に、ユーザーを見つけます。存在しない場合は失敗します。

次に、上記のハッシュを再度生成しますが、req.params のタイムスタンプを使用します。

次の場合、リンクのリセットは無効になります。

  • パスワードとログインを覚えている (last_login の変更)
  • 彼らは実際にはまだログインしています。
    • パスワードを変更するだけです (パスワードの変更)
    • メールを変更するだけです (メールの変更)
  • 明日が来る (タイムスタンプが変わりすぎる)

こちらです:

  • これらの一時的なものをデータベースに保存していません
  • トークンの目的が物の状態を変更することであり、その状態が変更された場合、トークンの目的は安​​全に関連しなくなります。
于 2014-12-20T13:08:30.783 に答える
15

Matt617 が提案したように node-password-reset を使用しようとしましたが、あまり気にしませんでした。現在、検索で出てくるのはこれだけです。

そのため、数時間掘り下げたところ、これを自分で実装する方が簡単であることがわかりました。最終的に、すべてのルート、UI、電子メール、およびすべてが機能するようになるまでに約 1 日かかりました。まだセキュリティを少し強化する必要があります (悪用を防ぐためにカウンターをリセットするなど) が、基本的な機能は得られました。

  1. /forgot と /reset の 2 つの新しいルートを作成しました。これらのルートは、ユーザーがアクセスするためにログインする必要はありません。
  2. /forgot で GET を実行すると、メールの入力が 1 つある UI が表示されます。
  3. /forgot の POST は、そのアドレスを持つユーザーが存在することを確認し、ランダム トークンを生成します。
    • トークンと有効期限でユーザーの記録を更新する
    • /reset/{token} へのリンクを記載したメールを送信
  4. /reset/{token} に対する GET は、有効期限が切れていないそのトークンを持つユーザーが存在することを確認し、UI に新しいパスワード エントリを表示します。
  5. /reset の POST (新しい pwd とトークンを送信) は、有効期限が切れていないそのトークンを持つユーザーが存在することを確認します。
    • ユーザーのパスワードを更新します。
    • ユーザーのトークンと有効期限を null に設定する

トークンを生成するためのコードは次のとおりです (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');
        }
    });
});
于 2014-10-15T18:55:31.973 に答える