diff --git a/api/controllers/AdminController.ts b/api/controllers/AdminController.ts index 6ab761084..6fe30da9a 100644 --- a/api/controllers/AdminController.ts +++ b/api/controllers/AdminController.ts @@ -1,5 +1,6 @@ import { JsonController, Post, Patch, UploadedFile, UseBefore, ForbiddenError, Body, Get } from 'routing-controllers'; import { UserAuthentication } from '../middleware/UserAuthentication'; +import { RateLimiter } from '../middleware/RateLimiter'; import { CreateBonusRequest, CreateMilestoneRequest, @@ -23,6 +24,7 @@ import StorageService from '../../services/StorageService'; import PermissionsService from '../../services/PermissionsService'; import { UserModel } from '../../models/UserModel'; import AttendanceService from '../../services/AttendanceService'; +import { RateLimit } from '../decorators/RateLimit'; @UseBefore(UserAuthentication) @JsonController('/admin') @@ -40,7 +42,9 @@ export class AdminController { this.attendanceService = attendanceService; } + @Get('/email') + @RateLimit() async getAllEmails(@AuthenticatedUser() user: UserModel): Promise { if (!PermissionsService.canSeeAllUserEmails(user)) throw new ForbiddenError(); const emails = await this.userAccountService.getAllEmails(); diff --git a/api/controllers/UserController.ts b/api/controllers/UserController.ts index e80f4347d..e7a93004d 100644 --- a/api/controllers/UserController.ts +++ b/api/controllers/UserController.ts @@ -7,6 +7,7 @@ import UserSocialMediaService from '../../services/UserSocialMediaService'; import StorageService from '../../services/StorageService'; import { UserAuthentication } from '../middleware/UserAuthentication'; import { AuthenticatedUser } from '../decorators/AuthenticatedUser'; +import { RateLimit } from '../decorators/RateLimit'; import { MediaType, File, diff --git a/api/decorators/RateLimit.ts b/api/decorators/RateLimit.ts new file mode 100644 index 000000000..b022ba780 --- /dev/null +++ b/api/decorators/RateLimit.ts @@ -0,0 +1,23 @@ +import rateLimit from 'express-rate-limit'; + +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 5, + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers +}); + +console.log("HELLO"); + +export function RateLimit(): MethodDecorator { + return function (_target: any, _key: string, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value; + console.log("originalMethod", originalMethod); + descriptor.value = function (...args: any[]) { + const context = args[0]; + limiter(context.req, context.res, () => {}); + return originalMethod.apply(this, args); + }; + return descriptor; + }; +} \ No newline at end of file diff --git a/api/middleware/RateLimiter.ts b/api/middleware/RateLimiter.ts new file mode 100644 index 000000000..d335d35b5 --- /dev/null +++ b/api/middleware/RateLimiter.ts @@ -0,0 +1,19 @@ +import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; + import * as express from 'express'; + import { rateLimit } from 'express-rate-limit'; + // import { Config } from '../../config'; + + @Middleware({ type: 'before' }) + export class RateLimiter implements ExpressMiddlewareInterface { + private limiter = rateLimit({ + windowMs: 15 * 60 * 1000, + max: 5, + message: 'Too many requests, please try again later.', + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers + }); + + use(req: express.Request, res: express.Response, next: express.NextFunction) { + return this.limiter(req, res, next); + } +} \ No newline at end of file diff --git a/api/middleware/index.ts b/api/middleware/index.ts index 8907e8f08..5c13b78de 100644 --- a/api/middleware/index.ts +++ b/api/middleware/index.ts @@ -2,10 +2,12 @@ import { RequestLogger } from './RequestLogger'; import { ErrorHandler } from './ErrorHandler'; import { NotFoundHandler } from './NotFoundHandler'; import { MetricsRecorder } from './MetricsRecorder'; +//import { RateLimiter } from './RateLimiter'; export const middlewares = [ ErrorHandler, NotFoundHandler, RequestLogger, MetricsRecorder, + //RateLimiter ]; diff --git a/index.ts b/index.ts index ed1aafffa..3dd1a1e4c 100644 --- a/index.ts +++ b/index.ts @@ -53,4 +53,6 @@ const app = createExpressServer({ defaultErrorHandler: false, }); +app.set('trust proxy', 1); + app.listen(Config.port); diff --git a/package.json b/package.json index ffa2d67fe..50387751f 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dotenv": "^8.2.0", "ejs": "^3.1.3", "express": "^4.17.1", + "express-rate-limit": "6.11.2", "faker": "^5.5.3", "jsonwebtoken": "^8.5.1", "moment": "^2.27.0", diff --git a/yarn.lock b/yarn.lock index c84255721..b3b8fbd48 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2392,6 +2392,11 @@ expect@^27.5.1: jest-matcher-utils "^27.5.1" jest-message-util "^27.5.1" +express-rate-limit@6.11.2: + version "6.11.2" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-6.11.2.tgz#6c42035603d3b52e4e2fb59f6ebaa89e628ef980" + integrity sha512-a7uwwfNTh1U60ssiIkuLFWHt4hAC5yxlLGU2VP0X4YNlyEDZAqF4tK3GD3NSitVBrCQmQ0++0uOyFOgC2y4DDw== + express-session@^1.17.1: version "1.17.2" resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.2.tgz#397020374f9bf7997f891b85ea338767b30d0efd"