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) {