summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/RateLimiterService.ts
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2022-09-18 03:27:08 +0900
committerGitHub <noreply@github.com>2022-09-18 03:27:08 +0900
commitb75184ec8e3436200bacdcd832e3324702553d20 (patch)
tree8b7e316f29e95df921db57289c8b8da476d18f07 /packages/backend/src/server/api/RateLimiterService.ts
parentUpdate ROADMAP.md (diff)
downloadsharkey-b75184ec8e3436200bacdcd832e3324702553d20.tar.gz
sharkey-b75184ec8e3436200bacdcd832e3324702553d20.tar.bz2
sharkey-b75184ec8e3436200bacdcd832e3324702553d20.zip
なんかもうめっちゃ変えた
Diffstat (limited to 'packages/backend/src/server/api/RateLimiterService.ts')
-rw-r--r--packages/backend/src/server/api/RateLimiterService.ts89
1 files changed, 89 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts
new file mode 100644
index 0000000000..d390a47b8f
--- /dev/null
+++ b/packages/backend/src/server/api/RateLimiterService.ts
@@ -0,0 +1,89 @@
+import { Inject, Injectable } from '@nestjs/common';
+import Limiter from 'ratelimiter';
+import Redis from 'ioredis';
+import { DI } from '@/di-symbols.js';
+import Logger from '@/logger.js';
+import type { IEndpointMeta } from './endpoints.js';
+
+const logger = new Logger('limiter');
+
+@Injectable()
+export class RateLimiterService {
+ constructor(
+ @Inject(DI.redis)
+ private redisClient: Redis.Redis,
+ ) {
+ }
+
+ public limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) {
+ return new Promise<void>((ok, reject) => {
+ if (process.env.NODE_ENV === 'test') ok();
+
+ // Short-term limit
+ const min = (): void => {
+ const minIntervalLimiter = new Limiter({
+ id: `${actor}:${limitation.key}:min`,
+ duration: limitation.minInterval,
+ max: 1,
+ db: this.redisClient,
+ });
+
+ minIntervalLimiter.get((err, info) => {
+ if (err) {
+ return reject('ERR');
+ }
+
+ logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
+
+ if (info.remaining === 0) {
+ reject('BRIEF_REQUEST_INTERVAL');
+ } else {
+ if (hasLongTermLimit) {
+ max();
+ } else {
+ ok();
+ }
+ }
+ });
+ };
+
+ // Long term limit
+ const max = (): void => {
+ const limiter = new Limiter({
+ id: `${actor}:${limitation.key}`,
+ duration: limitation.duration,
+ max: limitation.max,
+ db: this.redisClient,
+ });
+
+ limiter.get((err, info) => {
+ if (err) {
+ return reject('ERR');
+ }
+
+ logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
+
+ if (info.remaining === 0) {
+ reject('RATE_LIMIT_EXCEEDED');
+ } else {
+ ok();
+ }
+ });
+ };
+
+ const hasShortTermLimit = typeof limitation.minInterval === 'number';
+
+ const hasLongTermLimit =
+ typeof limitation.duration === 'number' &&
+ typeof limitation.max === 'number';
+
+ if (hasShortTermLimit) {
+ min();
+ } else if (hasLongTermLimit) {
+ max();
+ } else {
+ ok();
+ }
+ });
+ }
+}