server: fix rate limit error propagation

Changelog: Fixed
This commit is contained in:
Johann150 2023-03-28 22:40:51 +02:00
parent 1171567db2
commit 48405fba3b
Signed by: Johann150
GPG key ID: 9EE6577A2A06F8F1
2 changed files with 42 additions and 42 deletions

View file

@ -36,7 +36,7 @@ export class ApiError extends Error {
break; break;
case 429: case 429:
if (typeof this.info === 'object' && typeof this.info.reset === 'number') { 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; break;
} }

View file

@ -6,7 +6,7 @@ import { ApiError } from './error.js';
const logger = new Logger('limiter'); const logger = new Logger('limiter');
export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((resolve) => { export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string) => new Promise<void>((resolve, reject) => {
if (process.env.NODE_ENV === 'test') resolve(); if (process.env.NODE_ENV === 'test') resolve();
const hasShortTermLimit = typeof limitation.minInterval === 'number'; const hasShortTermLimit = typeof limitation.minInterval === 'number';
@ -15,45 +15,8 @@ export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<
typeof limitation.duration === 'number' && typeof limitation.duration === 'number' &&
typeof limitation.max === '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 // Long term limit
function max(): void { const max = (): void => {
const limiter = new Limiter({ const limiter = new Limiter({
id: `${actor}:${limitation.key}`, id: `${actor}:${limitation.key}`,
duration: limitation.duration, duration: limitation.duration,
@ -64,16 +27,53 @@ export const limiter = (limitation: IEndpointMeta['limit'] & { key: NonNullable<
limiter.get((err, info) => { limiter.get((err, info) => {
if (err) { if (err) {
logger.error(err); logger.error(err);
throw new ApiError('INTERNAL_ERROR'); reject(new ApiError('INTERNAL_ERROR'));
} }
logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`); logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
if (info.remaining === 0) { if (info.remaining === 0) {
throw new ApiError('RATE_LIMIT_EXCEEDED', info); reject(new ApiError('RATE_LIMIT_EXCEEDED', info));
} else { } else {
resolve(); 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();
}
}); });