diff --git a/api/package.json b/api/package.json index d1be7252..ca377285 100644 --- a/api/package.json +++ b/api/package.json @@ -30,6 +30,7 @@ "class-validator": "^0.10.0", "csv": "^5.1.1", "ejs": "^2.6.1", + "express-rate-limit": "^5.1.3", "generate-password": "^1.4.1", "gettext-parser": "^3.1.0", "handlebars": "^4.5.3", diff --git a/api/src/app.module.ts b/api/src/app.module.ts index 3fac36a8..817ca15a 100644 --- a/api/src/app.module.ts +++ b/api/src/app.module.ts @@ -5,6 +5,7 @@ import { PassportModule } from '@nestjs/passport'; import { NestExpressApplication } from '@nestjs/platform-express'; import { TypeOrmModule } from '@nestjs/typeorm'; import { renderFile } from 'ejs'; +import * as rateLimit from 'express-rate-limit'; import { config } from './config'; import { AuthController } from './controllers/auth.controller'; import { ExportsController } from './controllers/exports.controller'; @@ -100,6 +101,14 @@ export const addPipesAndFilters = (app: NestExpressApplication) => { whitelist: true, }), ); + + // rate-limiting middleware + app.use( + rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 20, // limit each IP to 20 requests per windowMs + }), + ); app.useStaticAssets(config.publicDir, { index: false, redirect: false }); diff --git a/api/src/services/user.service.ts b/api/src/services/user.service.ts index 70569501..a61bca9f 100644 --- a/api/src/services/user.service.ts +++ b/api/src/services/user.service.ts @@ -181,18 +181,9 @@ export class UserService { async authenticate({ grantType, email, password }: { grantType: GrantType; email: string; password?: string }): Promise { const normalizedEmail = normalizeEmail(email); - const user = await this.userRepo.findOneOrFail({ email: normalizedEmail }); - - const timeThreshold = moment() - .subtract(15, 'minutes') - .toDate(); - - // If lockout time has passed, reset counter - if (user.lastLogin < timeThreshold) { - user.loginAttempts = 0; - // Otherwise abort request - } else if (user.loginAttempts >= 3) { - throw new TooManyRequestsException('too many login attempts'); + const user = await this.userRepo.findOne({ email: normalizedEmail }); + if (!user) { + throw new UnauthorizedException('invalid credentials'); } switch (grantType) {