diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts index 176144f3e..201c234b7 100644 --- a/packages/backend/src/server/api/error.ts +++ b/packages/backend/src/server/api/error.ts @@ -36,7 +36,7 @@ export class ApiError extends Error { break; case 429: if (typeof this.info === 'object' && typeof this.info.reset === 'number') { - ctx.respose.set('Retry-After', Math.floor(this.info.reset - (Date.now() / 1000))); + ctx.response.set('Retry-After', Math.floor(this.info.reset - (Date.now() / 1000))); } break; } diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 2780afff3..63aba87bf 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -6,7 +6,7 @@ import { ApiError } from './error.js'; const logger = new Logger('limiter'); -export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) => new Promise((resolve) => { +export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable }, actor: string) => new Promise((resolve, reject) => { if (process.env.NODE_ENV === 'test') resolve(); const hasShortTermLimit = typeof limitation.minInterval === 'number'; @@ -15,45 +15,8 @@ export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable< typeof limitation.duration === 'number' && typeof limitation.max === 'number'; - if (hasShortTermLimit) { - min(); - } else if (hasLongTermLimit) { - max(); - } else { - resolve(); - } - - // Short-term limit, calls long term limit if appropriate. - function min(): void { - const minIntervalLimiter = new Limiter({ - id: `${actor}:${limitation.key}:min`, - duration: limitation.minInterval, - max: 1, - db: redisClient, - }); - - minIntervalLimiter.get((err, info) => { - if (err) { - logger.error(err); - throw new ApiError('INTERNAL_ERROR'); - } - - logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); - - if (info.remaining === 0) { - throw new ApiError('RATE_LIMIT_EXCEEDED', info); - } else { - if (hasLongTermLimit) { - max(); - } else { - resolve(); - } - } - }); - } - // Long term limit - function max(): void { + const max = (): void => { const limiter = new Limiter({ id: `${actor}:${limitation.key}`, duration: limitation.duration, @@ -64,16 +27,53 @@ export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable< limiter.get((err, info) => { if (err) { logger.error(err); - throw new ApiError('INTERNAL_ERROR'); + reject(new ApiError('INTERNAL_ERROR')); } logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); if (info.remaining === 0) { - throw new ApiError('RATE_LIMIT_EXCEEDED', info); + reject(new ApiError('RATE_LIMIT_EXCEEDED', info)); } else { resolve(); } }); } + + // Short-term limit, calls long term limit if appropriate. + const min = (): void => { + const minIntervalLimiter = new Limiter({ + id: `${actor}:${limitation.key}:min`, + duration: limitation.minInterval, + max: 1, + db: redisClient, + }); + + minIntervalLimiter.get((err, info) => { + if (err) { + logger.error(err); + reject(new ApiError('INTERNAL_ERROR')); + } + + logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`); + + if (info.remaining === 0) { + reject(new ApiError('RATE_LIMIT_EXCEEDED', info)); + } else { + if (hasLongTermLimit) { + max(); + } else { + resolve(); + } + } + }); + } + + if (hasShortTermLimit) { + min(); + } else if (hasLongTermLimit) { + max(); + } else { + resolve(); + } });