mirror of
https://github.com/andrey271192/kaskad.git
synced 2026-09-20 13:49:56 +00:00
fix(webui): cookie session login/logout (replace Basic Auth)
Browsers cannot clear HTTP Basic credentials; logout never worked reliably. Use Flask signed session + /login form + GET /logout + POST /api/logout. Optional KASKAD_SECRET_KEY or auto /etc/kaskad/.session_secret. Fetch API uses credentials: same-origin; 401 redirects to /login. Docs + README updated. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -246,9 +246,15 @@
|
||||
this.loading = true; this.error = '';
|
||||
try {
|
||||
const r = await fetch(url, {
|
||||
method, headers: {'Content-Type':'application/json'},
|
||||
method,
|
||||
credentials: 'same-origin',
|
||||
headers: {'Content-Type':'application/json'},
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
if (r.status === 401) {
|
||||
window.location.href = '/login?next=' + encodeURIComponent(window.location.pathname || '/');
|
||||
throw new Error('требуется вход');
|
||||
}
|
||||
const j = await r.json().catch(()=>({}));
|
||||
if (!r.ok) throw new Error(j.error || r.statusText);
|
||||
return j;
|
||||
@@ -278,33 +284,15 @@
|
||||
});
|
||||
} catch { return; }
|
||||
this.settingsOpen = false;
|
||||
this.flash('Пароль изменён. Сейчас браузер попросит новый.');
|
||||
setTimeout(() => this.logout(), 1200);
|
||||
this.flash('Пароль изменён');
|
||||
this.forms.pwd = {current:'', new:'', confirm:''};
|
||||
},
|
||||
|
||||
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();
|
||||
async logout() {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
await fetch('/api/logout', { method: 'POST', credentials: 'same-origin' });
|
||||
} catch {}
|
||||
window.location.href = '/login';
|
||||
},
|
||||
|
||||
fmtAge(s) {
|
||||
|
||||
28
webui/templates/login.html
Normal file
28
webui/templates/login.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Вход — Kaskad</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<div class="login-box">
|
||||
<h1>Каскад</h1>
|
||||
<p class="muted">Вход в панель управления</p>
|
||||
{% if error %}
|
||||
<p class="status err" style="margin:12px 0">{{ error }}</p>
|
||||
{% endif %}
|
||||
<form method="post" action="{{ url_for('login') }}">
|
||||
<input type="hidden" name="next" value="{{ next_url }}">
|
||||
<label>Логин
|
||||
<input name="username" autocomplete="username" required autofocus>
|
||||
</label>
|
||||
<label>Пароль
|
||||
<input type="password" name="password" autocomplete="current-password" required>
|
||||
</label>
|
||||
<button type="submit" class="danger solid">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user