From b8757548a25b1dfc72a5f52e8ef9fb3e686d9690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Tue, 12 May 2026 20:00:18 +0300 Subject: [PATCH] fix(webui): logout via XHR wrong Basic creds (Chrome-safe) fetch()+manual Authorization is ignored when browser has cached HTTP auth. Use XMLHttpRequest.open(user,password) with random bogus pair, then location.replace('/') so login prompts again. Add Cache-Control on 401. Header buttons: type=button, @click.prevent on logout. Co-authored-by: Cursor --- webui/app.py | 14 +++++++++++--- webui/templates/index.html | 39 ++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/webui/app.py b/webui/app.py index 945b777..27dabc2 100644 --- a/webui/app.py +++ b/webui/app.py @@ -594,10 +594,18 @@ def api_change_password(): @app.route("/api/logout") def api_logout(): - """Возвращаем 401 с новым realm — браузер сбрасывает кеш Basic Auth.""" + """401 + смена realm — подсказка браузеру забыть предыдущий Basic Auth. + + Клиент должен дергать этот URL через XMLHttpRequest.open(..., user, password) + с заведомо неверной парой; иначе многие браузеры подставят сохранённые креды.""" return Response( - "logged out", 401, - {"WWW-Authenticate": f'Basic realm="kaskad-logout-{os.urandom(4).hex()}"'}, + "logged out\n", + 401, + { + "WWW-Authenticate": f'Basic realm="kaskad-logout-{os.urandom(4).hex()}"', + "Cache-Control": "no-store, no-cache, must-revalidate", + "Pragma": "no-cache", + }, ) diff --git a/webui/templates/index.html b/webui/templates/index.html index b602b4b..45711bd 100644 --- a/webui/templates/index.html +++ b/webui/templates/index.html @@ -10,13 +10,13 @@

Каскад

- + загружаю…
- - + +
@@ -282,18 +282,29 @@ setTimeout(() => this.logout(), 1200); }, - async logout() { - // 1) бьём бэк бракованными credentials — он отвечает 401 с новым realm, - // браузер сбрасывает кешированный Basic Auth + logout() { + // HTTP Basic Auth живёт в браузере отдельно от cookies. fetch() с ручным + // Authorization часто ИГНОРИРУЕТСЯ — Chrome подставляет сохранённый пароль. + // XMLHttpRequest.open(..., user, password) заставляет уйти именно эта пара + // (заведомо ложная), сервер отвечает 401 + новый realm — после этого + // location.replace('/') обычно снова показывает окно входа. + const go = () => { + const u = new URL(window.location.href); + u.pathname = '/'; + u.search = '_bye=' + Date.now(); + u.hash = ''; + window.location.replace(u.href); + }; + const noise = () => Math.random().toString(36).slice(2) + Date.now(); try { - await fetch('/api/logout', { - headers: { 'Authorization': 'Basic ' + btoa('logout:' + Math.random()) }, - cache: 'no-store', - credentials: 'omit', - }); - } catch {} - // 2) редиректим на корень с уникальным query — браузер заново спросит логин/пароль - window.location.href = '/?_=' + Date.now(); + const xhr = new XMLHttpRequest(); + xhr.timeout = 12000; + xhr.open('GET', '/api/logout?_=' + noise(), true, '__logout', noise()); + xhr.onload = xhr.onerror = xhr.ontimeout = go; + xhr.send(); + } catch (e) { + go(); + } }, fmtAge(s) {