/** * Módulo de geração e validação de tokens JWT * Access tokens (15min) e Refresh tokens (7 dias) */ import jwt from "jsonwebtoken"; interface TokenPayload { userId: number; username: string; type: "access" | "refresh"; tierSlug?: string; tierLevel?: number; } /** * Obtém o secret do JWT (lazy loading) */ function getJWTSecret(): string { const secret = process.env.JWT_SECRET; if (!secret) { throw new Error("JWT_SECRET não está definido nas variáveis de ambiente"); } return secret; } /** * Obtém o refresh secret do JWT (lazy loading) */ function getJWTRefreshSecret(): string { const secret = process.env.JWT_REFRESH_SECRET; if (!secret) { throw new Error( "JWT_REFRESH_SECRET não está definido nas variáveis de ambiente", ); } return secret; } // Tempos de expiração const ACCESS_TOKEN_EXPIRY = "1h"; // 1 hora const REFRESH_TOKEN_EXPIRY = "7d"; // 7 dias /** * Gera access token JWT * @param userId - ID do usuário * @param username - Nome de usuário * @returns Access token */ interface TokenOptions { tierSlug?: string | null; tierLevel?: number | null; } export function generateAccessToken( userId: number, username: string, options?: TokenOptions, ): string { const payload: TokenPayload = { userId, username, type: "access", tierSlug: options?.tierSlug ?? undefined, tierLevel: options?.tierLevel ?? undefined, }; return jwt.sign(payload, getJWTSecret(), { expiresIn: ACCESS_TOKEN_EXPIRY, }); } /** * Gera refresh token JWT * @param userId - ID do usuário * @param username - Nome de usuário * @returns Refresh token */ export function generateRefreshToken( userId: number, username: string, options?: TokenOptions, ): string { const payload: TokenPayload = { userId, username, type: "refresh", tierSlug: options?.tierSlug ?? undefined, tierLevel: options?.tierLevel ?? undefined, }; return jwt.sign(payload, getJWTRefreshSecret(), { expiresIn: REFRESH_TOKEN_EXPIRY, }); } /** * Valida e decodifica access token * @param token - Access token * @returns Payload do token ou null se inválido */ export function verifyAccessToken(token: string): { userId: number; username: string; tierSlug?: string; tierLevel?: number; } | null { try { const decoded = jwt.verify(token, getJWTSecret()) as TokenPayload; if (decoded.type !== "access") { return null; } return { userId: decoded.userId, username: decoded.username, tierSlug: decoded.tierSlug, tierLevel: decoded.tierLevel, }; } catch (_error) { return null; } } /** * Valida e decodifica refresh token * @param token - Refresh token * @returns Payload do token ou null se inválido */ export function verifyRefreshToken(token: string): { userId: number; username: string; tierSlug?: string; tierLevel?: number; } | null { try { const decoded = jwt.verify(token, getJWTRefreshSecret()) as TokenPayload; if (decoded.type !== "refresh") { return null; } return { userId: decoded.userId, username: decoded.username, tierSlug: decoded.tierSlug, tierLevel: decoded.tierLevel, }; } catch (_error) { return null; } }