Passport 中间件,检查用户是否已经有来自

Passport middleware, check if the user already has a living session from

我正在构建一个 Web 应用程序,使用 angular-fullstack. The stack is using express-sessions 进行会话存储(在 Mongodb 中)并使用 passport.js 进行身份验证。

我想限制每个用户只能登录一次。我正在尝试找到一种方法来检查用户在登录时是否已经有实时会话。

有没有办法以编程方式调用路由以从护照中间件查询 mongodb?

'use strict';

import path from 'path';

import passport from 'passport';
import {Strategy as LocalStrategy} from 'passport-local';

import express from 'express';
import session from 'express-session';

import _ from 'lodash';
import Session from '../../api/session/session.model';

var app = express();
require('run-middleware')(app);

function localAuthenticate(User, email, password, done, req) {
  User.findOne({
    email: email.toLowerCase()
  }).exec()
    .then(user => {

      if (!user) {
        return done(null, false, {
          message: 'This email is not registered.'
        });
      }

      // HERE is where I am trying to check if a user
      // already has a living session when they login

      // I tried to use the runMiddleware
      // to query mongodb for all the existing sessions
      // but I get this error: http://pastebin.com/YTeu5AwA
      app.runMiddleware('/sessions',{},function(code,data){
        console.log(code) // 200 
        console.log(data) // { user: '20', name: 'Moyshale' }
      });

      // Is there a way to access and use an existing route?

      user.authenticate(password, function(authError, authenticated) {
        if (authError) {
          return done(authError);
        }
        if (!authenticated) {
          return done(null, false, { message: 'This password is not correct.' });
        } else {
          return done(null, user);
        }
      });
    })
    .catch(err => done(err));
}

export function setup(User, config) {

  passport.use(new LocalStrategy({
    passReqToCallback: true,
    usernameField: 'email',
    passwordField: 'password' // this is the virtual field on the model
  }, function(req, email, password, done) {
    return localAuthenticate(User, email, password, done, req);
  }));
}

好的,我明白了,我会尝试解释我所做的。我的具体实现要求我设置用户 'seats',其中每个用户都是一个组的一部分,并且每个组一次登录次数限制为 N。

正如我在问题中提到的,我正在使用 angular 全栈 yeoman 生成器,因此此解决方案特定于该设置。

  1. 我创建了一个 'sessions' API endpoint 以便我可以查询和修改存储在 mongo 数据库中的会话。我在会话模型中包含了一个 'seat' 类型为 Number 的记录。这用于跟踪每个会话的用户座位状态。每个用户都有一个 'loginSeat' 值,用于填充此字段。此外,会话现在有一个布尔类型的 seatAllowed,true:允许用户访问该站点,false:不允许用户访问该站点。

    'use strict';
    
    import mongoose from 'mongoose';
    
    var SessionSchema = new mongoose.Schema({
      _id: String,
      session: String,
      expires: Date,
      seat: Number,
      seatAllowed: Boolean // true: the user is allowed to access the site, false: the user is not allowed access to the site
    });
    
    export default mongoose.model('Session', SessionSchema); 
    
  2. 我修改了 server/auth/login/passport.js 以便当用户登录该站点时,所有其他具有匹配席位的用户都会被淘汰。

    'use strict';
    
    import path from 'path';
    
    import passport from 'passport';
    import {Strategy as LocalStrategy} from 'passport-local';
    import _ from 'lodash';
    import Sessions from '../../api/session/session.model';
    
    function saveUpdates(updates) {
      return function(entity) {
        var updated = _.merge(entity, updates);
        return updated.save()
          .then(updated => {
            return updated;
          });
      };
    }
    
    function localAuthenticate(User, email, password, done, req) {
      User.findOne({
        email: email.toLowerCase()
      }).exec()
        .then(user => {
          if (!user) {
            return done(null, false, {
              message: 'This email is not registered.'
            });
          }
    
          // When a user logs into the site we flag their seat as allowed
          var updateSession = {'seat': user.loginSeat, 'seatAllowed': true};
    
          Sessions.findById(req.session.id).exec()
            .then(saveUpdates(updateSession))
    
          // When a user logs into the site, we disallow the seats of all other sessions with matching seat
          Sessions.find().exec()
            .then(sessions => {
    
            // Check for existing user logged in with matching login seat
            for (var i = 0; i < sessions.length; i++) {
              if (sessions[i].seat === user.loginSeat && sessions[i].id !== req.session.id) {
                console.log('DISALOW SEAT:');
                var updateSession = {'seatAllowed': false};
    
                Sessions.findById(sessions[i].id).exec()
                  .then(saveUpdates(updateSession));
              }
            }
          });
    
          user.authenticate(password, function(authError, authenticated) {
            if (authError) {
              return done(authError);
            }
            if (!authenticated) {
              return done(null, false, { message: 'This password is not correct.' });
            } else {
              return done(null, user);
            }
          });
        })
        .catch(err => done(err));
    }
    
    export function setup(User, config) {
    
      passport.use(new LocalStrategy({
        passReqToCallback: true,
        usernameField: 'email',
        passwordField: 'password' // this is the virtual field on the model
      }, function(req, email, password, done) {
        return localAuthenticate(User, email, password, done, req);
      }));
    }
    
  3. 客户端每次发出请求都会触发isAuthenticated函数。这是我检查当前会话的 seaAllowed 布尔值的地方,如果为真,则允许用户访问该站点,否则注销用户:

    function saveUpdates(updates) {
      return function(entity) {
        var updated = _.merge(entity, updates);
        return updated.save()
          .then(updated => {
            return updated;
          });
      };
    }
    
    /**
     * Attaches the user object to the request if authenticated
     * Otherwise returns 403
     */
    export function isAuthenticated() {
    
      return compose()
        // Validate jwt
        .use(function(req, res, next) {
    
          // Allow access_token to be passed through query parameter as well
          if (req.query && req.query.hasOwnProperty('access_token')) {
            req.headers.authorization = 'Bearer ' + req.query.access_token;
          }
          validateJwt(req, res, next);
    
        })
        // Attach user to request
        .use(function(req, res, next) {
    
          User.findById(req.user._id).exec()
            .then(user => {
              if (!user) {
                return res.status(401).end();
              }
              req.user = user;
    
              ///////////////////////////
              // Login seat limitation //
              ///////////////////////////
    
              // Check if the user seat is allowed
              Sessions.findById(req.session.id).exec()
                .then(thisSession => {
                  // TODO access the session in a better way
                  if (thisSession.seatAllowed === false || thisSession.seatAllowed === undefined) {
                    res.redirect('/login');
                  }
              })
              next();
            })
            .catch(err => next(err));
        });
    }
    

就是这样。