From 9bee31198e5479910fb02b2627067a93dc8c7d0f Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 15 May 2017 10:03:03 -0600 Subject: [PATCH] Added logout routes --- routes/api/auth/index.js | 7 ++++- services/passport.js | 58 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 54932ac03..6f610e2b0 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -1,5 +1,5 @@ const express = require('express'); -const {passport, HandleGenerateCredentials} = require('../../../services/passport'); +const {passport, HandleGenerateCredentials, HandleLogout} = require('../../../services/passport'); const router = express.Router(); @@ -19,6 +19,11 @@ router.get('/', (req, res, next) => { res.json({user: req.user}); }); +/** + * This blacklists the token used to authenticate. + */ +router.delete('/', HandleLogout); + //============================================================================== // PASSPORT ROUTES //============================================================================== diff --git a/services/passport.js b/services/passport.js index 467605a1a..c64bb2334 100644 --- a/services/passport.js +++ b/services/passport.js @@ -8,6 +8,10 @@ const LocalStrategy = require('passport-local').Strategy; const errors = require('../errors'); const uuid = require('uuid'); const debug = require('debug')('talk:passport'); +const {createClient} = require('./redis'); + +// Create a redis client to use for authentication. +const client = createClient(); const { JWT_SECRET, @@ -116,6 +120,41 @@ function ValidateUserLogin(loginProfile, user, done) { // JWT STRATEGY //============================================================================== +/** + * Revoke the token on the request. + */ +const HandleLogout = (req, res, next) => { + const {jwt} = req; + + const now = new Date(); + const expiry = (jwt.exp - now.getTime() / 1000).toFixed(0); + + client.set(`jtir[${jwt.jti}]`, now.toISOString(), 'EX', expiry, (err) => { + if (err) { + return next(err); + } + + res.status(204).end(); + }); +}; + +/** + * Check if the given token is already blacklisted, throw an error if it is. + */ +const CheckBlacklisted = (jwt) => new Promise((resolve, reject) => { + client.get(`jtir[${jwt.jti}]`, (err, expiry) => { + if (err) { + return reject(err); + } + + if (expiry != null) { + return reject(new errors.ErrAuthentication('token was revoked')); + } + + return resolve(); + }); +}); + const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; @@ -136,14 +175,25 @@ passport.use(new JwtStrategy({ audience: JWT_AUDIENCE, // Enable only the HS256 algorithm. - algorithms: ['HS256'] -}, async (jwt, done) => { + algorithms: ['HS256'], + + // Pass the request objecto back to the callback so we can attach the JWT to + // it. + passReqToCallback: true +}, async (req, jwt, done) => { // Load the user from the environment, because we just got a user from the // header. try { + + // Check to see if the token has been revoked + await CheckBlacklisted(jwt); + let user = await UsersService.findById(jwt.sub); + // Attach the JWT to the request. + req.jwt = jwt; + return done(null, user); } catch(e) { return done(e); @@ -374,5 +424,7 @@ module.exports = { ValidateUserLogin, HandleFailedAttempt, HandleAuthPopupCallback, - HandleGenerateCredentials + HandleGenerateCredentials, + HandleLogout, + CheckBlacklisted };