mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2025-11-07 04:42:54 -05:00
Fix OIDC login: ensure Passport callback is defined (#7312)
MeshCentral OIDC strategy was throwing `TypeError: done is not a function` because the callback was not properly passed when using openid-client. This patch wraps the OIDC callback to detect missing callback parameters, extracts user info from the id_token if needed, and ensures `done()` is called in all code paths, including async group fetching. This restores functional OIDC logins for Azure AD/Keycloak. Tested on Azure B2C OIDC Co-authored-by: Szymon Sypula <szymon.sypula@dieboldnixdorf.com>
This commit is contained in:
68
webserver.js
68
webserver.js
@@ -8078,23 +8078,71 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
}
|
||||
|
||||
// Callback function must be able to grab info from API's using the access token, would prefer to use the token here.
|
||||
function oidcCallback(tokenset, profile, verified) {
|
||||
function oidcCallback(tokenset, profile, done) {
|
||||
// Handle case where done might not be the third parameter
|
||||
if (typeof done !== 'function') {
|
||||
// OpenID Connect strategy calls with (tokenset, done) instead of (tokenset, profile, done)
|
||||
if (typeof profile === 'function') {
|
||||
done = profile;
|
||||
profile = null;
|
||||
} else {
|
||||
parent.debug('error', 'OIDC: Unable to find callback function in parameters');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If profile is null/undefined, extract user info from the tokenset
|
||||
if (!profile && tokenset && tokenset.id_token) {
|
||||
try {
|
||||
// Simple JWT decoder to extract user claims from id_token
|
||||
const parts = tokenset.id_token.split('.');
|
||||
if (parts.length === 3) {
|
||||
const payload = parts[1];
|
||||
const paddedPayload = payload + '='.repeat((4 - payload.length % 4) % 4);
|
||||
const decoded = JSON.parse(Buffer.from(paddedPayload, 'base64').toString());
|
||||
if (decoded) {
|
||||
profile = decoded;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
parent.debug('error', `OIDC: Failed to decode id_token: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize user object
|
||||
let user = { 'strategy': 'oidc' }
|
||||
let claims = obj.common.validateObject(strategy.custom.claims) ? strategy.custom.claims : null;
|
||||
user.sid = obj.common.validateString(profile.sub) ? '~oidc:' + profile.sub : null;
|
||||
user.name = obj.common.validateString(profile.name) ? profile.name : null;
|
||||
user.email = obj.common.validateString(profile.email) ? profile.email : null;
|
||||
|
||||
user.sid = null;
|
||||
if (profile && obj.common.validateString(profile.sub)) {
|
||||
user.sid = '~oidc:' + profile.sub;
|
||||
} else if (profile && obj.common.validateString(profile.oid)) {
|
||||
user.sid = '~oidc:' + profile.oid;
|
||||
} else if (profile && obj.common.validateString(profile.email)) {
|
||||
user.sid = '~oidc:' + profile.email;
|
||||
} else if (profile && obj.common.validateString(profile.upn)) {
|
||||
user.sid = '~oidc:' + profile.upn;
|
||||
}
|
||||
|
||||
user.name = profile && obj.common.validateString(profile.name) ? profile.name : null;
|
||||
user.email = profile && obj.common.validateString(profile.email) ? profile.email : null;
|
||||
if (claims != null) {
|
||||
user.sid = obj.common.validateString(profile[claims.uuid]) ? '~oidc:' + profile[claims.uuid] : user.sid;
|
||||
user.name = obj.common.validateString(profile[claims.name]) ? profile[claims.name] : user.name;
|
||||
user.email = obj.common.validateString(profile[claims.email]) ? profile[claims.email] : user.email;
|
||||
}
|
||||
user.emailVerified = profile.email_verified ? profile.email_verified : obj.common.validateEmail(user.email);
|
||||
user.groups = obj.common.validateStrArray(profile.groups, 1) ? profile.groups : null;
|
||||
|
||||
// Ensure we have a valid sid before proceeding
|
||||
if (!user.sid) {
|
||||
parent.debug('error', `OIDC: No valid user identifier found in profile`);
|
||||
return done(new Error('OIDC: No valid user identifier found in profile'));
|
||||
}
|
||||
|
||||
user.emailVerified = profile && profile.email_verified ? profile.email_verified : obj.common.validateEmail(user.email);
|
||||
user.groups = profile && obj.common.validateStrArray(profile.groups, 1) ? profile.groups : null;
|
||||
user.preset = obj.common.validateString(strategy.custom.preset) ? strategy.custom.preset : null;
|
||||
if (strategy.groups && obj.common.validateString(strategy.groups.claim)) {
|
||||
user.groups = obj.common.validateStrArray(profile[strategy.groups.claim], 1) ? profile[strategy.groups.claim] : null
|
||||
user.groups = profile && obj.common.validateStrArray(profile[strategy.groups.claim], 1) ? profile[strategy.groups.claim] : null
|
||||
}
|
||||
|
||||
// Setup end session enpoint if not already configured this requires an auth token
|
||||
@@ -8114,16 +8162,16 @@ module.exports.CreateWebServer = function (parent, db, args, certificates, doneF
|
||||
if (strategy.groups && typeof user.preset == 'string') {
|
||||
getGroups(user.preset, tokenset).then((groups) => {
|
||||
user = Object.assign(user, { 'groups': groups });
|
||||
return verified(null, user);
|
||||
done(null, user);
|
||||
}).catch((err) => {
|
||||
let error = new Error('OIDC: GROUPS: No groups found due to error:', { cause: err });
|
||||
parent.debug('error', `${JSON.stringify(error)}`);
|
||||
parent.authLog('oidcCallback', error.message);
|
||||
user.groups = [];
|
||||
return verified(null, user);
|
||||
done(null, user);
|
||||
});
|
||||
} else {
|
||||
return verified(null, user);
|
||||
done(null, user);
|
||||
}
|
||||
|
||||
async function getGroups(preset, tokenset) {
|
||||
|
||||
Reference in New Issue
Block a user