Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 108 additions & 71 deletions src/routes/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,97 +63,116 @@ Auth.verifyToken = async function (token, done) {
}
};

Auth.reloadRoutes = async function (params) {
loginStrategies.length = 0;
const { router } = params;

// Local Logins
function registerLocalLoginStrategy() {
if (plugins.hooks.hasListeners('action:auth.overrideLogin')) {
winston.warn('[authentication] Login override detected, skipping local login strategy.');
plugins.hooks.fire('action:auth.overrideLogin');
} else {
passport.use(new passportLocal({ passReqToCallback: true }, controllers.authentication.localLogin));
return;
}

// HTTP bearer authentication
passport.use('core.api', new BearerStrategy({}, Auth.verifyToken));
passport.use(new passportLocal({ passReqToCallback: true }, controllers.authentication.localLogin));
}

// Additional logins via SSO plugins
async function loadLoginStrategies() {
try {
loginStrategies = await plugins.hooks.fire('filter:auth.init', loginStrategies);
return await plugins.hooks.fire('filter:auth.init', loginStrategies);
} catch (err) {
winston.error(`[authentication] ${err.stack}`);
return loginStrategies;
}
loginStrategies = loginStrategies || [];
loginStrategies.forEach((strategy) => {
if (strategy.url) {
router[strategy.urlMethod || 'get'](strategy.url, Auth.middleware.applyCSRF, async (req, res, next) => {
let opts = {
scope: strategy.scope,
prompt: strategy.prompt || undefined,
};

if (strategy.checkState !== false) {
req.session.ssoState = generateToken(req, true);
opts.state = req.session.ssoState;
}
if (req.query.next) {
req.session.next = req.query.next;
}
}

// Allow SSO plugins to override/append options (for use in passport prototype authorizationParams)
({ opts } = await plugins.hooks.fire('filter:auth.options', { req, res, opts }));
passport.authenticate(strategy.name, opts)(req, res, next);
});
function registerStrategyAuthRoute(router, strategy) {
if (!strategy.url) {
return;
}

router[strategy.urlMethod || 'get'](strategy.url, Auth.middleware.applyCSRF, async (req, res, next) => {
let opts = {
scope: strategy.scope,
prompt: strategy.prompt || undefined,
};

if (strategy.checkState !== false) {
req.session.ssoState = generateToken(req, true);
opts.state = req.session.ssoState;
}
if (req.query.next) {
req.session.next = req.query.next;
}

router[strategy.callbackMethod || 'get'](strategy.callbackURL, (req, res, next) => {
// Ensure the passed-back state value is identical to the saved ssoState (unless explicitly skipped)
if (strategy.checkState === false) {
return next();
}
// Allow SSO plugins to override/append options (for use in passport prototype authorizationParams)
({ opts } = await plugins.hooks.fire('filter:auth.options', { req, res, opts }));
passport.authenticate(strategy.name, opts)(req, res, next);
});
}

next(req.query.state !== req.session.ssoState ? new Error('[[error:csrf-invalid]]') : null);
}, (req, res, next) => {
// Trigger registration interstitial checks
req.session.registration = req.session.registration || {};
// save returnTo for later usage in /register/complete
// passport seems to remove `req.session.returnTo` after it redirects
req.session.registration.returnTo = req.session.next || req.session.returnTo;

passport.authenticate(strategy.name, (err, user) => {
if (err) {
if (req.session && req.session.registration) {
delete req.session.registration;
}
return next(err);
}
function validateCallbackState(strategy) {
return function (req, res, next) {
if (strategy.checkState === false) {
return next();
}

if (!user) {
if (req.session && req.session.registration) {
delete req.session.registration;
}
return helpers.redirect(res, strategy.failureUrl !== undefined ? strategy.failureUrl : '/login');
next(req.query.state !== req.session.ssoState ? new Error('[[error:csrf-invalid]]') : null);
};
}

function authenticateStrategyCallback(strategy) {
return function (req, res, next) {
// Trigger registration interstitial checks
req.session.registration = req.session.registration || {};
// save returnTo for later usage in /register/complete
// passport seems to remove `req.session.returnTo` after it redirects
req.session.registration.returnTo = req.session.next || req.session.returnTo;

passport.authenticate(strategy.name, (err, user) => {
if (err) {
if (req.session && req.session.registration) {
delete req.session.registration;
}
return next(err);
}

res.locals.user = user;
res.locals.strategy = strategy;
next();
})(req, res, next);
}, Auth.middleware.validateAuth, (req, res, next) => {
async.waterfall([
async.apply(req.login.bind(req), res.locals.user, { keepSessionInfo: true }),
async.apply(controllers.authentication.onSuccessfulLogin, req, res.locals.user.uid),
], (err) => {
if (err) {
return next(err);
if (!user) {
if (req.session && req.session.registration) {
delete req.session.registration;
}
return helpers.redirect(res, strategy.failureUrl !== undefined ? strategy.failureUrl : '/login');
}

helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/');
});
});
});
res.locals.user = user;
res.locals.strategy = strategy;
next();
})(req, res, next);
};
}

function finishStrategyLogin(strategy) {
return function (req, res, next) {
async.waterfall([
async.apply(req.login.bind(req), res.locals.user, { keepSessionInfo: true }),
async.apply(controllers.authentication.onSuccessfulLogin, req, res.locals.user.uid),
], (err) => {
if (err) {
return next(err);
}

helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/');
});
};
}

function registerStrategyCallbackRoute(router, strategy) {
router[strategy.callbackMethod || 'get'](
strategy.callbackURL,
validateCallbackState(strategy),
authenticateStrategyCallback(strategy),
Auth.middleware.validateAuth,
finishStrategyLogin(strategy)
);
}

function registerBaseAuthRoutes(router) {
const multipart = require('connect-multiparty');
const multipartMiddleware = multipart();
const middlewares = [multipartMiddleware, Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist];
Expand All @@ -163,6 +182,24 @@ Auth.reloadRoutes = async function (params) {
router.post('/register/abort', middlewares, controllers.authentication.registerAbort);
router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login);
router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout);
}

Auth.reloadRoutes = async function (params) {
loginStrategies.length = 0;
const { router } = params;

registerLocalLoginStrategy();

passport.use('core.api', new BearerStrategy({}, Auth.verifyToken));

loginStrategies = await loadLoginStrategies();
loginStrategies = loginStrategies || [];
loginStrategies.forEach((strategy) => {
registerStrategyAuthRoute(router, strategy);
registerStrategyCallbackRoute(router, strategy);
});

registerBaseAuthRoutes(router);
};

passport.serializeUser((user, done) => {
Expand Down
Loading