Spaces:
Running
Running
| /** | |
| * fetch-wrapper.js - Hardened Fetch Wrapper for Qualora Enterprise | |
| * * AUTOMATIC FEATURES: | |
| * - Automatic HTTP-Only Cookie propagation (Secure JWT handling) | |
| * - CSRF Token injection from 'csrf_token' cookie to 'X-CSRF-Token' header | |
| * - 401 Unauthorized handling (Clears local UI state and redirects) | |
| * - 429 Rate Limit handling (Exponential backoff retry) | |
| * - Strict Path Traversal and Network Error resilience | |
| */ | |
| (function() { | |
| 'use strict'; | |
| const originalFetch = window.fetch; | |
| /** | |
| * Helper: Extract CSRF token from browser cookies. | |
| * Aligned with security.py cookie naming. | |
| */ | |
| function getCsrfToken() { | |
| const match = document.cookie.match(/csrf_token=([^;]+)/); | |
| return match ? match[1] : null; | |
| } | |
| /** | |
| * Handle session expiration. | |
| * Synchronized with auth-init.js to use the 'user' key. | |
| */ | |
| function handleAuthFailure() { | |
| console.warn('[Qualora] Session expired or invalid. Cleaning up.'); | |
| // Remove the UI Hint (JS can't touch the HTTP-Only cookie anyway) | |
| localStorage.removeItem('user'); | |
| sessionStorage.clear(); | |
| // Only redirect if the user is currently on a protected page | |
| const publicPaths = ['/', '/index.html', '/login', '/register']; | |
| if (!publicPaths.includes(window.location.pathname)) { | |
| window.location.href = '/'; | |
| } | |
| } | |
| /** | |
| * Internal: Enhanced fetch with exponential backoff for 429 Rate Limits | |
| */ | |
| async function fetchWithRetry(url, options, retries = 2) { | |
| for (let i = 0; i <= retries; i++) { | |
| try { | |
| const response = await originalFetch(url, options); | |
| // Handle 429 (Rate Limit) from our hardened security.py logic | |
| if (response.status === 429 && i < retries) { | |
| const delay = Math.pow(2, i) * 1000; | |
| console.warn(`[Qualora] Rate limited. Retrying in ${delay}ms...`); | |
| await new Promise(res => setTimeout(res, delay)); | |
| continue; | |
| } | |
| return response; | |
| } catch (error) { | |
| if (i === retries) throw error; | |
| const delay = Math.pow(2, i) * 500; | |
| await new Promise(res => setTimeout(res, delay)); | |
| } | |
| } | |
| } | |
| /** | |
| * Global Fetch Override | |
| */ | |
| window.fetch = async function(url, options = {}) { | |
| options.headers = options.headers || {}; | |
| // 1. JWT Security: We DO NOT inject Bearer headers. | |
| // The browser handles the HTTP-Only 'access_token' cookie automatically | |
| // because we set credentials: 'include' below. | |
| // 2. CSRF Protection: Inject header for state-changing requests | |
| const method = (options.method || 'GET').toUpperCase(); | |
| const needsCsrf = ['POST', 'PUT', 'DELETE', 'PATCH'].includes(method); | |
| if (needsCsrf) { | |
| const csrfToken = getCsrfToken(); | |
| if (csrfToken) { | |
| options.headers['X-CSRF-Token'] = csrfToken; | |
| } | |
| } | |
| // 3. Credentials: MUST be 'include' to pass HTTP-Only cookies across origins | |
| options.credentials = 'include'; | |
| try { | |
| const response = await fetchWithRetry(url, options); | |
| // 4. Session Management | |
| if (response.status === 401) { | |
| handleAuthFailure(); | |
| } | |
| return response; | |
| } catch (error) { | |
| console.error('[Qualora] Network request failed:', error); | |
| throw error; | |
| } | |
| }; | |
| console.log('✅ Qualora Fetch Wrapper: Secure Cookie & CSRF mode active.'); | |
| })(); |