/** * Utilitários JWT com verificação de expiração padronizada * Clock skew tolerance de 60 segundos para compensar diferenças de tempo */ interface JWTPayload { exp?: number iat?: number [key: string]: any } const CLOCK_SKEW_SECONDS = 60 /** * Parse JWT token payload sem validação de assinatura * @param token JWT token * @returns Payload decodificado ou null se inválido */ export function parseJwt(token: string): JWTPayload | null { try { const base64Url = token.split('.')[1] if (!base64Url) return null const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') const jsonPayload = decodeURIComponent( atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('') ) return JSON.parse(jsonPayload) } catch (error) { console.warn('[JWT] Erro ao fazer parse do token:', error) return null } } /** * Verifica se token está expirado com tolerância de clock skew * @param token JWT token ou timestamp de expiração em segundos * @returns true se expirado */ export function isExpired(token: string | number): boolean { try { let expTimestamp: number if (typeof token === 'string') { const payload = parseJwt(token) if (!payload?.exp) { console.warn('[JWT] Token sem claim exp, considerando válido') return false } expTimestamp = payload.exp } else { expTimestamp = token } const nowSeconds = Math.floor(Date.now() / 1000) const isExpiredValue = nowSeconds >= (expTimestamp + CLOCK_SKEW_SECONDS) console.log('[JWT] Verificação de expiração:', { nowSeconds, expTimestamp, clockSkew: CLOCK_SKEW_SECONDS, isExpired: isExpiredValue, timeUntilExpiry: expTimestamp - nowSeconds }) return isExpiredValue } catch (error) { console.warn('[JWT] Erro na verificação de expiração:', error) return true // Assumir expirado em caso de erro } } /** * Verifica se token deve ser renovado (expira em menos de 5 minutos) * @param token JWT token ou timestamp de expiração em segundos * @returns true se deve renovar */ export function shouldRefresh(token: string | number): boolean { try { let expTimestamp: number if (typeof token === 'string') { const payload = parseJwt(token) if (!payload?.exp) return false expTimestamp = payload.exp } else { expTimestamp = token } const nowSeconds = Math.floor(Date.now() / 1000) const refreshThreshold = 5 * 60 // 5 minutos const shouldRefreshValue = nowSeconds >= (expTimestamp - refreshThreshold) console.log('[JWT] Verificação de renovação:', { nowSeconds, expTimestamp, refreshThreshold, shouldRefresh: shouldRefreshValue, timeUntilRefresh: expTimestamp - refreshThreshold - nowSeconds }) return shouldRefreshValue } catch (error) { console.warn('[JWT] Erro na verificação de renovação:', error) return false } } /** * Extrai informações úteis do token * @param token JWT token * @returns Informações do token ou null */ export function getTokenInfo(token: string): { payload: JWTPayload isExpired: boolean shouldRefresh: boolean expiresAt: Date | null } | null { const payload = parseJwt(token) if (!payload) return null return { payload, isExpired: isExpired(token), shouldRefresh: shouldRefresh(token), expiresAt: payload.exp ? new Date(payload.exp * 1000) : null } }