Files
aiscr-qgis-amcr-viewer/openspec/changes/archive/2026-10-02-fix-session-expiry-islogged/design.md
T
david-spacil 74090a80c6 fix: před stahováním ověřit přihlášení přes islogged (#72) (#80)
* fix: před stahováním ověřit přihlášení přes islogged

Server při vypršelé session nevrací chybu, ale tiše odpoví jako
anonymnímu uživateli (jen přístupnost A). Plugin proto před stahováním
volá /api/user/islogged; při 'nologged' se jednou znovu přihlásí
z uložených údajů, a když to nejde, varuje v liště, že stahuje anonymně.

- amcr_tools: _check_islogged, _ensure_logged_in, volání v load_amcr_data
- smoke test: 7 offline scénářů stavu přihlášení
- README; changelog v2.2.0 v metadata.txt
- openspec/changes/fix-session-expiry-islogged: návrh, spec, design, úkoly

Closes #72. Připraveno s pomocí AI (Claude), ručně zkontrolováno.

* feat: odebrání přihlašovacích údajů uživatele i odhlásí

Dosud zůstala přihlášená session v paměti až do restartu QGIS, takže
se po „Odebrat uložené přihlašovací údaje“ dál stahovalo jako přihlášený.
Nově se session odhlásí na serveru (GET /api/user/logout) a zahodí;
když server neodpoví, zahodí se aspoň lokálně.

- amcr_tools: logout_from_api; amcr_dialog: volání v _forget_credentials
- smoke test: odhlášení, chyba sítě, bez session
- README, changelog v2.2.0, OpenSpec (požadavek + úkoly 2b; 3.3 ověřeno)

Připraveno s pomocí AI (Claude), ručně zkontrolováno.

* chore: archivovat OpenSpec změnu fix-session-expiry-islogged

Všechny úkoly hotové (2b.2 – odhlášení – ověřeno ručně v QGIS),
archivováno s --skip-specs (stupeň change-tracked, bez openspec/specs/).

Připraveno s pomocí AI (Claude), ručně zkontrolováno.
2026-10-02 12:37:31 +02:00

4.0 KiB
Raw Blame History

Design

Context

  • The session lives only in memory (amcr_tools.AMCR_SESSION, a requests.Session with the JSESSIONID cookie). _get_session() logs in from stored credentials only when no session object exists, so after QGIS start the first download always logs in; an expired session object is reused forever.
  • All data requests of a download go through _api_get_json() (main query pages and PIAN batches). _is_auth_error() there reacts to HTTP 401 or error text – neither occurs on expiry (see proposal.md – Why).
  • Server behaviour (verified 2026-10-02, live API): GET /api/user/islogged → {"remaining": <s>} when logged in, {"error": "nologged"} otherwise, both HTTP 200. It does not extend the session; any search/query does.
  • Codelists (amcr_codelists.py) use plain requests.get without the session – unaffected by login state.

Goals / Non-Goals

Goals:

  • One check at the start of load_amcr_data, before the first data request.
  • Reuse existing login code (login_to_api, LoginDialog.get_credentials).
  • Testable offline: the check takes its HTTP behaviour from the session object so the smoke test can inject a fake.

Non-Goals:

  • Checking before every page / PIAN batch (a download takes seconds to minutes and every data request renews the sliding timeout).
  • Refactoring session handling into a class.

Decisions

  1. New helper _ensure_logged_in() -> str in amcr_tools.py, returning one of "anonymous" (no session, no credentials – nothing to check), "logged_in", "relogged", "fallback" (expected login, ended anonymous), "unknown" (check failed, proceeding). Flow: get session via _get_session() (logs in if needed); if none and no credentials → anonymous; if none but credentials → login failed → fallback; otherwise call islogged; remaining → logged_in; nologged → drop session, re-login once, verify again → relogged or fallback; exception / non-JSON → unknown. Alternative: re-login unconditionally before each download – simpler, but one POST with the password per download and no way to distinguish a real failure; rejected. Alternative: compare remaining with a local timestamp of last request – fragile (server timeout may change); rejected.

  2. Caller decides UI. load_amcr_data pushes the message bar warning on fallback; the helper only logs (keeps it free of iface for the test).

  3. Interpretation of the response: logged in iff the body is a dict with key remaining. Anything else with an error key → not logged in. Unknown shape → unknown (do not trigger a re-login loop on a format change).

  4. Keep _is_auth_error as a fallback, with a comment that the current server never triggers it; removing it brings no benefit and it still covers a possible future 401.

  5. Never log the response of islogged?wantsUser=true – we do not use that parameter at all; only remaining (number) is logged.

  6. Logout on credential removal – new logout_from_api() in amcr_tools.py called from LoginDialog._forget_credentials. The local session is dropped first and unconditionally; the server call is best effort (a failure is logged and reported in the dialog text). Without it, the in-memory session would keep downloading logged-in data until QGIS restarts even though the user believes he is "forgotten".

Risks / Trade-offs

  • [Extra request per download] → only for logged-in / credential users; cost ~100 ms.
  • [Session expires during a very long download] → practically impossible: each page request renews the 1 h sliding timeout.
  • [Re-login prompts for the QGIS master password] → get_credentials() is already called on first download after start; behaviour unchanged.
  • [islogged endpoint changes shape] → unknown, logged warning, download proceeds as today (no regression).

Migration Plan

Plain plugin update; no settings or data migration. Rollback = previous release.