LibreChat/api/strategies/localStrategy.js
Danny Avila 85fa881e3c
🔐 fix: Avoid Logging Password On Login Validation Error (#12926)
The Passport local strategy validation error logged the entire request
body (including the password) into error logs. Replace it with the
email only, matching the metadata shape used by sibling log calls in
the same function.
2026-05-03 21:27:41 -04:00

90 lines
3.3 KiB
JavaScript

const bcrypt = require('bcryptjs');
const { logger } = require('@librechat/data-schemas');
const { errorsToString } = require('librechat-data-provider');
const { Strategy: PassportLocalStrategy } = require('passport-local');
const { isEnabled, checkEmailConfig, comparePassword } = require('@librechat/api');
const { findUser, updateUser } = require('~/models');
const { loginSchema } = require('./validators');
// Unix timestamp for 2024-06-07 15:20:18 Eastern Time
const verificationEnabledTimestamp = 1717788018;
async function validateLoginRequest(req) {
const { error } = loginSchema.safeParse(req.body);
return error ? errorsToString(error.errors) : null;
}
async function passportLogin(req, email, password, done) {
try {
const validationError = await validateLoginRequest(req);
if (validationError) {
logError('Passport Local Strategy - Validation Error', { email: req.body?.email });
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
return done(null, false, { message: validationError });
}
const user = await findUser({ email: email.trim() }, '+password');
if (!user) {
logError('Passport Local Strategy - User Not Found', { email });
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
return done(null, false, { message: 'Email does not exist.' });
}
if (!user.password) {
logError('Passport Local Strategy - User has no password', { email });
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
return done(null, false, { message: 'Email does not exist.' });
}
const isMatch = await comparePassword(user, password, { compare: bcrypt.compare });
if (!isMatch) {
logError('Passport Local Strategy - Password does not match', { isMatch });
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
return done(null, false, { message: 'Incorrect password.' });
}
const emailEnabled = checkEmailConfig();
const userCreatedAtTimestamp = Math.floor(new Date(user.createdAt).getTime() / 1000);
if (
!emailEnabled &&
!user.emailVerified &&
userCreatedAtTimestamp < verificationEnabledTimestamp
) {
await updateUser(user._id, { emailVerified: true });
user.emailVerified = true;
}
const unverifiedAllowed = isEnabled(process.env.ALLOW_UNVERIFIED_EMAIL_LOGIN);
if (user.expiresAt && unverifiedAllowed) {
await updateUser(user._id, {});
}
if (!user.emailVerified && !unverifiedAllowed) {
logError('Passport Local Strategy - Email not verified', { email });
logger.error(`[Login] [Login failed] [Username: ${email}] [Request-IP: ${req.ip}]`);
return done(null, user, { message: 'Email not verified.' });
}
logger.info(`[Login] [Login successful] [Username: ${email}] [Request-IP: ${req.ip}]`);
return done(null, user);
} catch (err) {
return done(err);
}
}
function logError(title, parameters) {
const entries = Object.entries(parameters).map(([name, value]) => ({ name, value }));
logger.error(title, { parameters: entries });
}
module.exports = () =>
new PassportLocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
session: false,
passReqToCallback: true,
},
passportLogin,
);