Files
2025-11-11 03:08:36 -03:00

154 lines
3.2 KiB
TypeScript

/**
* 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;
}
}