qualora / static /js /fetch-wrapper.js
Pratham Amritkar
final
d833ce9
Raw
History Blame Contribute Delete
3.76 kB
/**
* 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.');
})();