You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
5.7 KiB
JavaScript
188 lines
5.7 KiB
JavaScript
'use strict';
|
|
|
|
const async = require('async');
|
|
const passport = require('passport');
|
|
const passportLocal = require('passport-local').Strategy;
|
|
const BearerStrategy = require('passport-http-bearer').Strategy;
|
|
const winston = require('winston');
|
|
|
|
const meta = require('../meta');
|
|
const controllers = require('../controllers');
|
|
const helpers = require('../controllers/helpers');
|
|
const plugins = require('../plugins');
|
|
|
|
let loginStrategies = [];
|
|
|
|
const Auth = module.exports;
|
|
|
|
Auth.initialize = function (app, middleware) {
|
|
app.use(passport.initialize());
|
|
app.use(passport.session());
|
|
app.use((req, res, next) => {
|
|
Auth.setAuthVars(req, res);
|
|
next();
|
|
});
|
|
|
|
Auth.app = app;
|
|
Auth.middleware = middleware;
|
|
|
|
// Apply wrapper around passport.authenticate to pass in keepSessionInfo option
|
|
const _authenticate = passport.authenticate;
|
|
passport.authenticate = (strategy, options, callback) => {
|
|
if (!callback && typeof options === 'function') {
|
|
return _authenticate.call(passport, strategy, options);
|
|
}
|
|
|
|
if (!options.hasOwnProperty('keepSessionInfo')) {
|
|
options.keepSessionInfo = true;
|
|
}
|
|
|
|
return _authenticate.call(passport, strategy, options, callback);
|
|
};
|
|
};
|
|
|
|
Auth.setAuthVars = function setAuthVars(req) {
|
|
const isSpider = req.isSpider();
|
|
req.loggedIn = !isSpider && !!req.user;
|
|
if (req.user) {
|
|
req.uid = parseInt(req.user.uid, 10);
|
|
} else if (isSpider) {
|
|
req.uid = -1;
|
|
} else {
|
|
req.uid = 0;
|
|
}
|
|
};
|
|
|
|
Auth.getLoginStrategies = function () {
|
|
return loginStrategies;
|
|
};
|
|
|
|
Auth.verifyToken = async function (token, done) {
|
|
const { tokens = [] } = await meta.settings.get('core.api');
|
|
const tokenObj = tokens.find(t => t.token === token);
|
|
const uid = tokenObj ? tokenObj.uid : undefined;
|
|
|
|
if (uid !== undefined) {
|
|
if (parseInt(uid, 10) > 0) {
|
|
done(null, {
|
|
uid: uid,
|
|
});
|
|
} else {
|
|
done(null, {
|
|
master: true,
|
|
});
|
|
}
|
|
} else {
|
|
done(false);
|
|
}
|
|
};
|
|
|
|
Auth.reloadRoutes = async function (params) {
|
|
loginStrategies.length = 0;
|
|
const { router } = params;
|
|
|
|
// Local Logins
|
|
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));
|
|
}
|
|
|
|
// HTTP bearer authentication
|
|
passport.use('core.api', new BearerStrategy({}, Auth.verifyToken));
|
|
|
|
// Additional logins via SSO plugins
|
|
try {
|
|
loginStrategies = await plugins.hooks.fire('filter:auth.init', loginStrategies);
|
|
} catch (err) {
|
|
winston.error(`[authentication] ${err.stack}`);
|
|
}
|
|
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 = req.csrfToken && req.csrfToken();
|
|
opts.state = req.session.ssoState;
|
|
}
|
|
|
|
// 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);
|
|
});
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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.returnTo;
|
|
|
|
passport.authenticate(strategy.name, (err, user) => {
|
|
if (err) {
|
|
if (req.session && req.session.registration) {
|
|
delete req.session.registration;
|
|
}
|
|
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');
|
|
}
|
|
|
|
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, req.uid),
|
|
], (err) => {
|
|
if (err) {
|
|
return next(err);
|
|
}
|
|
|
|
helpers.redirect(res, strategy.successUrl !== undefined ? strategy.successUrl : '/');
|
|
});
|
|
});
|
|
});
|
|
|
|
const multipart = require('connect-multiparty');
|
|
const multipartMiddleware = multipart();
|
|
const middlewares = [multipartMiddleware, Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist];
|
|
|
|
router.post('/register', middlewares, controllers.authentication.register);
|
|
router.post('/register/complete', middlewares, controllers.authentication.registerComplete);
|
|
router.post('/register/abort', Auth.middleware.applyCSRF, controllers.authentication.registerAbort);
|
|
router.post('/login', Auth.middleware.applyCSRF, Auth.middleware.applyBlacklist, controllers.authentication.login);
|
|
router.post('/logout', Auth.middleware.applyCSRF, controllers.authentication.logout);
|
|
};
|
|
|
|
passport.serializeUser((user, done) => {
|
|
done(null, user.uid);
|
|
});
|
|
|
|
passport.deserializeUser((uid, done) => {
|
|
done(null, {
|
|
uid: uid,
|
|
});
|
|
});
|