import { createError, type H3Event } from 'h3' export interface AuthRuntimeConfig { issuer: string audience: string accessTtlSec: number refreshTtlSec: number passwordResetTtlSec: number privateKeyPem: string publicKeyPem: string kid: string refreshTokenPepper: string passwordResetTokenPepper: string passwordResetBaseUrl: string } /** * Converte uma string para inteiro positivo e valida o resultado. * * @param value Valor recebido do runtime config. * @param label Nome amigável do campo para mensagens de erro. * @returns Número inteiro positivo. * @throws {H3Error} Quando o valor não for um inteiro positivo. */ function parsePositiveInt(value: string, label: string): number { const parsed = Number.parseInt(value, 10) if (!Number.isFinite(parsed) || parsed <= 0) { throw createError({ statusCode: 500, statusMessage: `${label} must be a positive integer` }) } return parsed } /** * Normaliza uma chave PEM salva em variável de ambiente. * Troca `\\n` por quebra de linha real e remove espaços nas pontas. * * @param rawValue Valor bruto vindo do ambiente. * @param label Nome amigável do campo para mensagens de erro. * @returns Chave PEM pronta para uso. * @throws {H3Error} Quando a chave estiver vazia. */ function normalizePem(rawValue: string, label: string): string { const normalized = rawValue.replace(/\\n/g, '\n').trim() if (!normalized) { throw createError({ statusCode: 500, statusMessage: `${label} is required` }) } return normalized } /** * Lê e valida todas as configurações obrigatórias de autenticação. * * @param event Evento da requisição (opcional), usado para acessar runtime config. * @returns Objeto com configurações de JWT, refresh token e recuperação de senha já validadas. * @throws {H3Error} Quando algum campo obrigatório estiver ausente ou inválido. */ export function getAuthRuntimeConfig(event?: H3Event): AuthRuntimeConfig { const runtimeConfig = useRuntimeConfig(event) const issuer = String(runtimeConfig.jwtIssuer ?? '').trim() const audience = String(runtimeConfig.jwtAudience ?? '').trim() const kid = String(runtimeConfig.jwtKid ?? '').trim() if (!issuer) { throw createError({ statusCode: 500, statusMessage: 'JWT issuer is required' }) } if (!audience) { throw createError({ statusCode: 500, statusMessage: 'JWT audience is required' }) } if (!kid) { throw createError({ statusCode: 500, statusMessage: 'JWT kid is required' }) } const refreshTokenPepper = String(runtimeConfig.refreshTokenPepper ?? '').trim() const passwordResetTokenPepper = String(runtimeConfig.passwordResetTokenPepper ?? '').trim() || refreshTokenPepper const passwordResetBaseUrl = String(runtimeConfig.passwordResetBaseUrl ?? '').trim() return { issuer, audience, kid, accessTtlSec: parsePositiveInt(String(runtimeConfig.jwtAccessTtlSec), 'JWT access TTL'), refreshTtlSec: parsePositiveInt(String(runtimeConfig.jwtRefreshTtlSec), 'JWT refresh TTL'), passwordResetTtlSec: parsePositiveInt( String(runtimeConfig.passwordResetTtlSec), 'Password reset TTL' ), privateKeyPem: normalizePem(String(runtimeConfig.jwtPrivateKeyPem ?? ''), 'JWT private key'), publicKeyPem: normalizePem(String(runtimeConfig.jwtPublicKeyPem ?? ''), 'JWT public key'), refreshTokenPepper, passwordResetTokenPepper, passwordResetBaseUrl } }