Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions BackendAcademy/src/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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);
}
});
});
});
});
7 changes: 7 additions & 0 deletions BackendAcademy/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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 {}
12 changes: 12 additions & 0 deletions BackendAcademy/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,28 @@ 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.
async function bootstrap() {
const logger = new Logger('Bootstrap');
const app = await NestFactory.create<NestExpressApplication>(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());
Expand Down