LibreChat/packages/api/src/middleware/error.ts
Danny Avila 1ccac58403
🔒 fix: Provider Validation for Social, OpenID, SAML, and LDAP Logins (#8999)
* fix: social login provider crossover

* feat: Enhance OpenID login handling and add tests for provider validation

* refactor: authentication error handling to use ErrorTypes.AUTH_FAILED enum

* refactor: update authentication error handling in LDAP and SAML strategies to use ErrorTypes.AUTH_FAILED enum

* ci: Add validation for login with existing email and different provider in SAML strategy

chore: Add logging for existing users with different providers in LDAP, SAML, and Social Login strategies
2025-08-11 18:51:46 -04:00

83 lines
2.9 KiB
TypeScript

import { logger } from '@librechat/data-schemas';
import { ErrorTypes } from 'librechat-data-provider';
import type { NextFunction, Request, Response } from 'express';
import type { MongoServerError, ValidationError, CustomError } from '~/types';
const handleDuplicateKeyError = (err: MongoServerError, res: Response) => {
logger.warn('Duplicate key error: ' + (err.errmsg || err.message));
const field = err.keyValue ? `${JSON.stringify(Object.keys(err.keyValue))}` : 'unknown';
const code = 409;
res
.status(code)
.send({ messages: `An document with that ${field} already exists.`, fields: field });
};
const handleValidationError = (err: ValidationError, res: Response) => {
logger.error('Validation error:', err.errors);
const errorMessages = Object.values(err.errors).map((el) => el.message);
const fields = `${JSON.stringify(Object.values(err.errors).map((el) => el.path))}`;
const code = 400;
const messages =
errorMessages.length > 1
? `${JSON.stringify(errorMessages.join(' '))}`
: `${JSON.stringify(errorMessages)}`;
res.status(code).send({ messages, fields });
};
/** Type guard for ValidationError */
function isValidationError(err: unknown): err is ValidationError {
return err !== null && typeof err === 'object' && 'name' in err && err.name === 'ValidationError';
}
/** Type guard for MongoServerError (duplicate key) */
function isMongoServerError(err: unknown): err is MongoServerError {
return err !== null && typeof err === 'object' && 'code' in err && err.code === 11000;
}
/** Type guard for CustomError with statusCode and body */
function isCustomError(err: unknown): err is CustomError {
return err !== null && typeof err === 'object' && 'statusCode' in err && 'body' in err;
}
export const ErrorController = (
err: Error | CustomError,
req: Request,
res: Response,
next: NextFunction,
): Response | void => {
try {
if (!err) {
return next();
}
const error = err as CustomError;
if (
(error.message === ErrorTypes.AUTH_FAILED || error.code === ErrorTypes.AUTH_FAILED) &&
req.originalUrl &&
req.originalUrl.includes('/oauth/') &&
req.originalUrl.includes('/callback')
) {
const domain = process.env.DOMAIN_CLIENT || 'http://localhost:3080';
return res.redirect(`${domain}/login?redirect=false&error=${ErrorTypes.AUTH_FAILED}`);
}
if (isValidationError(error)) {
return handleValidationError(error, res);
}
if (isMongoServerError(error)) {
return handleDuplicateKeyError(error, res);
}
if (isCustomError(error) && error.statusCode && error.body) {
return res.status(error.statusCode).send(error.body);
}
logger.error('ErrorController => error', err);
return res.status(500).send('An unknown error occurred.');
} catch (processingError) {
logger.error('ErrorController => processing error', processingError);
return res.status(500).send('Processing error in ErrorController.');
}
};