154 lines
3.2 KiB
TypeScript
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;
|
|
}
|
|
}
|