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,103 @@
import { Injectable, Inject } from '@nestjs/common';
import { RedisClientToken } from '../../core/configs/cache/redis-client.adapter.provider';
import { IRedisClient } from '../../core/configs/cache/IRedisClient';
import { JwtService } from '@nestjs/jwt';
import { JwtPayload } from '../models/jwt-payload.model';
@Injectable()
export class TokenBlacklistService {
constructor(
@Inject(RedisClientToken) private readonly redis: IRedisClient,
private readonly jwtService: JwtService,
) {}
/**
* Adiciona um token à blacklist
* @param token Token JWT a ser invalidado
* @param expiresIn Tempo de expiração do token em segundos
*/
async addToBlacklist(token: string, expiresIn?: number): Promise<void> {
try {
const decoded = this.jwtService.decode(token) as JwtPayload;
if (!decoded) {
throw new Error('Token inválido');
}
const blacklistKey = this.buildBlacklistKey(token);
const ttl = expiresIn || this.calculateTokenTTL(decoded);
await this.redis.set(blacklistKey, 'blacklisted', ttl);
} catch (error) {
throw new Error(`Erro ao adicionar token à blacklist: ${error.message}`);
}
}
/**
* Verifica se um token está na blacklist
* @param token Token JWT a ser verificado
* @returns true se o token estiver blacklistado
*/
async isBlacklisted(token: string): Promise<boolean> {
try {
const blacklistKey = this.buildBlacklistKey(token);
const result = await this.redis.get(blacklistKey);
return result === 'blacklisted';
} catch (error) {
return false;
}
}
/**
* Remove um token da blacklist (útil para testes)
* @param token Token JWT a ser removido
*/
async removeFromBlacklist(token: string): Promise<void> {
const blacklistKey = this.buildBlacklistKey(token);
await this.redis.del(blacklistKey);
}
/**
* Limpa todos os tokens blacklistados de um usuário
* @param userId ID do usuário
*/
async clearUserBlacklist(userId: number): Promise<void> {
const pattern = `auth:blacklist:${userId}:*`;
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
/**
* Constrói a chave para armazenar o token na blacklist
* @param token Token JWT
* @returns Chave para o Redis
*/
private buildBlacklistKey(token: string): string {
const decoded = this.jwtService.decode(token) as JwtPayload;
const tokenHash = this.hashToken(token);
return `auth:blacklist:${decoded.id}:${tokenHash}`;
}
/**
* Calcula o TTL do token baseado na expiração
* @param payload Payload do JWT
* @returns TTL em segundos
*/
private calculateTokenTTL(payload: JwtPayload): number {
const now = Math.floor(Date.now() / 1000);
const exp = payload.exp || (now + 8 * 60 * 60); // 8h padrão
return Math.max(0, exp - now);
}
/**
* Gera um hash do token para usar como identificador único
* @param token Token JWT
* @returns Hash do token
*/
private hashToken(token: string): string {
const crypto = require('crypto');
return crypto.createHash('sha256').update(token).digest('hex').substring(0, 16);
}
}