-
Notifications
You must be signed in to change notification settings - Fork 0
/
rate-limit-guard.ts
61 lines (48 loc) · 1.7 KB
/
rate-limit-guard.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import { Reflector } from '@nestjs/core';
import {
Injectable,
CanActivate,
ExecutionContext,
Inject,
} from '@nestjs/common';
import { Request } from 'express';
import { TooManyRequestsException, config } from '@/common';
import {
ACCESS_CONTROL_PROVIDER,
AccessControlProvider,
} from '../access-control';
import { RATE_LIMIT_WEIGHT_METADATA_KEY } from '../decorators';
@Injectable()
export class RateLimitGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
@Inject(ACCESS_CONTROL_PROVIDER)
private readonly accessControlProvider: AccessControlProvider,
) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest<Request>();
const metadataTargets = [context.getHandler(), context.getClass()];
const weight =
this.reflector.getAllAndOverride<number>(
RATE_LIMIT_WEIGHT_METADATA_KEY,
metadataTargets,
) || 1;
const { ip } = request;
const token: string = request['token'];
const isForToken = !!token;
const { exceeded, tryAfter } = await (isForToken
? this.accessControlProvider.validateRateLimitForToken(token, weight)
: this.accessControlProvider.validateRateLimitForIp(ip, weight));
if (exceeded) {
const { rangeSeconds, maxRequestsIp, maxRequestsToken } =
config.rateLimit;
const limitationLemma = `${
isForToken ? maxRequestsToken : maxRequestsIp
} request points per ${Math.trunc(rangeSeconds / 60)} minutes`;
throw new TooManyRequestsException(
`Rate Limit (${limitationLemma}) exceeded. This request costs ${weight} points. Try after ${tryAfter.toISOString()}`,
);
}
return true;
}
}