feat(bezpieczeństwo): przycisk wylogowania (LOG-32)
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m17s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m29s
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 1m18s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m21s
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 5s
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 9m29s
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 1m18s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 10m21s
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 5s
Testy / Kontrola składni wszystkich warstw (push) Successful in 4s
Aplikacja nie miała jak z siebie wyjść: raz podane hasło działało do zamknięcia
przeglądarki, a na wspólnym komputerze nie było sposobu, żeby oddać ekran komuś
innemu.
HTTP BASIC NIE MA PRAWDZIWEGO WYLOGOWANIA i to jest sedno tej zmiany. Nie ma
sesji do skasowania: przeglądarka zapamiętuje login i hasło, po czym dosyła je
SAMA przy każdym żądaniu. Serwer nie ma czego zapomnieć — kolejne kliknięcie
przyszłoby z kompletem poświadczeń i weszłoby z powrotem.
Działa natomiast doprowadzenie do tego, żeby to PRZEGLĄDARKA porzuciła zapamiętane
dane, i robimy to dwutorowo, bo ani jedno, ani drugie osobno nie wystarcza:
1. /wyloguj odpowiada ZAWSZE 401 z nagłówkiem WWW-Authenticate, co wymusza
ponowne pytanie o hasło. Działa bez JavaScriptu, ale samo w sobie zostawia
stare dane w pamięci przeglądarki: po anulowaniu okienka wystarczyłoby wejść
na dowolny adres, żeby wrócić do środka.
2. wyloguj.js wysyła żądanie z CELOWO błędnymi danymi, którym przeglądarka
nadpisuje swój wpis. To jest część, która faktycznie czyści pamięć — ale
opiera się na zachowaniu powszechnym, a nie zapisanym w standardzie, więc
nie może być jedynym mechanizmem.
Strona wylogowania mówi wprost, że pewnym sposobem w KAŻDEJ przeglądarce jest
zamknięcie okna. Nie obiecujemy więcej, niż Basic potrafi — obietnica bez pokrycia
byłaby tu gorsza od braku przycisku, bo dawałaby złudzenie, że ekran jest oddany.
Trasa stoi POZA bramką logowania, celowo: inaczej dostałaby 200 od zalogowanej
sesji i nie miałaby jak odpowiedzieć 401. Odpowiada też bez logowania — inaczej
wyjście wymagałoby bycia w środku, co jest błędnym kołem. Nagłówki zakazują
zapamiętania strony, bo oddana z pamięci podręcznej nie dotarłaby do serwera
i okienko w ogóle by się nie pojawiło.
Obok wyjścia pokazujemy, KTO jest zalogowany: bez tego przycisk jest w połowie
bezużyteczny, bo na wspólnym komputerze nie wiadomo, kogo się wylogowuje. Przy
wyłączonym logowaniu nie ma ani jednego, ani drugiego — przycisk sugerowałby
ochronę, której nie ma.
Sprawdzone w przeglądarce: oba żądania wychodzą (to z błędnymi danymi i samo
przejście), oba wracają 401, strona renderuje się poprawnie. Samego unieważnienia
pamięci poświadczeń NIE dało się tu potwierdzić — wymaga okienka systemowego,
którego automat nie obsłuży.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit was merged in pull request #75.
This commit is contained in:
@@ -86,6 +86,9 @@ def _perms(request: Request) -> frozenset[str]:
|
||||
# dokładnie ten rodzaj sprytu, który po roku zostawia kogoś z dostępem, o którym
|
||||
# nikt już nie pamięta.
|
||||
templates.env.globals["nav_screens"] = lambda request: features.screens_for(_perms(request))
|
||||
# Kto jest zalogowany i czy logowanie w ogóle działa — pod przycisk wyjścia.
|
||||
templates.env.globals["current_user"] = lambda request: getattr(request.state, "user", "") or ""
|
||||
templates.env.globals["auth_on"] = security.auth_enabled
|
||||
templates.env.globals["can"] = lambda request, key: key in _perms(request)
|
||||
|
||||
|
||||
@@ -879,6 +882,48 @@ def accounts_delete(request: Request, login: str = Form(...)):
|
||||
return _accounts_redirect(done=f"Skasowano konto „{login}”.")
|
||||
|
||||
|
||||
# ---------------- Wylogowanie (LOG-32) ----------------
|
||||
# HTTP Basic NIE MA prawdziwego wylogowania i trzeba to powiedzieć wprost:
|
||||
# przeglądarka zapamiętuje dane logowania i dosyła je sama przy każdym żądaniu,
|
||||
# więc „zapomnienie" po stronie serwera nic nie daje — kolejne kliknięcie weszłoby
|
||||
# z powrotem. Nie ma czego wyczyścić w sesji, bo sesji nie ma.
|
||||
#
|
||||
# Działa natomiast jedno: doprowadzić do tego, żeby PRZEGLĄDARKA porzuciła to, co
|
||||
# zapamiętała. Robimy to dwutorowo:
|
||||
#
|
||||
# 1. ta trasa odpowiada ZAWSZE 401 z nagłówkiem WWW-Authenticate, co zmusza
|
||||
# przeglądarkę do ponownego zapytania o hasło — działa też bez JavaScriptu,
|
||||
# 2. wyloguj.js dokłada to, co faktycznie CZYŚCI pamięć przeglądarki: żądanie
|
||||
# z celowo błędnymi danymi, którym nadpisuje ona zapamiętany wpis.
|
||||
#
|
||||
# Sam punkt 1 zostawiłby stare dane w pamięci przeglądarki: po anulowaniu okienka
|
||||
# wystarczyłoby wejść na dowolny adres, żeby wrócić do środka. Dlatego oba naraz.
|
||||
|
||||
@app.get("/wyloguj", response_class=HTMLResponse)
|
||||
def wyloguj() -> HTMLResponse:
|
||||
"""Kończy sesję przeglądarki. Zawsze 401 — to jest tu WYNIK, nie awaria."""
|
||||
strona = (
|
||||
'<!DOCTYPE html><html lang="pl"><head><meta charset="utf-8">'
|
||||
'<title>astrololo · wylogowano</title>'
|
||||
f'<link rel="stylesheet" href="{static_url("styles.css")}"></head>'
|
||||
'<body><main><header class="topbar"><h1>astrololo</h1></header>'
|
||||
'<p class="done-note">Wylogowano.</p>'
|
||||
'<p class="muted">Aby wrócić, <a href="/">zaloguj się ponownie</a>.</p>'
|
||||
'<p class="muted small">Jeśli komputer jest współdzielony, zamknij okno '
|
||||
'przeglądarki — to jedyny sposób pewny w każdej przeglądarce.</p>'
|
||||
'</main></body></html>'
|
||||
)
|
||||
return HTMLResponse(
|
||||
strona, status_code=401,
|
||||
headers={"WWW-Authenticate": 'Basic realm="astrololo"',
|
||||
# Bez tego przeglądarka mogłaby oddać tę stronę z pamięci
|
||||
# podręcznej przy kolejnym wylogowaniu, nie pytając serwera —
|
||||
# a wtedy okienko logowania w ogóle by się nie pojawiło.
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate",
|
||||
"Pragma": "no-cache"},
|
||||
)
|
||||
|
||||
|
||||
@app.get("/health")
|
||||
def health() -> dict:
|
||||
return {"status": "ok", "layer": "presentation"}
|
||||
|
||||
Reference in New Issue
Block a user