feat(bezpieczeństwo): sesje logowania zamiast HTTP Basic (LOG-34)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m17s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m30s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 5s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 4s
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m18s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Successful in 4s
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m17s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m30s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 5s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 4s
build / build (push) Successful in 7s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m18s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m29s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 6s
Testy / Kontrola składni wszystkich warstw (push) Successful in 4s
Basic nie miał wylogowania i nie dało się tego obejść: przeglądarka zapamiętuje
hasło i dosyła je SAMA przy każdym żądaniu, więc serwer nie ma czego zapomnieć.
Poprzednia próba (LOG-32) opierała się na nakłonieniu przeglądarki, żeby porzuciła
zapamiętane dane — zachowaniu powszechnym, ale nigdzie nie zapisanym. Teraz to
serwer decyduje, czy dana przeglądarka jest w środku, i może to cofnąć.
TRZY POZIOMY UNIEWAŻNIENIA, celowo rozdzielone, bo każdy kosztuje co innego:
1. wylogowanie = skasowanie ciasteczka. Natychmiastowe, bez magazynu.
2. zmiana hasła albo skasowanie konta = odcisk poświadczenia wpisany
w ciasteczko przestaje pasować. Dzieje się SAMO, bez pamiętania o tym.
Bez tego odebranie komuś dostępu nie odbierałoby dostępu aż do wygaśnięcia.
3. „zamknij sesje" z ekranu kont = licznik pokolenia. Jedyny wymagający zapisu,
więc jedyny opcjonalny: gdy licznika nie ma, poziomy 1 i 2 nadal działają.
KONTO ADMINISTRACYJNE ODSEPAROWANE. Sprawdzane pierwsze i BEZ DOTYKANIA pliku
kont, co daje dwie rzeczy naraz: konto z pliku o tym samym loginie nie przesłoni
administratora, a administrator zaloguje się także wtedy, gdy plik jest uszkodzony
— czyli w jedynej sytuacji, w której ktoś MUSI wejść, żeby to naprawić. Trzymanie
jego stanu w tym samym pliku dawałoby zakleszczenie: nie da się naprawić, bo nie
da się wejść. Jego odpowiednikiem „wyloguj zewsząd" jest zmiana APP_PASSWORD.
KLUCZ WYMAGANY, FAIL-CLOSED. Usługa z kontami, ale bez klucza podpisu, nie
odróżniłaby ważnej sesji od podrobionej, więc nie wstaje — i lepiej przy starcie
niż przy pierwszym logowaniu człowieka. Losowanie klucza byłoby wygodne, ale
wylogowywałoby wszystkich przy każdym restarcie poda: wygląda jak awaria i uczy
ludzi ignorować ekran logowania.
Ciasteczko HttpOnly (jeden wstrzyknięty skrypt inaczej wynosi sesję) i
SameSite=Strict (obca strona nie zadziała w imieniu zalogowanego). Wylogowanie
POST-em, nie odsyłaczem: pod adresem GET wystarczyłby obrazek na obcej stronie.
Adres powrotu po zalogowaniu przechodzi przez sito — bez tego `?dokad=https://obcy`
zamieniłby nasz ekran logowania w narzędzie do wyłudzania haseł.
Kres bezczynności 8 h i twardy 30 dni. Znacznik aktywności odświeżany z progiem,
inaczej Set-Cookie leciałby przy każdym obrazku i arkuszu stylów.
Zdarzenia logowania w dzienniku (PRE-17). Nieudane próby są tam ważniejsze od
udanych: pojedyncza nic nie znaczy, ale seria pod jednym adresem to jedyny
widoczny ślad zgadywania haseł. Test pilnuje, że hasło tam nie trafia.
Sprawdzone w przeglądarce: po zalogowaniu ciasteczko jest NIEWIDOCZNE dla
JavaScriptu, a po wylogowaniu wejście na chronioną stronę ląduje na ekranie
logowania — czyli dokładnie to, czego Basic nie potrafił.
Testy: 21 na rdzeń podpisywania (w tym podrabianie ładunku, podpisu i klucza,
ciasteczko z przyszłości, śmieci na wejściu), reszta przepisana z Basic na sesje.
Prezentacja 338 zielonych.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit was merged in pull request #76.
This commit is contained in:
@@ -271,5 +271,17 @@ tr.row-held td { background: rgba(184, 134, 11, .07); }
|
||||
rozpychać. */
|
||||
.whoami { color: var(--muted); font-size: .85rem; align-self: center;
|
||||
margin-left: 1rem; padding-left: 1rem; border-left: 1px solid var(--line); }
|
||||
nav a.wyloguj { color: var(--muted); }
|
||||
nav a.wyloguj:hover { color: var(--fg, inherit); }
|
||||
/* Wylogowanie idzie POST-em, więc jest przyciskiem — ale ma wyglądać jak
|
||||
reszta paska, nie jak przycisk akcji w formularzu. */
|
||||
.wyloguj-form { display: inline; margin: 0; padding: 0; background: none; border: none; }
|
||||
button.wyloguj {
|
||||
background: none; border: 1px solid transparent; color: var(--muted);
|
||||
font: inherit; padding: .4rem .8rem; border-radius: 8px; cursor: pointer;
|
||||
}
|
||||
button.wyloguj:hover { color: var(--ink); }
|
||||
|
||||
/* Ekran logowania — wąski, bez nawigacji: nie ma dokąd iść przed zalogowaniem. */
|
||||
.login-main { max-width: 26rem; }
|
||||
.login-card label { display: block; margin-top: .75rem; }
|
||||
.login-card input { width: 100%; }
|
||||
.login-note { margin-top: 1.25rem; }
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// Wylogowanie z HTTP Basic (LOG-32).
|
||||
//
|
||||
// PROBLEM, KTÓREGO NIE DA SIĘ ROZWIĄZAĆ PO STRONIE SERWERA. Basic nie ma sesji:
|
||||
// przeglądarka zapamiętuje login i hasło, po czym dosyła je SAMA przy każdym
|
||||
// żądaniu. Serwer nie ma czego „zapomnieć" — następne kliknięcie przyszłoby
|
||||
// z kompletem poświadczeń i weszłoby z powrotem.
|
||||
//
|
||||
// Działa natomiast doprowadzenie do tego, żeby PRZEGLĄDARKA porzuciła to, co
|
||||
// zapamiętała: żądanie z celowo błędnymi danymi, którym nadpisuje ona swój wpis
|
||||
// dla tej witryny. To jest zachowanie powszechne w przeglądarkach, ale NIE
|
||||
// ZAPISANE w żadnym standardzie — dlatego jest tu jako uzupełnienie, a nie
|
||||
// jedyny mechanizm: sama trasa /wyloguj odpowiada 401 i wymusza okienko
|
||||
// logowania nawet przy wyłączonym JavaScripcie.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ready(fn) {
|
||||
if (document.readyState !== 'loading') fn();
|
||||
else document.addEventListener('DOMContentLoaded', fn);
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
var link = document.querySelector('a[data-wyloguj]');
|
||||
if (!link) return;
|
||||
|
||||
link.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
// Znacznik czasu w loginie: gdyby przeglądarka zapamiętała nawet TE dane,
|
||||
// przy kolejnym wylogowaniu byłyby już inne, więc nie da się przypadkiem
|
||||
// trafić w cokolwiek działającego.
|
||||
var bezsensowne = 'Basic ' + btoa('wyloguj:' + Date.now());
|
||||
fetch(link.href, {
|
||||
headers: {Authorization: bezsensowne},
|
||||
cache: 'no-store',
|
||||
credentials: 'omit'
|
||||
}).catch(function () {
|
||||
// Brak sieci nie może zablokować wyjścia — i tak przechodzimy dalej,
|
||||
// bo sama trasa /wyloguj odpowie 401 i wywoła okienko logowania.
|
||||
}).then(function () {
|
||||
window.location.href = link.href;
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user