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:
Joelson
2025-09-16 18:17:37 -03:00
parent 055f138e5a
commit 21c3225c52
33 changed files with 1061 additions and 1375 deletions

View 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}`;
}
}