From 8d3524dd611751a812ea1ac508364c42f92ca2ee Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 3 Apr 2017 16:06:38 -0600 Subject: [PATCH] Added support for auth based plugins --- PLUGINS.md | 52 ++++++++++++++++++++++++++++++++ app.js | 2 +- routes/api/auth/index.js | 50 +------------------------------ services/passport.js | 64 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 117 insertions(+), 51 deletions(-) diff --git a/PLUGINS.md b/PLUGINS.md index cca319abd..819066217 100644 --- a/PLUGINS.md +++ b/PLUGINS.md @@ -233,6 +233,58 @@ module.exports = { } ``` +#### Field: `auth` + +```js +const FacebookStrategy = require('passport-facebook').Strategy; +const UsersService = require('services/users'); +const {ValidateUserLogin, HandleAuthPopupCallback} = require('services/passport'); + +module.exports = { + auth(passport) { + passport.use(new FacebookStrategy({ + clientID: process.env.TALK_FACEBOOK_APP_ID, + clientSecret: process.env.TALK_FACEBOOK_APP_SECRET, + callbackURL: `${process.env.TALK_ROOT_URL}/api/v1/auth/facebook/callback`, + passReqToCallback: true, + profileFields: ['id', 'displayName', 'picture.type(large)'] + }, async (req, accessToken, refreshToken, profile, done) => { + + let user; + try { + user = await UsersService.findOrCreateExternalUser(profile); + } catch (err) { + return done(err); + } + + return ValidateUserLogin(profile, user, done); + })); + }, + routes(router) { + const {passport} = require('services/passport'); + + /** + * Facebook auth endpoint, this will redirect the user immediatly to facebook + * for authorization. + */ + router.get('/facebook', passport.authenticate('facebook', {display: 'popup', authType: 'rerequest', scope: ['public_profile']})); + + /** + * Facebook callback endpoint, this will send the user a html page designed to + * send back the user credentials upon sucesfull login. + */ + router.get('/facebook/callback', (req, res, next) => { + + // Perform the facebook login flow and pass the data back through the opener. + passport.authenticate('facebook', HandleAuthPopupCallback(req, res, next))(req, res, next); + }); + } +}; +``` + +This is a full example including the routes hook to add the required components +to the application router to support a different auth strategy. + ### Full Example Contents of `plugins.json`: diff --git a/app.js b/app.js index 5b704bea0..3ae03ec72 100644 --- a/app.js +++ b/app.js @@ -3,7 +3,7 @@ const bodyParser = require('body-parser'); const morgan = require('morgan'); const path = require('path'); const helmet = require('helmet'); -const passport = require('./services/passport'); +const {passport} = require('./services/passport'); const session = require('express-session'); const enabled = require('debug').enabled; const RedisStore = require('connect-redis')(session); diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index fc3b87e51..12a3e8bbe 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -1,7 +1,6 @@ const express = require('express'); -const passport = require('../../../services/passport'); +const {passport, HandleAuthCallback, HandleAuthPopupCallback} = require('../../../services/passport'); const authorization = require('../../../middleware/authorization'); -const errors = require('../../../errors'); const router = express.Router(); @@ -34,53 +33,6 @@ router.delete('/', authorization.needed(), (req, res) => { // PASSPORT ROUTES //============================================================================== -/** - * This sends back the user data as JSON. - */ -const HandleAuthCallback = (req, res, next) => (err, user) => { - if (err) { - return next(err); - } - - if (!user) { - return next(errors.ErrNotAuthorized); - } - - // Perform the login of the user! - req.logIn(user, (err) => { - if (err) { - return next(err); - } - - // We logged in the user! Let's send back the user data and the CSRF token. - res.json({user}); - }); -}; - -/** - * Returns the response to the login attempt via a popup callback with some JS. - */ - -const HandleAuthPopupCallback = (req, res, next) => (err, user) => { - if (err) { - return res.render('auth-callback', {err: JSON.stringify(err), data: null}); - } - - if (!user) { - return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); - } - - // Perform the login of the user! - req.logIn(user, (err) => { - if (err) { - return res.render('auth-callback', {err: JSON.stringify(err), data: null}); - } - - // We logged in the user! Let's send back the user data. - res.render('auth-callback', {err: null, data: JSON.stringify(user)}); - }); -}; - /** * Local auth endpoint, will recieve a email and password */ diff --git a/services/passport.js b/services/passport.js index f1994392f..2d6604c3b 100644 --- a/services/passport.js +++ b/services/passport.js @@ -7,6 +7,7 @@ const LocalStrategy = require('passport-local').Strategy; const FacebookStrategy = require('passport-facebook').Strategy; const errors = require('../errors'); const debug = require('debug')('talk:passport'); +const plugins = require('./plugins'); //============================================================================== // SESSION SERIALIZATION @@ -27,6 +28,52 @@ passport.deserializeUser((id, done) => { }); }); +/** + * This sends back the user data as JSON. + */ +const HandleAuthCallback = (req, res, next) => (err, user) => { + if (err) { + return next(err); + } + + if (!user) { + return next(errors.ErrNotAuthorized); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return next(err); + } + + // We logged in the user! Let's send back the user data and the CSRF token. + res.json({user}); + }); +}; + +/** + * Returns the response to the login attempt via a popup callback with some JS. + */ +const HandleAuthPopupCallback = (req, res, next) => (err, user) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } + + if (!user) { + return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); + } + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } + + // We logged in the user! Let's send back the user data. + res.render('auth-callback', {err: null, data: JSON.stringify(user)}); + }); +}; + /** * Validates that a user is allowed to login. * @param {User} user the user to be validated @@ -329,4 +376,19 @@ if (process.env.TALK_FACEBOOK_APP_ID && process.env.TALK_FACEBOOK_APP_SECRET && console.error('Facebook cannot be enabled, missing one of TALK_FACEBOOK_APP_ID, TALK_FACEBOOK_APP_SECRET, TALK_ROOT_URL'); } -module.exports = passport; +// Inject server route plugins. +plugins.get('server', 'auth').forEach(({plugin, auth}) => { + debug(`added plugin '${plugin.name}'`); + + // Pass the passport.js instance to the plugin to allow it to inject it's + // functionality. + auth(passport); +}); + +module.exports = { + passport, + ValidateUserLogin, + HandleFailedAttempt, + HandleAuthCallback, + HandleAuthPopupCallback +};