From 5d371a2a8b6f4e83271f765d3643e4b758af742e Mon Sep 17 00:00:00 2001 From: louispaulb Date: Thu, 4 Jun 2026 12:20:01 -0400 Subject: [PATCH] =?UTF-8?q?Ops:=20authFetch=20robuste=20=E2=80=94=20reconn?= =?UTF-8?q?exion=20auto=20sur=20session=20Authentik=20expir=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avant: ne rechargeait que sur 401/403 stricts → ratait la redirection IdP / page HTML de login (vrai cas d'expiration) → données vides nécessitant un refresh manuel. Maintenant: détecte redirection auth.targo.ca + HTML-au-lieu-de-JSON → reload auto (anti-boucle 20s), + 1 retry sur coupure réseau transitoire (ex: backend qui redémarre). Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/ops/src/api/auth.js | 50 ++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/apps/ops/src/api/auth.js b/apps/ops/src/api/auth.js index d202d40..33c797b 100644 --- a/apps/ops/src/api/auth.js +++ b/apps/ops/src/api/auth.js @@ -4,22 +4,42 @@ import { BASE_URL } from 'src/config/erpnext' // Only needed for local dev (VITE_ERP_TOKEN in .env). const SERVICE_TOKEN = import.meta.env.VITE_ERP_TOKEN || '' -export function authFetch (url, opts = {}) { - if (SERVICE_TOKEN) { - opts.headers = { - ...opts.headers, - Authorization: 'token ' + SERVICE_TOKEN, - } - } else { - opts.headers = { ...opts.headers } +// Reconnexion auto sur session Authentik expirée, anti-boucle (1 reload / 20 s max). +let _lastReload = 0 +function _reauth (status) { + const now = Date.now() + if (now - _lastReload > 20000) { + _lastReload = now + window.location.reload() } - return fetch(url, opts).then(res => { - if (res.status === 401 || res.status === 403) { - window.location.reload() - return new Response('{}', { status: res.status }) - } - return res - }) + return new Response('{}', { status: status || 401 }) +} + +export async function authFetch (url, opts = {}) { + opts.headers = SERVICE_TOKEN + ? { ...opts.headers, Authorization: 'token ' + SERVICE_TOKEN } + : { ...opts.headers } + + let res + try { + res = await fetch(url, opts) + } catch (e) { + // Coupure réseau transitoire (ex: backend qui redémarre) → 1 retry court avant d'abandonner + await new Promise(r => setTimeout(r, 800)) + res = await fetch(url, opts) + } + + // Détection « session Authentik expirée » (la cause du "il faut recharger") : + // - 401/403 explicites + // - réponse redirigée vers l'IdP (auth.targo.ca / /if/flow/) + // - page HTML de login renvoyée à la place du JSON attendu sur un /api/ + const ct = (res.headers && res.headers.get && res.headers.get('content-type')) || '' + const redirectedToIdp = res.redirected && /auth\.targo\.ca|\/if\/flow\//.test(res.url || '') + const htmlForApi = res.status === 200 && ct.includes('text/html') && /\/api\//.test(String(url)) + if (res.status === 401 || res.status === 403 || redirectedToIdp || htmlForApi) { + return _reauth(res.status) + } + return res } export async function getLoggedUser () {