diff --git a/package.json b/package.json index cde392aaa..b855887a1 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "dependencies": { "@babel/plugin-proposal-class-properties": "^7.18.6", "@babel/plugin-proposal-decorators": "^7.17.8", + "@nestjs/throttler": "^5.0.1", "cross-env": "^7.0.3", "nodemailer": "^6.9.1" }, diff --git a/packages/api-v2/src/app.module.ts b/packages/api-v2/src/app.module.ts index 6b8c20b91..69b3118d4 100644 --- a/packages/api-v2/src/app.module.ts +++ b/packages/api-v2/src/app.module.ts @@ -5,10 +5,11 @@ import ormconfig from "../ormconfig"; import { StudentModule } from "./student/student.module"; import { AuthModule } from "./auth/auth.module"; import { PlanModule } from "./plan/plan.module"; -import { APP_INTERCEPTOR } from "@nestjs/core"; +import { APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core"; import { LoggingInterceptor } from "./interceptors/logging.interceptor"; import { MajorModule } from "./major/major.module"; import { EmailModule } from "./email/email.module"; +import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler"; @Module({ imports: [ @@ -17,6 +18,12 @@ import { EmailModule } from "./email/email.module"; envFilePath: [`.env.${process.env.NODE_ENV}.local`], isGlobal: true, }), + ThrottlerModule.forRoot([ + { + ttl: 60000, // no more than 100 requests in a minute + limit: 100, + }, + ]), StudentModule, AuthModule, PlanModule, @@ -28,6 +35,10 @@ import { EmailModule } from "./email/email.module"; provide: APP_INTERCEPTOR, useClass: LoggingInterceptor, }, + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, ], }) export class AppModule {} diff --git a/packages/api-v2/src/auth/auth.controller.ts b/packages/api-v2/src/auth/auth.controller.ts index fbc8f6825..4bdcdb161 100644 --- a/packages/api-v2/src/auth/auth.controller.ts +++ b/packages/api-v2/src/auth/auth.controller.ts @@ -30,6 +30,7 @@ import { WeakPassword, } from "src/student/student.errors"; import { BadToken, InvalidPayload, TokenExpiredError } from "./auth.errors"; +import { Throttle } from "@nestjs/throttler"; @Controller("auth") export class AuthController { @@ -74,6 +75,7 @@ export class AuthController { return student; } + @Throttle({ default: { limit: 20, ttl: 60000 } }) // restrict to no more than 20 requests per minute @Post("login") public async login( @Res({ passthrough: true }) response: Response, diff --git a/yarn.lock b/yarn.lock index 60d975fc0..827960548 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3782,6 +3782,19 @@ __metadata: languageName: node linkType: hard +"@nestjs/throttler@npm:^5.0.1": + version: 5.0.1 + resolution: "@nestjs/throttler@npm:5.0.1" + dependencies: + md5: ^2.2.1 + peerDependencies: + "@nestjs/common": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + "@nestjs/core": ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 + checksum: 9649fb7ea54ed97d552745b477e06bcbcc4f2ee1464af2e54c149ec8c1dab76226786013c75844589992bf2cd566d21bad11b3fbb0cd0c63ad10525361f2fae5 + languageName: node + linkType: hard + "@nestjs/typeorm@npm:^8.0.2": version: 8.1.4 resolution: "@nestjs/typeorm@npm:8.1.4" @@ -5848,6 +5861,13 @@ __metadata: languageName: node linkType: hard +"charenc@npm:0.0.2": + version: 0.0.2 + resolution: "charenc@npm:0.0.2" + checksum: 81dcadbe57e861d527faf6dd3855dc857395a1c4d6781f4847288ab23cffb7b3ee80d57c15bba7252ffe3e5e8019db767757ee7975663ad2ca0939bb8fcaf2e5 + languageName: node + linkType: hard + "chokidar@npm:3.5.3, chokidar@npm:^3.4.0, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -6317,6 +6337,13 @@ __metadata: languageName: node linkType: hard +"crypt@npm:0.0.2": + version: 0.0.2 + resolution: "crypt@npm:0.0.2" + checksum: baf4c7bbe05df656ec230018af8cf7dbe8c14b36b98726939cef008d473f6fe7a4fad906cfea4062c93af516f1550a3f43ceb4d6615329612c6511378ed9fe34 + languageName: node + linkType: hard + "css-box-model@npm:1.2.1": version: 1.2.1 resolution: "css-box-model@npm:1.2.1" @@ -8011,6 +8038,7 @@ __metadata: "@babel/plugin-proposal-decorators": ^7.17.8 "@babel/preset-env": ^7.16.11 "@babel/preset-typescript": ^7.16.7 + "@nestjs/throttler": ^5.0.1 "@types/nodemailer": ^6.4.7 "@typescript-eslint/eslint-plugin": ^5.15.0 "@typescript-eslint/parser": ^5.15.0 @@ -8501,6 +8529,13 @@ __metadata: languageName: node linkType: hard +"is-buffer@npm:~1.1.6": + version: 1.1.6 + resolution: "is-buffer@npm:1.1.6" + checksum: 4a186d995d8bbf9153b4bd9ff9fd04ae75068fe695d29025d25e592d9488911eeece84eefbd8fa41b8ddcc0711058a71d4c466dcf6f1f6e1d83830052d8ca707 + languageName: node + linkType: hard + "is-callable@npm:^1.1.4, is-callable@npm:^1.2.6": version: 1.2.7 resolution: "is-callable@npm:1.2.7" @@ -9749,6 +9784,17 @@ __metadata: languageName: node linkType: hard +"md5@npm:^2.2.1": + version: 2.3.0 + resolution: "md5@npm:2.3.0" + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: ~1.1.6 + checksum: a63cacf4018dc9dee08c36e6f924a64ced735b37826116c905717c41cebeb41a522f7a526ba6ad578f9c80f02cb365033ccd67fe186ffbcc1a1faeb75daa9b6e + languageName: node + linkType: hard + "mdast-util-from-markdown@npm:^1.2.0": version: 1.2.0 resolution: "mdast-util-from-markdown@npm:1.2.0"