mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-07-01 20:01:35 +00:00
* harden token and remote file handling * sort s3 storage imports * split token submission rate limits
121 lines
4.6 KiB
JavaScript
121 lines
4.6 KiB
JavaScript
const express = require('express');
|
|
const request = require('supertest');
|
|
|
|
const mockCheckBan = jest.fn((req, res, next) => next());
|
|
const mockResetPasswordSubmissionLimiter = jest.fn((req, res, next) => next());
|
|
const mockValidatePasswordReset = jest.fn((req, res, next) => next());
|
|
const mockResetPasswordController = jest.fn((req, res) => res.status(204).end());
|
|
|
|
jest.mock('@librechat/api', () => ({
|
|
createSetBalanceConfig: jest.fn(() => (req, res, next) => next()),
|
|
forceRefreshCloudFrontAuthCookies: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('~/server/controllers/AuthController', () => ({
|
|
refreshController: jest.fn((req, res) => res.status(204).end()),
|
|
registrationController: jest.fn((req, res) => res.status(204).end()),
|
|
resetPasswordController: (...args) => mockResetPasswordController(...args),
|
|
resetPasswordRequestController: jest.fn((req, res) => res.status(204).end()),
|
|
graphTokenController: jest.fn((req, res) => res.status(204).end()),
|
|
}));
|
|
|
|
jest.mock('~/server/controllers/TwoFactorController', () => ({
|
|
enable2FA: jest.fn((req, res) => res.status(204).end()),
|
|
verify2FA: jest.fn((req, res) => res.status(204).end()),
|
|
confirm2FA: jest.fn((req, res) => res.status(204).end()),
|
|
disable2FA: jest.fn((req, res) => res.status(204).end()),
|
|
regenerateBackupCodes: jest.fn((req, res) => res.status(204).end()),
|
|
}));
|
|
|
|
jest.mock('~/server/controllers/auth/TwoFactorAuthController', () => ({
|
|
verify2FAWithTempToken: jest.fn((req, res) => res.status(204).end()),
|
|
}));
|
|
|
|
jest.mock('~/server/controllers/auth/LogoutController', () => ({
|
|
logoutController: jest.fn((req, res) => res.status(204).end()),
|
|
}));
|
|
|
|
jest.mock('~/server/controllers/auth/LoginController', () => ({
|
|
loginController: jest.fn((req, res) => res.status(204).end()),
|
|
}));
|
|
|
|
jest.mock('~/models', () => ({
|
|
findBalanceByUser: jest.fn(),
|
|
upsertBalanceFields: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('~/server/services/Config', () => ({
|
|
getAppConfig: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('~/server/middleware', () => {
|
|
const pass = (req, res, next) => next();
|
|
return {
|
|
logHeaders: pass,
|
|
loginLimiter: pass,
|
|
setTwoFactorTempUser: pass,
|
|
twoFactorTempLimiter: pass,
|
|
checkBan: (...args) => mockCheckBan(...args),
|
|
requireLocalAuth: pass,
|
|
requireLdapAuth: pass,
|
|
registerLimiter: pass,
|
|
checkInviteUser: pass,
|
|
validateRegistration: pass,
|
|
resetPasswordLimiter: pass,
|
|
resetPasswordSubmissionLimiter: (...args) => mockResetPasswordSubmissionLimiter(...args),
|
|
validatePasswordReset: (...args) => mockValidatePasswordReset(...args),
|
|
requireJwtAuth: pass,
|
|
};
|
|
});
|
|
|
|
const authRouter = require('./auth');
|
|
|
|
describe('POST /api/auth/resetPassword rate limiting', () => {
|
|
let app;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockCheckBan.mockImplementation((req, res, next) => next());
|
|
mockResetPasswordSubmissionLimiter.mockImplementation((req, res, next) => next());
|
|
mockValidatePasswordReset.mockImplementation((req, res, next) => next());
|
|
mockResetPasswordController.mockImplementation((req, res) => res.status(204).end());
|
|
|
|
app = express();
|
|
app.use(express.json());
|
|
app.use('/api/auth', authRouter);
|
|
});
|
|
|
|
it('limits before ban checks, validation, and password reset work', async () => {
|
|
await request(app).post('/api/auth/resetPassword').send({ token: 'token' }).expect(204);
|
|
|
|
expect(mockResetPasswordSubmissionLimiter).toHaveBeenCalledTimes(1);
|
|
expect(mockCheckBan).toHaveBeenCalledTimes(1);
|
|
expect(mockValidatePasswordReset).toHaveBeenCalledTimes(1);
|
|
expect(mockResetPasswordController).toHaveBeenCalledTimes(1);
|
|
expect(mockResetPasswordSubmissionLimiter.mock.invocationCallOrder[0]).toBeLessThan(
|
|
mockCheckBan.mock.invocationCallOrder[0],
|
|
);
|
|
expect(mockCheckBan.mock.invocationCallOrder[0]).toBeLessThan(
|
|
mockValidatePasswordReset.mock.invocationCallOrder[0],
|
|
);
|
|
expect(mockValidatePasswordReset.mock.invocationCallOrder[0]).toBeLessThan(
|
|
mockResetPasswordController.mock.invocationCallOrder[0],
|
|
);
|
|
});
|
|
|
|
it('does not validate or reset passwords after the limiter rejects the request', async () => {
|
|
mockResetPasswordSubmissionLimiter.mockImplementation((req, res) =>
|
|
res.status(429).json({ message: 'Too many password reset attempts' }),
|
|
);
|
|
|
|
const response = await request(app)
|
|
.post('/api/auth/resetPassword')
|
|
.send({ token: 'token' })
|
|
.expect(429);
|
|
|
|
expect(response.body).toEqual({ message: 'Too many password reset attempts' });
|
|
expect(mockCheckBan).not.toHaveBeenCalled();
|
|
expect(mockValidatePasswordReset).not.toHaveBeenCalled();
|
|
expect(mockResetPasswordController).not.toHaveBeenCalled();
|
|
});
|
|
});
|