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:
173
src/auth/services/refresh-token.service.ts
Normal file
173
src/auth/services/refresh-token.service.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Injectable, Inject, UnauthorizedException } 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';
|
||||
import { randomBytes } from 'crypto';
|
||||
|
||||
export interface RefreshTokenData {
|
||||
userId: number;
|
||||
tokenId: string;
|
||||
expiresAt: number;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RefreshTokenService {
|
||||
private readonly REFRESH_TOKEN_TTL = 7 * 24 * 60 * 60; // 7 dias em segundos
|
||||
private readonly MAX_REFRESH_TOKENS_PER_USER = 5; // Máximo 5 refresh tokens por usuário
|
||||
|
||||
constructor(
|
||||
@Inject(RedisClientToken) private readonly redis: IRedisClient,
|
||||
private readonly jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Gera um novo refresh token para o usuário
|
||||
* @param userId ID do usuário
|
||||
* @returns Refresh token
|
||||
*/
|
||||
async generateRefreshToken(userId: number): Promise<string> {
|
||||
const tokenId = randomBytes(32).toString('hex');
|
||||
const refreshToken = this.jwtService.sign(
|
||||
{ userId, tokenId, type: 'refresh' },
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
const tokenData: RefreshTokenData = {
|
||||
userId,
|
||||
tokenId,
|
||||
expiresAt: Date.now() + (this.REFRESH_TOKEN_TTL * 1000),
|
||||
createdAt: Date.now(),
|
||||
};
|
||||
|
||||
const key = this.buildRefreshTokenKey(userId, tokenId);
|
||||
await this.redis.set(key, tokenData, this.REFRESH_TOKEN_TTL);
|
||||
|
||||
// Limita o número de refresh tokens por usuário
|
||||
await this.limitRefreshTokensPerUser(userId);
|
||||
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida um refresh token e retorna os dados do usuário
|
||||
* @param refreshToken Token de refresh
|
||||
* @returns Dados do usuário se válido
|
||||
*/
|
||||
async validateRefreshToken(refreshToken: string): Promise<JwtPayload> {
|
||||
try {
|
||||
const decoded = this.jwtService.verify(refreshToken) as any;
|
||||
|
||||
if (decoded.type !== 'refresh') {
|
||||
throw new UnauthorizedException('Token inválido');
|
||||
}
|
||||
|
||||
const { userId, tokenId } = decoded;
|
||||
const key = this.buildRefreshTokenKey(userId, tokenId);
|
||||
const tokenData = await this.redis.get<RefreshTokenData>(key);
|
||||
|
||||
if (!tokenData) {
|
||||
throw new UnauthorizedException('Refresh token expirado ou inválido');
|
||||
}
|
||||
|
||||
if (tokenData.expiresAt < Date.now()) {
|
||||
await this.revokeRefreshToken(userId, tokenId);
|
||||
throw new UnauthorizedException('Refresh token expirado');
|
||||
}
|
||||
|
||||
return {
|
||||
id: userId,
|
||||
sellerId: 0,
|
||||
storeId: '',
|
||||
username: '',
|
||||
email: '',
|
||||
tokenId
|
||||
} as JwtPayload;
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Refresh token inválido');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoga um refresh token específico
|
||||
* @param userId ID do usuário
|
||||
* @param tokenId ID do token
|
||||
*/
|
||||
async revokeRefreshToken(userId: number, tokenId: string): Promise<void> {
|
||||
const key = this.buildRefreshTokenKey(userId, tokenId);
|
||||
await this.redis.del(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoga todos os refresh tokens de um usuário
|
||||
* @param userId ID do usuário
|
||||
*/
|
||||
async revokeAllRefreshTokens(userId: number): Promise<void> {
|
||||
const pattern = this.buildRefreshTokenPattern(userId);
|
||||
const keys = await this.redis.keys(pattern);
|
||||
|
||||
if (keys.length > 0) {
|
||||
await this.redis.del(...keys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todos os refresh tokens ativos de um usuário
|
||||
* @param userId ID do usuário
|
||||
* @returns Lista de tokens ativos
|
||||
*/
|
||||
async getActiveRefreshTokens(userId: number): Promise<RefreshTokenData[]> {
|
||||
const pattern = this.buildRefreshTokenPattern(userId);
|
||||
const keys = await this.redis.keys(pattern);
|
||||
|
||||
const tokens: RefreshTokenData[] = [];
|
||||
|
||||
for (const key of keys) {
|
||||
const tokenData = await this.redis.get<RefreshTokenData>(key);
|
||||
if (tokenData && tokenData.expiresAt > Date.now()) {
|
||||
tokens.push(tokenData);
|
||||
}
|
||||
}
|
||||
|
||||
return tokens.sort((a, b) => b.createdAt - a.createdAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Limita o número de refresh tokens por usuário
|
||||
* @param userId ID do usuário
|
||||
*/
|
||||
private async limitRefreshTokensPerUser(userId: number): Promise<void> {
|
||||
const activeTokens = await this.getActiveRefreshTokens(userId);
|
||||
|
||||
if (activeTokens.length > this.MAX_REFRESH_TOKENS_PER_USER) {
|
||||
// Remove os tokens mais antigos
|
||||
const tokensToRemove = activeTokens
|
||||
.slice(this.MAX_REFRESH_TOKENS_PER_USER)
|
||||
.map(token => token.tokenId);
|
||||
|
||||
for (const tokenId of tokensToRemove) {
|
||||
await this.revokeRefreshToken(userId, tokenId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrói a chave para armazenar o refresh token
|
||||
* @param userId ID do usuário
|
||||
* @param tokenId ID do token
|
||||
* @returns Chave para o Redis
|
||||
*/
|
||||
private buildRefreshTokenKey(userId: number, tokenId: string): string {
|
||||
return `auth:refresh_tokens:${userId}:${tokenId}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constrói o padrão para buscar refresh tokens de um usuário
|
||||
* @param userId ID do usuário
|
||||
* @returns Padrão para o Redis
|
||||
*/
|
||||
private buildRefreshTokenPattern(userId: number): string {
|
||||
return `auth:refresh_tokens:${userId}:*`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user