/** * CSRF Manager * * Handles CSRF token management including automatic refresh. */ import { Logger } from '../../core/logger.js'; /** * CsrfManager - CSRF token management */ export class CsrfManager { constructor(config = {}) { this.config = { tokenName: config.tokenName || '_token', headerName: config.headerName || 'X-CSRF-TOKEN', refreshInterval: config.refreshInterval || 30 * 60 * 1000, // 30 minutes autoRefresh: config.autoRefresh ?? true, endpoint: config.endpoint || '/api/csrf-token', ...config }; this.currentToken = null; this.refreshTimer = null; this.isRefreshing = false; // Initialize this.init(); } /** * Create a new CsrfManager instance */ static create(config = {}) { return new CsrfManager(config); } /** * Initialize CSRF manager */ init() { // Get initial token from meta tag or form this.currentToken = this.getTokenFromPage(); if (!this.currentToken) { Logger.warn('[CsrfManager] No CSRF token found on page'); } else { Logger.info('[CsrfManager] Initialized with token'); } // Set up auto-refresh if enabled if (this.config.autoRefresh) { this.startAutoRefresh(); } // Update all forms and meta tags this.updateAllTokens(); } /** * Get token from page (meta tag or form) */ getTokenFromPage() { // Try meta tag first const metaTag = document.querySelector('meta[name="csrf-token"]'); if (metaTag) { return metaTag.getAttribute('content'); } // Try form input const formInput = document.querySelector(`input[name="${this.config.tokenName}"]`); if (formInput) { return formInput.value; } return null; } /** * Get current CSRF token */ getToken() { return this.currentToken; } /** * Refresh CSRF token */ async refreshToken() { if (this.isRefreshing) { Logger.debug('[CsrfManager] Token refresh already in progress'); return; } this.isRefreshing = true; try { const response = await fetch(this.config.endpoint, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json' } }); if (!response.ok) { throw new Error(`Failed to refresh token: ${response.status}`); } const data = await response.json(); const newToken = data.token || data.csrf_token || data._token; if (!newToken) { throw new Error('No token in response'); } this.currentToken = newToken; this.updateAllTokens(); Logger.info('[CsrfManager] Token refreshed'); // Trigger event this.triggerTokenRefreshedEvent(newToken); } catch (error) { Logger.error('[CsrfManager] Failed to refresh token', error); throw error; } finally { this.isRefreshing = false; } } /** * Update all tokens on the page */ updateAllTokens() { if (!this.currentToken) { return; } // Update meta tag const metaTag = document.querySelector('meta[name="csrf-token"]'); if (metaTag) { metaTag.setAttribute('content', this.currentToken); } // Update all form inputs const formInputs = document.querySelectorAll(`input[name="${this.config.tokenName}"]`); formInputs.forEach(input => { input.value = this.currentToken; }); // Update LiveComponent tokens const liveComponents = document.querySelectorAll('[data-live-component]'); liveComponents.forEach(element => { const tokenInput = element.querySelector(`input[name="${this.config.tokenName}"]`); if (tokenInput) { tokenInput.value = this.currentToken; } }); } /** * Start auto-refresh timer */ startAutoRefresh() { if (this.refreshTimer) { return; } this.refreshTimer = setInterval(() => { this.refreshToken().catch(error => { Logger.error('[CsrfManager] Auto-refresh failed', error); }); }, this.config.refreshInterval); Logger.debug('[CsrfManager] Auto-refresh started', { interval: this.config.refreshInterval }); } /** * Stop auto-refresh timer */ stopAutoRefresh() { if (this.refreshTimer) { clearInterval(this.refreshTimer); this.refreshTimer = null; } } /** * Get token for use in fetch requests */ getTokenHeader() { return { [this.config.headerName]: this.currentToken }; } /** * Trigger token refreshed event */ triggerTokenRefreshedEvent(token) { const event = new CustomEvent('csrf:token-refreshed', { detail: { token }, bubbles: true }); window.dispatchEvent(event); } /** * Destroy CSRF manager */ destroy() { this.stopAutoRefresh(); this.currentToken = null; Logger.info('[CsrfManager] Destroyed'); } }