mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-10 08:13:46 +00:00
* 🔐 fix: Reject OpenID email fallback when stored openidId mismatches token sub When `findOpenIDUser` falls back to email lookup after the primary `openidId`/`idOnTheSource` query fails, it now rejects any user whose stored `openidId` differs from the incoming JWT subject claim. This closes an account-takeover vector where a valid IdP JWT containing a victim's email but a different `sub` could authenticate as the victim when OPENID_REUSE_TOKENS is enabled. The migration path (user has no `openidId` yet) is unaffected. * test: Validate openidId mismatch guard in email fallback path Update `findOpenIDUser` unit tests to assert that email-based lookups returning a user with a different `openidId` are rejected with AUTH_FAILED. Add matching integration test in `openIdJwtStrategy.spec` exercising the full verify callback with the real `findOpenIDUser`. * 🔐 fix: Remove redundant `openidId` truthiness check from mismatch guard The `&& openidId` middle term in the guard condition caused it to be bypassed when the incoming token `sub` was empty or undefined. Since the JS callers can pass `payload?.sub` (which may be undefined), this created a path where the guard never fired and the email fallback returned the victim's account. Removing the term ensures the guard rejects whenever the stored openidId differs from the incoming value, regardless of whether the incoming value is falsy. * test: Cover falsy openidId bypass and openidStrategy mismatch rejection Add regression test for the guard bypass when `openidId` is an empty string and the email lookup finds a user with a stored openidId. Add integration test in openidStrategy.spec.js exercising the mismatch rejection through the full processOpenIDAuth callback, ensuring both OIDC paths (JWT reuse and standard callback) are covered. Restore intent-documenting comment on the no-provider fixture.
68 lines
2.2 KiB
TypeScript
68 lines
2.2 KiB
TypeScript
import { logger } from '@librechat/data-schemas';
|
|
import { ErrorTypes } from 'librechat-data-provider';
|
|
import type { IUser, UserMethods } from '@librechat/data-schemas';
|
|
|
|
/**
|
|
* Finds or migrates a user for OpenID authentication
|
|
* @returns user object (with migration fields if needed), error message, and whether migration is needed
|
|
*/
|
|
export async function findOpenIDUser({
|
|
openidId,
|
|
findUser,
|
|
email,
|
|
idOnTheSource,
|
|
strategyName = 'openid',
|
|
}: {
|
|
openidId: string;
|
|
findUser: UserMethods['findUser'];
|
|
email?: string;
|
|
idOnTheSource?: string;
|
|
strategyName?: string;
|
|
}): Promise<{ user: IUser | null; error: string | null; migration: boolean }> {
|
|
const primaryConditions = [];
|
|
|
|
if (openidId && typeof openidId === 'string') {
|
|
primaryConditions.push({ openidId });
|
|
}
|
|
|
|
if (idOnTheSource && typeof idOnTheSource === 'string') {
|
|
primaryConditions.push({ idOnTheSource });
|
|
}
|
|
|
|
let user = null;
|
|
if (primaryConditions.length > 0) {
|
|
user = await findUser({ $or: primaryConditions });
|
|
}
|
|
if (!user && email) {
|
|
user = await findUser({ email });
|
|
logger.warn(
|
|
`[${strategyName}] user ${user ? 'found' : 'not found'} with email: ${email} for openidId: ${openidId}`,
|
|
);
|
|
|
|
// If user found by email, check if they're allowed to use OpenID provider
|
|
if (user && user.provider && user.provider !== 'openid') {
|
|
logger.warn(
|
|
`[${strategyName}] Attempted OpenID login by user ${user.email}, was registered with "${user.provider}" provider`,
|
|
);
|
|
return { user: null, error: ErrorTypes.AUTH_FAILED, migration: false };
|
|
}
|
|
|
|
if (user?.openidId && user.openidId !== openidId) {
|
|
logger.warn(
|
|
`[${strategyName}] Rejected email fallback for ${user.email}: stored openidId does not match token sub`,
|
|
);
|
|
return { user: null, error: ErrorTypes.AUTH_FAILED, migration: false };
|
|
}
|
|
|
|
if (user && !user.openidId) {
|
|
logger.info(
|
|
`[${strategyName}] Preparing user ${user.email} for migration to OpenID with sub: ${openidId}`,
|
|
);
|
|
user.provider = 'openid';
|
|
user.openidId = openidId;
|
|
return { user, error: null, migration: true };
|
|
}
|
|
}
|
|
|
|
return { user, error: null, migration: false };
|
|
}
|