mirror of
https://github.com/andrey271192/kaskad.git
synced 2026-09-20 13:49:56 +00:00
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 <cursoragent@cursor.com>
This commit is contained in:
14
webui/app.py
14
webui/app.py
@@ -594,10 +594,18 @@ def api_change_password():
|
|||||||
|
|
||||||
@app.route("/api/logout")
|
@app.route("/api/logout")
|
||||||
def api_logout():
|
def api_logout():
|
||||||
"""Возвращаем 401 с новым realm — браузер сбрасывает кеш Basic Auth."""
|
"""401 + смена realm — подсказка браузеру забыть предыдущий Basic Auth.
|
||||||
|
|
||||||
|
Клиент должен дергать этот URL через XMLHttpRequest.open(..., user, password)
|
||||||
|
с заведомо неверной парой; иначе многие браузеры подставят сохранённые креды."""
|
||||||
return Response(
|
return Response(
|
||||||
"logged out", 401,
|
"logged out\n",
|
||||||
{"WWW-Authenticate": f'Basic realm="kaskad-logout-{os.urandom(4).hex()}"'},
|
401,
|
||||||
|
{
|
||||||
|
"WWW-Authenticate": f'Basic realm="kaskad-logout-{os.urandom(4).hex()}"',
|
||||||
|
"Cache-Control": "no-store, no-cache, must-revalidate",
|
||||||
|
"Pragma": "no-cache",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
<body x-data="kaskad()" x-init="load()">
|
<body x-data="kaskad()" x-init="load()">
|
||||||
<header>
|
<header>
|
||||||
<h1>Каскад</h1>
|
<h1>Каскад</h1>
|
||||||
<button @click="load()" :disabled="loading">⟳ Обновить</button>
|
<button type="button" @click="load()" :disabled="loading">⟳ Обновить</button>
|
||||||
<span class="status" x-show="loading">загружаю…</span>
|
<span class="status" x-show="loading">загружаю…</span>
|
||||||
<span class="status err" x-show="error" x-text="error"></span>
|
<span class="status err" x-show="error" x-text="error"></span>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<span class="muted" x-show="user" x-text="'👤 ' + user"></span>
|
<span class="muted" x-show="user" x-text="'👤 ' + user"></span>
|
||||||
<button @click="openSettings()" title="сменить пароль">🔑 Пароль</button>
|
<button type="button" @click="openSettings()" title="сменить пароль">🔑 Пароль</button>
|
||||||
<button class="danger" @click="logout()" title="выйти из WebUI">↪ Выход</button>
|
<button type="button" class="danger" @click.prevent="logout()" title="выйти из WebUI">↪ Выход</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- Settings modal -->
|
<!-- Settings modal -->
|
||||||
@@ -282,18 +282,29 @@
|
|||||||
setTimeout(() => this.logout(), 1200);
|
setTimeout(() => this.logout(), 1200);
|
||||||
},
|
},
|
||||||
|
|
||||||
async logout() {
|
logout() {
|
||||||
// 1) бьём бэк бракованными credentials — он отвечает 401 с новым realm,
|
// HTTP Basic Auth живёт в браузере отдельно от cookies. fetch() с ручным
|
||||||
// браузер сбрасывает кешированный Basic Auth
|
// 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 {
|
try {
|
||||||
await fetch('/api/logout', {
|
const xhr = new XMLHttpRequest();
|
||||||
headers: { 'Authorization': 'Basic ' + btoa('logout:' + Math.random()) },
|
xhr.timeout = 12000;
|
||||||
cache: 'no-store',
|
xhr.open('GET', '/api/logout?_=' + noise(), true, '__logout', noise());
|
||||||
credentials: 'omit',
|
xhr.onload = xhr.onerror = xhr.ontimeout = go;
|
||||||
});
|
xhr.send();
|
||||||
} catch {}
|
} catch (e) {
|
||||||
// 2) редиректим на корень с уникальным query — браузер заново спросит логин/пароль
|
go();
|
||||||
window.location.href = '/?_=' + Date.now();
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fmtAge(s) {
|
fmtAge(s) {
|
||||||
|
|||||||
Reference in New Issue
Block a user