Files
astrololo/docs/konta-i-uprawnienia.md
T
gitea 1d0ff2f72e
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 10m20s
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 5s
build / build (push) Successful in 1m39s
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4m45s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m32s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 7s
Testy / Kontrola składni wszystkich warstw (push) Successful in 6s
PRE-27: generowanie tekstu przez model znika bez uprawnienia
Wymaganie było mocniejsze niż schowanie przycisku: po niedostępnej funkcji nie
może zostać śladu w źródle strony. Największy wyciek nie był przyciskiem —
_prompt_block wstrzykiwał w stronę CAŁY katalog modeli jako JSON (dostawcy,
nazwy modeli, rozmiary okien kontekstu), na każdym ekranie z generowaniem,
niezależnie od uprawnień konta.

Druga dziura była głębsza: handlery nie sprawdzały nic. Trasa /interpret musi
być dostępna dla konta z Interpretacjami, więc granica przebiega WEWNĄTRZ niej,
po polu `action` — spreparowany formularz z action=prompt generował tekst, a
action=export pobierał arkusz, mimo że szablon chował oba przyciski.

Akcja bez uprawnienia wraca do akcji domyślnej ekranu zamiast dawać błąd:
komunikat „brak uprawnień do generowania" sam w sobie mówiłby, że taka funkcja
istnieje.

Ślady wycięte także tam, gdzie nie były kontrolką: znaczniki natalNote/
reportNatal, pliki models.js/progress.js/natal.js/predictions.js oraz podtytuł
ekranu Skompiluj, który wymieniał interpretację od AI z nazwy.

12 testów; 9 z nich pada na kodzie sprzed poprawki (sprawdzone przez cofnięcie
zmian w app/). Kontrola pozytywna pilnuje, żeby nie przechodziły dlatego, że
generowanie jest zepsute dla wszystkich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:10:16 +02:00

5.5 KiB

Konta i uprawnienia (PRE-27)

Rozszerzenie kont imiennych z PRE-17: konta zakłada się z aplikacji, a każde dostaje własny zestaw funkcji.

Dwie zasady, z których wynika reszta

1. Konto ograniczone widzi program KOMPLETNY — tylko mniejszy. Nic nie może zdradzać, że istnieje coś więcej. Żadnych wyszarzonych zakładek, żadnego „brak uprawnień", żadnego 403 — bo 403 samo w sobie jest informacją, że pod tym adresem coś jest. Ścieżka bez uprawnienia odpowiada 404, tak samo jak adres, którego nie ma.

Z tej zasady wynikło też wyłączenie /docs, /redoc i /openapi.json. Automatyczna dokumentacja FastAPI wypisuje komplet tras — czyli spis wszystkich funkcji programu. Ochrona zakładek nic by nie dała, gdyby obok leżał ich katalog. (Znalezione testem, nie przeglądem kodu.)

2. Konto administracyjne pochodzi WYŁĄCZNIE ze środowiska. APP_USER / APP_PASSWORD (albo APP_USERS) — jak dotąd. To konto ma wszystkie uprawnienia i jako jedyne zarządza pozostałymi. Nie leży w pliku kont, więc nie da się go skasować ani ograniczyć z ekranu — nawet przez pomyłkę, nawet spreparowanym żądaniem. Konto założone w pliku o tym samym loginie nie przesłoni administracyjnego (kolejność sprawdzania jest odwrotna).

Podział funkcji

Ekrany — zakładki widoczne w nawigacji:

klucz zakładka
chart Horoskop
interpret Interpretacje
timeline Kalendarz
synastry Synastria
significators Sygnifikatory
compile Skompiluj
settings Ustawienia

Rozszerzenia — poziomy złożoności wewnątrz ekranów:

klucz co daje
houses_compare wybór systemu domów, zestawienie kilku obok siebie, obrót koła
extra_charts aspektarian, wykres deklinacji, oś antyscji
advanced_calc stacje planet, tabele żywiołów i faz, aspekty poboczne, zodiaki syderyczne
ai horoskopy pisane przez model językowy (każde użycie kosztuje)
export pobieranie PDF i Excela

Konto bez houses_compare dostaje horoskop w Whole Sign i nie widzi, że systemów jest trzynaście. Konto bez ai nie zobaczy przycisku generowania ani nie wywoła go z pominięciem interfejsu.

Gdzie leży granica

W handlerze, nie w szablonie. Ukrycie pola w formularzu chroni przed przypadkiem, ale nie przed kimś, kto zna nazwy pól — dlatego _limit_options() ścina opcje po stronie serwera, a rysunki dodatkowe bez uprawnienia w ogóle nie powstają (nie ma ich nawet w źródle strony).

Mapa trasa → uprawnienie jest jedna, w app/features.py. Rozproszenie jej po dekoratorach kończy się trasą, o której ochronie ktoś zapomniał — a taka dziura jest niewidoczna do chwili, gdy ktoś ją znajdzie. Trasa bez wpisu w mapie wymaga uprawnień administracyjnych: przeoczenie ma zamykać, nie otwierać. Test przechodzi po wszystkich trasach aplikacji i wymaga, by każda była opisana.

Gdy jedna trasa robi kilka rzeczy

Mapa tras nie wystarcza tam, gdzie jedna trasa obsługuje kilka funkcji naraz. POST /interpret liczy horoskop, ale to samo pole action prosi o wygenerowanie promptu, napisanie horoskopu przez model albo eksport arkusza. Konto, które ma mieć Interpretacje bez generowania, musi dostać tę trasę — więc granica przebiega wewnątrz niej, po akcjach: _AKCJE_POD_UPRAWNIENIEM przypisuje akcji uprawnienie, a _dozwolona_akcja() sprowadza żądanie bez uprawnienia do akcji domyślnej ekranu.

Sprowadza — nie odrzuca. Komunikat „brak uprawnień do generowania" sam w sobie mówiłby, że taka funkcja istnieje, czyli łamałby zasadę drugą po to, żeby wyegzekwować pierwszą. Akcja bez uprawnienia ma wyglądać na literówkę w formularzu.

Ślad to nie tylko przycisk

Wymaganie brzmi „nie może być śladu", i to jest mocniejsze niż schowanie kontrolki. Największym wyciekiem po stronie generowania nie był przycisk, tylko katalog modeli — nazwy dostawców, nazwy modeli i rozmiary okien kontekstu — wstrzykiwany w stronę blokiem JSON na każdym ekranie z generowaniem, niezależnie od uprawnień. Dlatego _llm_catalog_for() oddaje pusty katalog kontu bez uprawnienia, a szablony trzymają pod bramką także znaczniki (natalNote, reportNatal), pliki skryptów (models.js, progress.js, natal.js, predictions.js) i zdania opisujące funkcję — podtytuł ekranu Skompiluj wymieniał interpretację od AI z nazwy.

Testu na to nie da się napisać przez „sprawdź, czy przycisku nie ma": trzeba sprawdzić, że w źródle strony nie ma żadnego z tych śladów, i mieć kontrolę pozytywną, że przy uprawnieniu wszystkie są. Inaczej test przechodzi także wtedy, gdy generowanie jest zepsute dla wszystkich.

Gdzie leżą konta

Plik JSON wskazany przez ACCOUNTS_FILE (domyślnie /app/state/accounts.json), na NFS — własny podkatalog prezentacji, nie katalog z bazami: zamontowanie tutaj całego udziału obeszłoby bokiem zamknięcie dostępu z DAN-25.

Hasła wyłącznie jako hash scrypt, tym samym mechanizmem co APP_USERS — jedna implementacja, więc nie ma czego rozjechać. Zapis jest atomowy (plik tymczasowy + os.replace w tym samym katalogu): przerwanie zapisu nie obetnie pliku, czyli nie skasuje wszystkich kont naraz.

Czego ten mechanizm NIE robi

Nie zastępuje ochrony baz na poziomie sieci ani NFS (DAN-25). Ktoś z dostępem do udziału albo do warstwy danych nadal je odczyta — uprawnienia w aplikacji ograniczają to, co widać przez aplikację, i tyle.