🔄 fix: Resolve Infinite Refresh Loop on OpenID Provider/Database Switch (#11002)

The refreshController was not updating user openidId when found by email
with a different openidId already set. This caused an infinite loop where:
1. JWT auth failed (openidId mismatch)
2. Refresh found user by email but didn't update openidId
3. Next JWT auth failed again → loop

Now updates openidId when either migration is needed OR when the stored
openidId doesn't match the token's sub claim. Added debug logging for
visibility into auth state during refresh.
This commit is contained in:
Danny Avila 2025-12-16 18:42:00 -05:00 committed by GitHub
parent b4459ab564
commit d505e3124f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -10,7 +10,13 @@ const {
setAuthTokens,
registerUser,
} = require('~/server/services/AuthService');
const { findUser, getUserById, deleteAllUserSessions, findSession } = require('~/models');
const {
deleteAllUserSessions,
getUserById,
findSession,
updateUser,
findUser,
} = require('~/models');
const { getGraphApiToken } = require('~/server/services/GraphTokenService');
const { getOAuthReconnectionManager } = require('~/config');
const { getOpenIdConfig } = require('~/strategies');
@ -72,16 +78,38 @@ const refreshController = async (req, res) => {
const openIdConfig = getOpenIdConfig();
const tokenset = await openIdClient.refreshTokenGrant(openIdConfig, refreshToken);
const claims = tokenset.claims();
const { user, error } = await findOpenIDUser({
const { user, error, migration } = await findOpenIDUser({
findUser,
email: claims.email,
openidId: claims.sub,
idOnTheSource: claims.oid,
strategyName: 'refreshController',
});
logger.debug(
`[refreshController] findOpenIDUser result: user=${user?.email ?? 'null'}, error=${error ?? 'null'}, migration=${migration}, userOpenidId=${user?.openidId ?? 'null'}, claimsSub=${claims.sub}`,
);
if (error || !user) {
logger.warn(
`[refreshController] Redirecting to /login: error=${error ?? 'null'}, user=${user ? 'exists' : 'null'}`,
);
return res.status(401).redirect('/login');
}
// Handle migration: update user with openidId if found by email without openidId
// Also handle case where user has mismatched openidId (e.g., after database switch)
if (migration || user.openidId !== claims.sub) {
const reason = migration ? 'migration' : 'openidId mismatch';
await updateUser(user._id.toString(), {
provider: 'openid',
openidId: claims.sub,
});
logger.info(
`[refreshController] Updated user ${user.email} openidId (${reason}): ${user.openidId ?? 'null'} -> ${claims.sub}`,
);
}
const token = setOpenIDAuthTokens(tokenset, res, user._id.toString(), refreshToken);
user.federatedTokens = {