From 3425e6cdd106e444c88c1ee680c812861b52889a Mon Sep 17 00:00:00 2001 From: Ranjeet Prabhat Date: Wed, 14 Oct 2026 07:57:11 +0545 Subject: [PATCH] feat(api): BA-117 add bounded request body size and reject oversized JSON payloads --- BackendAcademy/src/app.controller.spec.ts | 47 +++++++++++++++++++++++ BackendAcademy/src/app.module.ts | 7 ++++ BackendAcademy/src/main.ts | 12 ++++++ 3 files changed, 66 insertions(+) diff --git a/BackendAcademy/src/app.controller.spec.ts b/BackendAcademy/src/app.controller.spec.ts index 4f5dfe55e5..87535fa8d1 100644 --- a/BackendAcademy/src/app.controller.spec.ts +++ b/BackendAcademy/src/app.controller.spec.ts @@ -1,4 +1,6 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { json } from 'express'; +import { Readable } from 'stream'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @@ -19,4 +21,49 @@ describe('AppController', () => { expect(appController.getHello()).toBe('RustAcademy BackendAcademy API'); }); }); + + describe('Bounded Request Body Size (BA-117)', () => { + it('should reject oversized JSON bodies with a documented 413 client error', (done) => { + const limitMiddleware = json({ limit: '64b' }); + const rawPayload = JSON.stringify({ message: 'A'.repeat(128) }); + const req: any = Readable.from([rawPayload]); + req.headers = { + 'content-type': 'application/json', + 'content-length': String(Buffer.byteLength(rawPayload)), + }; + const res: any = {}; + + limitMiddleware(req, res, (err?: any) => { + try { + expect(err).toBeDefined(); + expect(err.status || err.statusCode).toBe(413); + expect(err.type).toBe('entity.too.large'); + done(); + } catch (e) { + done(e); + } + }); + }); + + it('should allow valid JSON bodies within the size limit', (done) => { + const limitMiddleware = json({ limit: '100kb' }); + const validPayload = JSON.stringify({ title: 'Rust course' }); + const req: any = Readable.from([validPayload]); + req.headers = { + 'content-type': 'application/json', + 'content-length': String(Buffer.byteLength(validPayload)), + }; + const res: any = {}; + + limitMiddleware(req, res, (err?: any) => { + try { + expect(err).toBeUndefined(); + expect(req.body).toEqual({ title: 'Rust course' }); + done(); + } catch (e) { + done(e); + } + }); + }); + }); }); diff --git a/BackendAcademy/src/app.module.ts b/BackendAcademy/src/app.module.ts index 498dc78e10..ffc2fc2318 100644 --- a/BackendAcademy/src/app.module.ts +++ b/BackendAcademy/src/app.module.ts @@ -15,6 +15,8 @@ import { ProgressModule } from './progress/progress.module'; import { CoursesModule } from './courses/courses.module'; import { CertificatesModule } from './certificates/certificates.module'; +export const REQUEST_BODY_LIMIT = 'REQUEST_BODY_LIMIT'; + // Root application module: wires together global configuration/guards, // every feature module, and the top-level controllers/providers. @Module({ @@ -57,6 +59,11 @@ import { CertificatesModule } from './certificates/certificates.module'; // token), so the rate limiting configured above is actually // enforced on every request rather than just being available. { provide: APP_GUARD, useClass: ThrottlerGuard }, + // Provides configured request body limit across the app + { + provide: REQUEST_BODY_LIMIT, + useValue: process.env.MAX_BODY_SIZE ?? '1mb', + }, ], }) export class AppModule {} \ No newline at end of file diff --git a/BackendAcademy/src/main.ts b/BackendAcademy/src/main.ts index 71c0100751..d8b649c81c 100644 --- a/BackendAcademy/src/main.ts +++ b/BackendAcademy/src/main.ts @@ -3,9 +3,16 @@ import { ValidationPipe, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NestExpressApplication } from '@nestjs/platform-express'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { json, urlencoded } from 'express'; import helmet from 'helmet'; import { AppModule } from './app.module'; +export const DEFAULT_MAX_BODY_SIZE = '1mb'; + +export function getRequestBodyLimit(): string { + return process.env.MAX_BODY_SIZE ?? DEFAULT_MAX_BODY_SIZE; +} + // Application entry point: creates the Nest app, applies global // middleware/pipes, optionally sets up Swagger docs, and starts // listening on the configured port. @@ -13,6 +20,11 @@ async function bootstrap() { const logger = new Logger('Bootstrap'); const app = await NestFactory.create(AppModule); + // Bounded request body size: rejects oversized JSON/urlencoded bodies with 413 error + const bodyLimit = getRequestBodyLimit(); + app.use(json({ limit: bodyLimit })); + app.use(urlencoded({ extended: true, limit: bodyLimit })); + // Sets a range of security-related HTTP headers (CSP, X-Frame-Options, // etc.) on every response. app.use(helmet());