2025-09-28 04:10:40 -03:00

133 lines
3.5 KiB
TypeScript

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