From 2eb90e0820816af098bec4ee38767a09acfee819 Mon Sep 17 00:00:00 2001 From: David Humphrey Date: Thu, 15 Apr 2021 12:49:32 -0400 Subject: [PATCH] Refactor auth routes into separate files --- src/api/auth/src/index.js | 4 +- src/api/auth/src/routes.js | 137 ----------------------- src/api/auth/src/routes/index.js | 13 +++ src/api/auth/src/routes/login.js | 64 +++++++++++ src/api/auth/src/routes/logout.js | 70 ++++++++++++ src/api/auth/src/routes/saml-metadata.js | 16 +++ 6 files changed, 164 insertions(+), 140 deletions(-) delete mode 100644 src/api/auth/src/routes.js create mode 100644 src/api/auth/src/routes/index.js create mode 100644 src/api/auth/src/routes/login.js create mode 100644 src/api/auth/src/routes/logout.js create mode 100644 src/api/auth/src/routes/saml-metadata.js diff --git a/src/api/auth/src/index.js b/src/api/auth/src/index.js index c0771a05cb..fd1aa61052 100644 --- a/src/api/auth/src/index.js +++ b/src/api/auth/src/index.js @@ -6,10 +6,8 @@ const RedisStore = require('connect-redis')(session); // Setup SAML SSO-based Authentication require('./authentication'); -const routes = require('./routes'); - const service = new Satellite({ - router: routes, + router: require('./routes'), beforeRouter(app) { // Initialize and use Session and Passport middleware on the app. In production // we use Redis for session storage, and in-memory otherwise. diff --git a/src/api/auth/src/routes.js b/src/api/auth/src/routes.js deleted file mode 100644 index be68ff45b8..0000000000 --- a/src/api/auth/src/routes.js +++ /dev/null @@ -1,137 +0,0 @@ -const { Router, logger } = require('@senecacdot/satellite'); -const passport = require('passport'); -const { errors } = require('celebrate'); -const createError = require('http-errors'); - -const { createToken } = require('./token'); -const { samlMetadata } = require('./authentication'); -const { - validateRedirectAndStateParams, - validateRedirectUriOrigin, - captureAuthDetailsOnSession, -} = require('./middleware'); - -const router = Router(); - -/** - * /login allows users to authenticate with the external SAML SSO provider. - * The caller needs to provide a request_uri query param to indicate where - * we should redirect - */ -router.get( - '/login', - validateRedirectAndStateParams(), - validateRedirectUriOrigin(), - captureAuthDetailsOnSession(), - passport.authenticate('saml') -); - -/** - * /login/callback is where the external SAML SSO provider will redirect - * users upon successful login. We redirect the user back to wherever - * they were trying to go, along with their original state unmodified. - */ -router.post('/login/callback', passport.authenticate('saml'), (req, res, next) => { - // We should have a login object in the session. If not, something's wrong. - if (!req.session.authDetails) { - logger.warn('/login/callback route hit without valid login session details'); - next(createError(400, `unexpected access of /login/callback`)); - return; - } - - const { redirectUri, state } = req.session.authDetails; - delete req.session.authDetails; - logger.debug({ redirectUri, state }, 'processing /login/callback'); - - // Create a token for this user, setting their authorization roles - const { user } = req; - const token = createToken( - user.email, - user.firstName, - user.lastName, - user.displayName, - user.roles, - user.avatarUrl - ); - - let url = `${redirectUri}?access_token=${token}`; - // Add the state we received before, if it was given at all - if (state) { - url += `&state=${state}`; - } - - // TODO: send back expires info too? - res.redirect(url); -}); - -/** - * /logout/callback is where the external SAML SSO provider will redirect - * users upon successful logout. We support both GET and POST, since different - * IdPs do it different ways. - */ -function logout() { - return (req, res, next) => { - // We should have a logout object in the session. If not, something's wrong. - if (!req.session.authDetails) { - logger.warn('/logout/callback route hit without valid logout session details'); - next(createError(400, `unexpected access of /logout/callback`)); - return; - } - - const { redirectUri, state } = req.session.authDetails; - delete req.session.authDetails; - logger.debug({ redirectUri, state }, 'processing /logout/callback'); - - // Clear session passport.js user info - req.logout(); - - // Add the state on the redirect if present - const url = state ? `${redirectUri}?state=${state}` : redirectUri; - res.redirect(url); - }; -} - -router.get('/logout/callback', logout()); -router.post('/logout/callback', logout()); - -/** - * /logout allows users to use Single Logout and clear login tokens - * from their passport.js session. - */ -router.get( - '/logout', - validateRedirectAndStateParams(), - validateRedirectUriOrigin(), - captureAuthDetailsOnSession(), - // passport.authenticate('saml'), - (req, res, next) => { - try { - // eslint-disable-next-line no-underscore-dangle - passport._strategy('saml').logout(req, (error, requestUrl) => { - if (error) { - logger.error({ error }, 'logout error - unable to generate logout URL'); - next(createError(500, `unable to logout`)); - } else { - res.redirect(requestUrl); - } - }); - } catch (error) { - logger.error({ error }, 'logout error'); - next(createError(500, `unable to logout`)); - } - } -); - -/** - * Provide SAML Metadata endpoint for our Service Provider's Entity ID. - * The naming is {host}/sp, for example: http://localhost/v1/auth/sp - */ -router.get('/sp', (req, res) => { - res.type('application/xml'); - res.status(200).send(samlMetadata()); -}); - -// Let Celebrate handle validation errors -router.use(errors()); - -module.exports = router; diff --git a/src/api/auth/src/routes/index.js b/src/api/auth/src/routes/index.js new file mode 100644 index 0000000000..87f5c8ac46 --- /dev/null +++ b/src/api/auth/src/routes/index.js @@ -0,0 +1,13 @@ +const { Router } = require('@senecacdot/satellite'); +const { errors } = require('celebrate'); + +const router = Router(); + +router.use('/login', require('./login')); +router.use('/logout', require('./logout')); +router.use('/sp', require('./saml-metadata')); + +// Let Celebrate handle validation errors +router.use(errors()); + +module.exports = router; diff --git a/src/api/auth/src/routes/login.js b/src/api/auth/src/routes/login.js new file mode 100644 index 0000000000..bd75273239 --- /dev/null +++ b/src/api/auth/src/routes/login.js @@ -0,0 +1,64 @@ +const { Router, logger, createError } = require('@senecacdot/satellite'); +const passport = require('passport'); + +const { createToken } = require('../token'); +const { + validateRedirectAndStateParams, + validateRedirectUriOrigin, + captureAuthDetailsOnSession, +} = require('../middleware'); + +const router = Router(); + +/** + * /login allows users to authenticate with the external SAML SSO provider. + * The caller needs to provide a request_uri query param to indicate where + * we should redirect + */ +router.get( + '/', + validateRedirectAndStateParams(), + validateRedirectUriOrigin(), + captureAuthDetailsOnSession(), + passport.authenticate('saml') +); + +/** + * /login/callback is where the external SAML SSO provider will redirect + * users upon successful login. We redirect the user back to wherever + * they were trying to go, along with their original state unmodified. + */ +router.post('/callback', passport.authenticate('saml'), (req, res, next) => { + // We should have a login object in the session. If not, something's wrong. + if (!req.session.authDetails) { + logger.warn('/login/callback route hit without valid login session details'); + next(createError(400, `unexpected access of /login/callback`)); + return; + } + + const { redirectUri, state } = req.session.authDetails; + delete req.session.authDetails; + logger.debug({ redirectUri, state }, 'processing /login/callback'); + + // Create a token for this user, setting their authorization roles + const { user } = req; + const token = createToken( + user.email, + user.firstName, + user.lastName, + user.displayName, + user.roles, + user.avatarUrl + ); + + let url = `${redirectUri}?access_token=${token}`; + // Add the state we received before, if it was given at all + if (state) { + url += `&state=${state}`; + } + + // TODO: send back expires info too? + res.redirect(url); +}); + +module.exports = router; diff --git a/src/api/auth/src/routes/logout.js b/src/api/auth/src/routes/logout.js new file mode 100644 index 0000000000..8f53c16166 --- /dev/null +++ b/src/api/auth/src/routes/logout.js @@ -0,0 +1,70 @@ +const { Router, logger, createError } = require('@senecacdot/satellite'); +const passport = require('passport'); + +const { + validateRedirectAndStateParams, + validateRedirectUriOrigin, + captureAuthDetailsOnSession, +} = require('../middleware'); + +const router = Router(); + +/** + * /logout/callback is where the external SAML SSO provider will redirect + * users upon successful logout. We support both GET and POST, since different + * IdPs do it different ways. + */ +function logout() { + return (req, res, next) => { + // We should have a logout object in the session. If not, something's wrong. + if (!req.session.authDetails) { + logger.warn('/logout/callback route hit without valid logout session details'); + next(createError(400, `unexpected access of /logout/callback`)); + return; + } + + const { redirectUri, state } = req.session.authDetails; + delete req.session.authDetails; + logger.debug({ redirectUri, state }, 'processing /logout/callback'); + + // Clear session passport.js user info + req.logout(); + + // Add the state on the redirect if present + const url = state ? `${redirectUri}?state=${state}` : redirectUri; + res.redirect(url); + }; +} + +router.get('/callback', logout()); +router.post('/callback', logout()); + +/** + * /logout allows users to use Single Logout and clear login tokens + * from their passport.js session. + */ +router.get( + '/', + validateRedirectAndStateParams(), + validateRedirectUriOrigin(), + captureAuthDetailsOnSession(), + // passport.authenticate('saml'), + (req, res, next) => { + try { + // eslint-disable-next-line no-underscore-dangle + passport._strategy('saml').logout(req, (error, requestUrl) => { + if (error) { + logger.error({ error }, 'logout error - unable to generate logout URL'); + next(createError(500, `unable to logout`)); + } else { + res.redirect(requestUrl); + } + }); + } catch (error) { + logger.error({ error }, 'logout error'); + next(createError(500, `unable to logout`)); + } + } +); + +module.exports = router; diff --git a/src/api/auth/src/routes/saml-metadata.js b/src/api/auth/src/routes/saml-metadata.js new file mode 100644 index 0000000000..e468d58f92 --- /dev/null +++ b/src/api/auth/src/routes/saml-metadata.js @@ -0,0 +1,16 @@ +const { Router } = require('@senecacdot/satellite'); + +const { samlMetadata } = require('../authentication'); + +const router = Router(); + +/** + * Provide SAML Metadata endpoint for our Service Provider's Entity ID. + * The naming is {host}/sp, for example: http://localhost/v1/auth/sp + */ +router.get('/', (req, res) => { + res.type('application/xml'); + res.status(200).send(samlMetadata()); +}); + +module.exports = router;