diff --git a/.github/branch-rules.yml b/.github/branch-rules.yml new file mode 100644 index 00000000..e7ec3469 --- /dev/null +++ b/.github/branch-rules.yml @@ -0,0 +1,8 @@ +# Branch protection rules +rules: + - pattern: main + required_status_checks: + - CI + required_approving_review_count: 1 + dismiss_stale_reviews: true + restrict_pushes: true \ No newline at end of file diff --git a/src/middleware/throttle/throttle.middleware.ts b/src/middleware/throttle/throttle.middleware.ts new file mode 100644 index 00000000..ea75ebd1 --- /dev/null +++ b/src/middleware/throttle/throttle.middleware.ts @@ -0,0 +1,25 @@ +import { Injectable, NestMiddleware, HttpException, HttpStatus } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +const hits = new Map(); +const LIMIT = 100; +const WINDOW_MS = 60_000; + +@Injectable() +export class ThrottleMiddleware implements NestMiddleware { + use(req: Request, res: Response, next: NextFunction): void { + const key = (req.headers['x-forwarded-for'] as string)?.split(',')[0].trim() ?? req.ip ?? 'unknown'; + const now = Date.now(); + const entry = hits.get(key); + if (!entry || now > entry.reset) { + hits.set(key, { count: 1, reset: now + WINDOW_MS }); + return next(); + } + if (entry.count >= LIMIT) { + res.setHeader('Retry-After', Math.ceil((entry.reset - now) / 1000)); + throw new HttpException('Too Many Requests', HttpStatus.TOO_MANY_REQUESTS); + } + entry.count++; + next(); + } +} \ No newline at end of file