From aebecd75bde9db6538ab2e36c71662bda1bb6569 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sat, 3 Oct 2026 22:11:19 +0200 Subject: [PATCH] =?UTF-8?q?Acc=C3=A8s=20refus=C3=A9=20:=20l'interface=20di?= =?UTF-8?q?t=20pourquoi=20et=20comment=20le=20lever?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depuis la protection anti-site-tiers (0.14.0), un accès par nom de domaine derrière un reverse proxy, sans clé de pilotage, reçoit un 403 sur toute l'API. L'interface restait vide : chaque appel échouait en silence dans la console. - Au premier 403, un bandeau fixe affiche la raison donnée par le serveur et le correctif : LOKI_TRUSTED_HOSTS= dans les variables du conteneur, ou une clé de pilotage. - docker-compose.yml et docker-compose.unraid.yml exposent la variable LOKI_TRUSTED_HOSTS, commentée. Co-Authored-By: Claude Opus 5.5 --- docker-compose.unraid.yml | 5 +++++ docker-compose.yml | 3 +++ internal/loki/ui/index.html | 23 +++++++++++++++++++++++ internal/loki/ui/src/js/04-api.js | 23 +++++++++++++++++++++++ 4 files changed, 54 insertions(+) diff --git a/docker-compose.unraid.yml b/docker-compose.unraid.yml index 8b8884e..4163fc9 100644 --- a/docker-compose.unraid.yml +++ b/docker-compose.unraid.yml @@ -34,6 +34,11 @@ services: # lancement). Ex. LOKI_MODEL=Qwen3-14B-Q4_K_M.gguf après dépôt du # fichier dans /mnt/user/appdata/loki/models. - LOKI_MODEL= + # Accès par NOM DE DOMAINE (reverse proxy, ex. loki.mondomaine.fr) sans + # clé de pilotage : liste ici le(s) nom(s), séparés par des virgules — + # sinon l'API répond 403 (protection contre les sites tiers / DNS + # rebinding). Inutile pour un accès par IP ou nom local. + - LOKI_TRUSTED_HOSTS= volumes: # /data : config, base, mémoire, workspace, modèles téléchargés par l'UI - /mnt/user/appdata/loki/data:/data diff --git a/docker-compose.yml b/docker-compose.yml index 8aab18d..c6fce55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,9 @@ services: - LOKI_MODEL=${LOKI_MODEL:-} - LOKI_CTX=${LOKI_CTX:-} - LOKI_NGL=${LOKI_NGL:-} + # Accès par nom de domaine (reverse proxy) sans clé de pilotage : nom(s) + # autorisé(s), séparés par des virgules — sinon l'API répond 403. + - LOKI_TRUSTED_HOSTS=${LOKI_TRUSTED_HOSTS:-} volumes: # /data = LOKI_HOME : config, base bbolt, mémoire, workspace, modèles téléchargés - ./data:/data diff --git a/internal/loki/ui/index.html b/internal/loki/ui/index.html index 62c8aa1..b3650f4 100644 --- a/internal/loki/ui/index.html +++ b/internal/loki/ui/index.html @@ -3919,8 +3919,31 @@ async function jfetch(u, opts){ const k = await askKeyOnce(); if(k){ opts.headers = authHeaders(opts.headers); r = await fetch(u, opts); } } + // 403 de la garde anti-site-tiers (web_auth.go, crossSiteReject) : sans ce + // bandeau, l'interface restait vide sans dire pourquoi — chaque appel échouait + // en silence dans la console. + if(r.status === 403 && !accessBlockedShown){ + r.clone().json().then(j=>{ if(j && j.error) showAccessBlocked(j.error); }).catch(()=>{}); + } return r; } + +// showAccessBlocked : bandeau fixe expliquant le refus d'accès et comment le +// lever (nom de domaine derrière un reverse proxy, sans clé de pilotage). +let accessBlockedShown = false; +function showAccessBlocked(msg){ + if(accessBlockedShown) return; + accessBlockedShown = true; + const d = document.createElement('div'); + d.id = 'access-blocked'; + d.setAttribute('role', 'alert'); + d.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;padding:12px 16px;background:var(--err,#b3564d);color:#fff;font-size:13px;line-height:1.5'; + const host = location.hostname; + d.innerHTML = 'Accès refusé par Loki — ' + escHtml(msg) + + '
Derrière un reverse proxy : ajoute LOKI_TRUSTED_HOSTS=' + escHtml(host) + + ' aux variables du conteneur puis recrée-le, ou définis une clé de pilotage (docker exec -it loki loki set-web-key).'; + document.body.appendChild(d); +} // askKeyOnce : UNE seule demande de clé à la fois, partagée par tous les appels. // // Le chargement de l'UI lance une dizaine d'appels /api/* en parallèle, et le diff --git a/internal/loki/ui/src/js/04-api.js b/internal/loki/ui/src/js/04-api.js index da2b611..d7cbdd7 100644 --- a/internal/loki/ui/src/js/04-api.js +++ b/internal/loki/ui/src/js/04-api.js @@ -33,8 +33,31 @@ async function jfetch(u, opts){ const k = await askKeyOnce(); if(k){ opts.headers = authHeaders(opts.headers); r = await fetch(u, opts); } } + // 403 de la garde anti-site-tiers (web_auth.go, crossSiteReject) : sans ce + // bandeau, l'interface restait vide sans dire pourquoi — chaque appel échouait + // en silence dans la console. + if(r.status === 403 && !accessBlockedShown){ + r.clone().json().then(j=>{ if(j && j.error) showAccessBlocked(j.error); }).catch(()=>{}); + } return r; } + +// showAccessBlocked : bandeau fixe expliquant le refus d'accès et comment le +// lever (nom de domaine derrière un reverse proxy, sans clé de pilotage). +let accessBlockedShown = false; +function showAccessBlocked(msg){ + if(accessBlockedShown) return; + accessBlockedShown = true; + const d = document.createElement('div'); + d.id = 'access-blocked'; + d.setAttribute('role', 'alert'); + d.style.cssText = 'position:fixed;top:0;left:0;right:0;z-index:9999;padding:12px 16px;background:var(--err,#b3564d);color:#fff;font-size:13px;line-height:1.5'; + const host = location.hostname; + d.innerHTML = 'Accès refusé par Loki — ' + escHtml(msg) + + '
Derrière un reverse proxy : ajoute LOKI_TRUSTED_HOSTS=' + escHtml(host) + + ' aux variables du conteneur puis recrée-le, ou définis une clé de pilotage (docker exec -it loki loki set-web-key).'; + document.body.appendChild(d); +} // askKeyOnce : UNE seule demande de clé à la fois, partagée par tous les appels. // // Le chargement de l'UI lance une dizaine d'appels /api/* en parallèle, et le