started with Multi-Tenant OpenID.

TODO:
working code but needs some refactoring and cleaning up.
This commit is contained in:
Ruben Talstra 2025-02-08 13:12:07 +01:00
parent d786bf263c
commit 6577144554
Failed to extract signature
10 changed files with 350 additions and 58 deletions

View file

@ -52,10 +52,9 @@ router.get('/', async function (req, res) {
!!process.env.APPLE_KEY_ID &&
!!process.env.APPLE_PRIVATE_KEY_PATH,
openidLoginEnabled:
!!process.env.OPENID_CLIENT_ID &&
!!process.env.OPENID_CLIENT_SECRET &&
!!process.env.OPENID_ISSUER &&
!!process.env.OPENID_ENABLED &&
!!process.env.OPENID_SESSION_SECRET,
openidMultiTenantEnabled: !!process.env.OPENID_MULTI_TENANT,
openidLabel: process.env.OPENID_BUTTON_LABEL || 'Continue with OpenID',
openidImageUrl: process.env.OPENID_IMAGE_URL,
serverDomain: process.env.DOMAIN_SERVER || 'http://localhost:3080',

View file

@ -4,6 +4,7 @@ const passport = require('passport');
const { loginLimiter, checkBan, checkDomainAllowed } = require('~/server/middleware');
const { setAuthTokens } = require('~/server/services/AuthService');
const { logger } = require('~/config');
const { chooseOpenIdStrategy } = require('~/server/utils/openidHelper');
const router = express.Router();
@ -30,7 +31,7 @@ const oauthHandler = async (req, res) => {
router.get('/error', (req, res) => {
// A single error message is pushed by passport when authentication fails.
logger.error('Error in OAuth authentication:', { message: req.session.messages.pop() });
logger.error('Error in OAuth authentication:', { message: req.session?.messages?.pop() });
res.redirect(`${domains.client}/login`);
});
@ -83,20 +84,32 @@ router.get(
/**
* OpenID Routes
*/
router.get(
'/openid',
passport.authenticate('openid', {
session: false,
}),
);
router.get('/openid', async (req, res, next) => {
try {
const strategy = await chooseOpenIdStrategy(req);
console.log('OpenID login using strategy:', strategy);
passport.authenticate(strategy, {
session: false,
})(req, res, next);
} catch (err) {
next(err);
}
});
router.get(
'/openid/callback',
passport.authenticate('openid', {
failureRedirect: `${domains.client}/oauth/error`,
failureMessage: true,
session: false,
}),
async (req, res, next) => {
try {
const strategy = await chooseOpenIdStrategy(req);
passport.authenticate(strategy, {
failureRedirect: `${domains.client}/oauth/error`,
failureMessage: true,
session: false,
})(req, res, next);
} catch (err) {
next(err);
}
},
oauthHandler,
);

View file

@ -15,7 +15,6 @@ const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config');
/**
*
* @param {Express.Application} app
*/
const configureSocialLogins = (app) => {
@ -35,10 +34,7 @@ const configureSocialLogins = (app) => {
passport.use(appleLogin());
}
if (
process.env.OPENID_CLIENT_ID &&
process.env.OPENID_CLIENT_SECRET &&
process.env.OPENID_ISSUER &&
process.env.OPENID_SCOPE &&
process.env.OPENID_ENABLED &&
process.env.OPENID_SESSION_SECRET
) {
const sessionOptions = {

View file

@ -0,0 +1,52 @@
const { logger } = require('~/config');
const { getCustomConfig } = require('~/server/services/Config');
/**
* Loads the tenant configurations from the custom configuration.
* @returns {Promise<Array>} Array of tenant configurations.
*/
async function getOpenIdTenants() {
try {
const customConfig = await getCustomConfig();
if (customConfig?.openid?.tenants) {
return customConfig.openid.tenants;
}
} catch (err) {
logger.error('Failed to load custom configuration for OpenID tenants:', err);
}
return [];
}
/**
* Chooses the OpenID strategy name based on the email domain.
* It consults the global tenant mapping (built in setupOpenId).
* @param {import('express').Request} req - The Express request object.
* @returns {Promise<string>} - The chosen strategy name.
*/
async function chooseOpenIdStrategy(req) {
if (req.query.email) {
const email = req.query.email;
const domain = email.split('@')[1].toLowerCase();
const tenants = await getOpenIdTenants();
// Iterate over the tenants and return the strategy name of the first matching tenant
for (const tenant of tenants) {
if (tenant.domains) {
const tenantDomains = tenant.domains.split(',').map(s => s.trim().toLowerCase());
if (tenantDomains.includes(domain)) {
// Look up the registered strategy via the global mapping.
if (tenant.name && tenant.name.trim() && global.__openidTenantMapping) {
const mapped = global.__openidTenantMapping.get(tenant.name.trim().toLowerCase());
if (mapped) {
return mapped;
}
}
return 'openid'; // Fallback if no mapping exists.
}
}
}
}
return 'openid';
}
module.exports = { getOpenIdTenants, chooseOpenIdStrategy };