feat: implementar melhorias na autenticação
- 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
This commit is contained in:
126
src/auth/services/rate-limiting.service.ts
Normal file
126
src/auth/services/rate-limiting.service.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user