feat(security): share rate limits across instances, drain syncs on shutdown
- Rate limiting counters live in PostgreSQL (throttler_records), shared by every instance, one atomic upsert per request; fails open. - On shutdown, new syncs are refused (503) and the running one is waited for up to SYNC_SHUTDOWN_TIMEOUT; Prisma disconnects last. - Hiboutik calls never follow redirects and are capped at 50 MB. - Prisma errors no longer print query arguments (errorFormat minimal). - In production, DATABASE_URL must require TLS. - The sync lock connection times out after 10 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,14 +10,18 @@ import { PrismaService } from './../prisma/prisma.service';
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication<App>;
|
||||
const hiboutikService = { startSync: jest.fn() };
|
||||
// Rate limiting counters (ThrottlerDatabaseStorage): the database answers "not blocked" unless a test says otherwise
|
||||
const notBlocked = { totalHits: 1, timeToExpire: 60, isBlocked: false, timeToBlockExpire: 0 };
|
||||
const prisma = { $queryRaw: jest.fn(), throttlerRecord: { deleteMany: jest.fn().mockResolvedValue({ count: 0 }) } };
|
||||
|
||||
beforeEach(async () => {
|
||||
hiboutikService.startSync.mockReset();
|
||||
prisma.$queryRaw.mockReset().mockResolvedValue([notBlocked]);
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
})
|
||||
.overrideProvider(PrismaService)
|
||||
.useValue({})
|
||||
.useValue(prisma)
|
||||
.overrideProvider(HiboutikService)
|
||||
.useValue(hiboutikService)
|
||||
.compile();
|
||||
@@ -36,6 +40,13 @@ describe('AppController (e2e)', () => {
|
||||
return request(app.getHttpServer()).post('/sync').expect(409);
|
||||
});
|
||||
|
||||
it('POST /sync answers 429 when the shared rate limiting counter is blocked', async () => {
|
||||
prisma.$queryRaw.mockResolvedValue([{ totalHits: 6, timeToExpire: 30, isBlocked: true, timeToBlockExpire: 30 }]);
|
||||
|
||||
await request(app.getHttpServer()).post('/sync').expect(429).expect('Retry-After', '30');
|
||||
expect(hiboutikService.startSync).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user