manicanldes-backend/src/identity/rate-limit.service.ts

31 lines
1.2 KiB
TypeScript

import { AppError } from '../common/errors/app-error';
import { Injectable } from '@nestjs/common';
import { DatabaseService } from '../database/database.service';
import { hashToken } from './tokens';
@Injectable()
export class RateLimitService {
constructor(private readonly db: DatabaseService) {}
async consume(key: string, limit: number, seconds: number): Promise<void> {
const digest = hashToken(key);
const [row] = await this.db.$queryRaw<
Array<{ hits: number; expiresAt: Date }>
>`
INSERT INTO rate_limits (key, hits, expires_at)
VALUES (${digest}, 1, NOW() + ${seconds} * INTERVAL '1 second')
ON CONFLICT (key) DO UPDATE SET
hits = CASE WHEN rate_limits.expires_at <= NOW() THEN 1 ELSE rate_limits.hits + 1 END,
expires_at = CASE WHEN rate_limits.expires_at <= NOW()
THEN NOW() + ${seconds} * INTERVAL '1 second' ELSE rate_limits.expires_at END
RETURNING hits, expires_at AS "expiresAt"
`;
if (row.hits > limit)
throw new AppError(
'RATE_LIMITED',
key.split(':')[0],
undefined,
Math.max(1, Math.ceil((row.expiresAt.getTime() - Date.now()) / 1000)),
);
}
}