- Adicionar refresh tokens para renovação automática de tokens - Implementar controle de sessões simultâneas - Adicionar blacklist de tokens para logout seguro - Implementar rate limiting para proteção contra ataques - Melhorar detecção de IP e identificação de sessão atual - Adicionar endpoints para gerenciamento de sessões - Corrigir inconsistências na validação de usuário - Atualizar configuração Redis com nova conexão
127 lines
3.7 KiB
TypeScript
127 lines
3.7 KiB
TypeScript
import { Injectable, Inject } from '@nestjs/common';
|
|
import { RedisClientToken } from '../../core/configs/cache/redis-client.adapter.provider';
|
|
import { IRedisClient } from '../../core/configs/cache/IRedisClient';
|
|
|
|
export interface RateLimitConfig {
|
|
maxAttempts: number;
|
|
windowMs: number;
|
|
blockDurationMs: number;
|
|
}
|
|
|
|
@Injectable()
|
|
export class RateLimitingService {
|
|
private readonly defaultConfig: RateLimitConfig = {
|
|
maxAttempts: 5, // 5 tentativas
|
|
windowMs: 15 * 60 * 1000, // 15 minutos
|
|
blockDurationMs: 30 * 60 * 1000, // 30 minutos de bloqueio
|
|
};
|
|
|
|
constructor(
|
|
@Inject(RedisClientToken) private readonly redis: IRedisClient,
|
|
) {}
|
|
|
|
/**
|
|
* Verifica se o IP pode fazer uma tentativa de login
|
|
* @param ip Endereço IP do cliente
|
|
* @param config Configuração personalizada (opcional)
|
|
* @returns true se permitido, false se bloqueado
|
|
*/
|
|
async isAllowed(ip: string, config?: Partial<RateLimitConfig>): Promise<boolean> {
|
|
const finalConfig = { ...this.defaultConfig, ...config };
|
|
const key = this.buildAttemptKey(ip);
|
|
const blockKey = this.buildBlockKey(ip);
|
|
|
|
// Verifica se está bloqueado
|
|
const isBlocked = await this.redis.get(blockKey);
|
|
if (isBlocked) {
|
|
return false;
|
|
}
|
|
|
|
// Conta tentativas na janela de tempo
|
|
const attempts = await this.redis.get<string>(key);
|
|
const attemptCount = attempts ? parseInt(attempts) : 0;
|
|
|
|
if (attemptCount >= finalConfig.maxAttempts) {
|
|
// Bloqueia o IP
|
|
await this.redis.set(blockKey, 'blocked', finalConfig.blockDurationMs / 1000);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Registra uma tentativa de login
|
|
* @param ip Endereço IP do cliente
|
|
* @param success true se login foi bem-sucedido
|
|
* @param config Configuração personalizada (opcional)
|
|
*/
|
|
async recordAttempt(ip: string, success: boolean, config?: Partial<RateLimitConfig>): Promise<void> {
|
|
const finalConfig = { ...this.defaultConfig, ...config };
|
|
const key = this.buildAttemptKey(ip);
|
|
|
|
if (success) {
|
|
await this.redis.del(key);
|
|
} else {
|
|
const attempts = await this.redis.get<string>(key);
|
|
const attemptCount = attempts ? parseInt(attempts) + 1 : 1;
|
|
|
|
await this.redis.set(key, attemptCount.toString(), finalConfig.windowMs / 1000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtém informações sobre tentativas de um IP
|
|
* @param ip Endereço IP do cliente
|
|
* @returns Informações sobre tentativas
|
|
*/
|
|
async getAttemptInfo(ip: string): Promise<{
|
|
attempts: number;
|
|
isBlocked: boolean;
|
|
remainingTime?: number;
|
|
}> {
|
|
const key = this.buildAttemptKey(ip);
|
|
const blockKey = this.buildBlockKey(ip);
|
|
|
|
const attempts = await this.redis.get<string>(key);
|
|
const isBlocked = await this.redis.get(blockKey);
|
|
const ttl = await this.redis.ttl(blockKey);
|
|
|
|
return {
|
|
attempts: attempts ? parseInt(attempts) : 0,
|
|
isBlocked: !!isBlocked,
|
|
remainingTime: isBlocked ? ttl : undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Limpa tentativas de um IP (útil para testes ou admin)
|
|
* @param ip Endereço IP do cliente
|
|
*/
|
|
async clearAttempts(ip: string): Promise<void> {
|
|
const key = this.buildAttemptKey(ip);
|
|
const blockKey = this.buildBlockKey(ip);
|
|
|
|
await this.redis.del(key);
|
|
await this.redis.del(blockKey);
|
|
}
|
|
|
|
/**
|
|
* Constrói a chave para armazenar tentativas
|
|
* @param ip Endereço IP
|
|
* @returns Chave para o Redis
|
|
*/
|
|
private buildAttemptKey(ip: string): string {
|
|
return `auth:rate_limit:attempts:${ip}`;
|
|
}
|
|
|
|
/**
|
|
* Constrói a chave para armazenar bloqueio
|
|
* @param ip Endereço IP
|
|
* @returns Chave para o Redis
|
|
*/
|
|
private buildBlockKey(ip: string): string {
|
|
return `auth:rate_limit:blocked:${ip}`;
|
|
}
|
|
}
|