refactor: return rate limit instead of throwing an object
This commit is contained in:
parent
fce656b5e9
commit
f9dd3aca78
|
@ -317,19 +317,15 @@ export class ApiCallService implements OnApplicationShutdown {
|
||||||
|
|
||||||
if (factor > 0) {
|
if (factor > 0) {
|
||||||
// Rate limit
|
// Rate limit
|
||||||
await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor).catch(err => {
|
const rateLimit = await this.rateLimiterService.limit(limit as IEndpointMeta['limit'] & { key: NonNullable<string> }, limitActor, factor);
|
||||||
if ('info' in err) {
|
if (rateLimit != null) {
|
||||||
// errはLimiter.LimiterInfoであることが期待される
|
throw new ApiError({
|
||||||
throw new ApiError({
|
message: 'Rate limit exceeded. Please try again later.',
|
||||||
message: 'Rate limit exceeded. Please try again later.',
|
code: 'RATE_LIMIT_EXCEEDED',
|
||||||
code: 'RATE_LIMIT_EXCEEDED',
|
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
||||||
id: 'd5826d14-3982-4d2e-8011-b9e9f02499ef',
|
httpStatusCode: 429,
|
||||||
httpStatusCode: 429,
|
}, rateLimit.info);
|
||||||
}, err.info);
|
}
|
||||||
} else {
|
|
||||||
throw new TypeError('information must be a rate-limiter information.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,14 @@ import { LoggerService } from '@/core/LoggerService.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import type { IEndpointMeta } from './endpoints.js';
|
import type { IEndpointMeta } from './endpoints.js';
|
||||||
|
|
||||||
|
type RateLimitInfo = {
|
||||||
|
code: 'BRIEF_REQUEST_INTERVAL',
|
||||||
|
info: Limiter.LimiterInfo,
|
||||||
|
} | {
|
||||||
|
code: 'RATE_LIMIT_EXCEEDED',
|
||||||
|
info: Limiter.LimiterInfo,
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RateLimiterService {
|
export class RateLimiterService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
@ -35,7 +43,7 @@ export class RateLimiterService {
|
||||||
return new Promise<Limiter.LimiterInfo>((resolve, reject) => {
|
return new Promise<Limiter.LimiterInfo>((resolve, reject) => {
|
||||||
new Limiter(options).get((err, info) => {
|
new Limiter(options).get((err, info) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject({ code: 'ERR', info });
|
return reject(err);
|
||||||
}
|
}
|
||||||
resolve(info);
|
resolve(info);
|
||||||
});
|
});
|
||||||
|
@ -43,9 +51,9 @@ export class RateLimiterService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1) {
|
public async limit(limitation: IEndpointMeta['limit'] & { key: NonNullable<string> }, actor: string, factor = 1): Promise<RateLimitInfo | null> {
|
||||||
if (this.disabled) {
|
if (this.disabled) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short-term limit
|
// Short-term limit
|
||||||
|
@ -61,7 +69,7 @@ export class RateLimiterService {
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
if (info.remaining === 0) {
|
||||||
// eslint-disable-next-line no-throw-literal
|
// eslint-disable-next-line no-throw-literal
|
||||||
throw { code: 'BRIEF_REQUEST_INTERVAL', info };
|
return { code: 'BRIEF_REQUEST_INTERVAL', info };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,8 +86,10 @@ export class RateLimiterService {
|
||||||
|
|
||||||
if (info.remaining === 0) {
|
if (info.remaining === 0) {
|
||||||
// eslint-disable-next-line no-throw-literal
|
// eslint-disable-next-line no-throw-literal
|
||||||
throw { code: 'RATE_LIMIT_EXCEEDED', info };
|
return { code: 'RATE_LIMIT_EXCEEDED', info };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,10 +73,9 @@ export class SigninApiService {
|
||||||
return { error };
|
return { error };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
// not more than 1 attempt per second and not more than 10 attempts per hour
|
// not more than 1 attempt per second and not more than 10 attempts per hour
|
||||||
await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
const rateLimit = await this.rateLimiterService.limit({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(request.ip));
|
||||||
} catch (err) {
|
if (rateLimit != null) {
|
||||||
reply.code(429);
|
reply.code(429);
|
||||||
return {
|
return {
|
||||||
error: {
|
error: {
|
||||||
|
|
Loading…
Reference in New Issue