summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/limiter.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/limiter.ts')
-rw-r--r--packages/backend/src/server/api/limiter.ts83
1 files changed, 83 insertions, 0 deletions
diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts
new file mode 100644
index 0000000000..1e2fe5bcb3
--- /dev/null
+++ b/packages/backend/src/server/api/limiter.ts
@@ -0,0 +1,83 @@
+import * as Limiter from 'ratelimiter';
+import { redisClient } from '../../db/redis';
+import { IEndpoint } from './endpoints';
+import * as Acct from 'misskey-js/built/acct';
+import { User } from '@/models/entities/user';
+import Logger from '@/services/logger';
+
+const logger = new Logger('limiter');
+
+export default (endpoint: IEndpoint, user: User) => new Promise<void>((ok, reject) => {
+ const limitation = endpoint.meta.limit!;
+
+ const key = limitation.hasOwnProperty('key')
+ ? limitation.key
+ : endpoint.name;
+
+ const hasShortTermLimit =
+ limitation.hasOwnProperty('minInterval');
+
+ const hasLongTermLimit =
+ limitation.hasOwnProperty('duration') &&
+ limitation.hasOwnProperty('max');
+
+ if (hasShortTermLimit) {
+ min();
+ } else if (hasLongTermLimit) {
+ max();
+ } else {
+ ok();
+ }
+
+ // Short-term limit
+ function min() {
+ const minIntervalLimiter = new Limiter({
+ id: `${user.id}:${key}:min`,
+ duration: limitation.minInterval,
+ max: 1,
+ db: redisClient
+ });
+
+ minIntervalLimiter.get((err, info) => {
+ if (err) {
+ return reject('ERR');
+ }
+
+ logger.debug(`@${Acct.toString(user)} ${endpoint.name} min remaining: ${info.remaining}`);
+
+ if (info.remaining === 0) {
+ reject('BRIEF_REQUEST_INTERVAL');
+ } else {
+ if (hasLongTermLimit) {
+ max();
+ } else {
+ ok();
+ }
+ }
+ });
+ }
+
+ // Long term limit
+ function max() {
+ const limiter = new Limiter({
+ id: `${user.id}:${key}`,
+ duration: limitation.duration,
+ max: limitation.max,
+ db: redisClient
+ });
+
+ limiter.get((err, info) => {
+ if (err) {
+ return reject('ERR');
+ }
+
+ logger.debug(`@${Acct.toString(user)} ${endpoint.name} max remaining: ${info.remaining}`);
+
+ if (info.remaining === 0) {
+ reject('RATE_LIMIT_EXCEEDED');
+ } else {
+ ok();
+ }
+ });
+ }
+});