From f5d8d38b1e2c8a6749473ccde0d7751a33836bed Mon Sep 17 00:00:00 2001 From: louispaulb Date: Fri, 3 Jul 2026 17:18:53 -0400 Subject: [PATCH] =?UTF-8?q?fix(ops):=20retry=20permission=20load=20(superu?= =?UTF-8?q?ser=20plus=20jamais=20bloqu=C3=A9=20par=20un=20=C3=A9chec=20tra?= =?UTF-8?q?nsitoire=20au=20boot)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptôme (overlay fail-loud confirmé) : « OPS a reçu louis@targo.ca mais pas de permissions », alors que le compte EST superuser (is_superuser=True, admin+sysadmin, vérifié live) et que le hub renvoie 200 (6/6, ~0,14 s). La recherche (même base /hub, appelée plus tard) marche → seul le 1er appel /hub au démarrage (checkSession) échoue = course de boot / session pas encore « chaude ». Fix : loadPermissions réessaie jusqu'à 4× (backoff 0,7→2,1 s, cache:no-store) avant de conclure « pas d'accès ». Un échec transitoire ne verrouille plus l'app derrière un menu vide / overlay. (Les droits sont dans Authentik : groupes admin/sysadmin + is_superuser, inchangés — ce n'était ni le jeton ni les droits.) Co-Authored-By: Claude Opus 4.8 --- apps/ops/src/composables/usePermissions.js | 39 ++++++++++++---------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/apps/ops/src/composables/usePermissions.js b/apps/ops/src/composables/usePermissions.js index 847f49a..22de974 100644 --- a/apps/ops/src/composables/usePermissions.js +++ b/apps/ops/src/composables/usePermissions.js @@ -29,26 +29,31 @@ async function loadPermissions (email, username) { const qs = new URLSearchParams() if (email) qs.set('email', email) if (username) qs.set('username', username) - fetchPromise = fetch(`${HUB_URL}/auth/permissions?${qs.toString()}`) - .then(r => { - if (!r.ok) throw new Error('Permission fetch failed: ' + r.status) - return r.json() - }) - .then(data => { + const url = `${HUB_URL}/auth/permissions?${qs.toString()}` + const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' + + // RÉSILIENCE : le tout 1er appel /hub au démarrage échoue parfois (session pas encore « chaude » / course au boot) + // alors que les appels /hub suivants (ex. recherche) passent. On RÉESSAIE (jusqu'à 4 tentatives, backoff 0,7→2,1 s) + // avant de conclure « pas d'accès » → un superuser n'est plus bloqué par un échec transitoire (le vrai bug observé). + async function attempt (n) { + try { + const r = await fetch(url, { cache: 'no-store' }) + if (!r.ok) throw new Error('HTTP ' + r.status) + const data = await r.json() permissions.value = data - }) - .catch(e => { - console.error('[usePermissions]', e.message) + error.value = null + } catch (e) { + if (n < 4) { await new Promise(res => setTimeout(res, 700 * n)); return attempt(n + 1) } + console.error('[usePermissions] load failed after retries:', e.message) error.value = e.message - // DEV local (pas de hub devant) : le fetch échoue → on débloque l'app avec un profil permissif - // pour pouvoir prévisualiser/auditer toutes les pages. En PROD on reste null (= restrictif/sûr). - const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' + // DEV local (pas de hub devant) : profil permissif pour prévisualiser/auditer ; PROD → null (App.vue « fail-loud »). permissions.value = isLocal ? { email, is_superuser: true, capabilities: {}, groups: ['admin'] } : null - }) - .finally(() => { - loading.value = false - fetchPromise = null - }) + } + } + fetchPromise = attempt(1).finally(() => { + loading.value = false + fetchPromise = null + }) return fetchPromise }