diff --git a/api/strategies/samlStrategy.js b/api/strategies/samlStrategy.js index cab43044b9..ced6c26148 100644 --- a/api/strategies/samlStrategy.js +++ b/api/strategies/samlStrategy.js @@ -216,6 +216,15 @@ function createSamlCallback(existingUsersOnly = false) { }); } + if (user?.samlId && user.samlId !== profile.nameID) { + logger.warn( + `[samlStrategy] Refused SAML login with a different NameID for user: ${user.email}`, + ); + return done(null, false, { + message: ErrorTypes.AUTH_FAILED, + }); + } + const appConfig = user?.tenantId ? await resolveAppConfigForUser(getAppConfig, user) : baseConfig; diff --git a/api/strategies/samlStrategy.spec.js b/api/strategies/samlStrategy.spec.js index 301fee3a87..353ab1df7a 100644 --- a/api/strategies/samlStrategy.spec.js +++ b/api/strategies/samlStrategy.spec.js @@ -7,6 +7,7 @@ jest.mock('@librechat/data-schemas', () => ({ logger: { info: jest.fn(), debug: jest.fn(), + warn: jest.fn(), error: jest.fn(), }, hashToken: jest.fn().mockResolvedValue('hashed-token'), @@ -426,6 +427,23 @@ u7wlOSk+oFzDIO/UILIA expect(user.email).toBe(baseProfile.email); }); + it('should reject an email match bound to a different NameID', async () => { + const { findUser, updateUser } = require('~/models'); + const existingUser = { + _id: 'existing-user-id', + provider: 'saml', + email: baseProfile.email, + samlId: 'original-name-id', + }; + findUser.mockResolvedValueOnce(null).mockResolvedValueOnce(existingUser); + + const result = await validate(baseProfile); + + expect(result.user).toBe(false); + expect(result.details.message).toBe(require('librechat-data-provider').ErrorTypes.AUTH_FAILED); + expect(updateUser).not.toHaveBeenCalled(); + }); + it('should block login when email exists with different provider', async () => { // Set up findUser to return a user with different provider const { findUser } = require('~/models');