/**
* CSRF Protection — Client Module
*
* Provides helpers for the Double-Submit Cookie pattern.
* Auto-discovered by the framework via import.meta.glob.
*/
const COOKIE_NAME = 'XSRF-TOKEN'
const HEADER_NAME = 'X-CSRF-Token'
/** Token injetado pelo SSR no (se houver). */
function getMetaToken(): string | undefined {
if (typeof document === 'undefined') return undefined
const meta = document.querySelector('meta[name="csrf-token"]')
return meta?.getAttribute('content') ?? undefined
}
function getCookieToken(): string | undefined {
if (typeof document === 'undefined') return undefined
const match = document.cookie.split('; ').find(c => c.startsWith(`${COOKIE_NAME}=`))
return match ? match.slice(COOKIE_NAME.length + 1) : undefined
}
/** Lê o token CSRF: cookie primeiro, depois o injetado pelo SSR. */
export function getToken(): string | undefined {
return getCookieToken() ?? getMetaToken()
}
/** Build headers object with the CSRF token (if available). */
export function getHeaders(): Record {
const token = getToken()
return token ? { [HEADER_NAME]: token } : {}
}
/** Ensure the CSRF cookie exists. Fetches a token if missing (e sem meta). */
export async function ensureToken(): Promise {
if (getToken()) return
await fetch('/api/__csrf', { credentials: 'include' })
}
// Auto-init: só faz fetch se NÃO houver token nem no cookie nem no do SSR.
// Com SSR injetando o token, esse round-trip é eliminado.
if (typeof document !== 'undefined') {
if (!getToken()) {
fetch('/api/__csrf', { credentials: 'include' })
}
}