v0.165.0: Indítópult megosztása — guest launcher via capability URL (+ optional password, QR)

Mint a 160-bit capability URL (/s/<token>) serving a standalone read-only guest
launcher: same tiles, opens apps in new tabs, no account, no admin session.
Information only, zero control — every privilege stays behind each app's own auth.

- /s/ pre-auth pass-through (after the claim gate) + session-CSRF exemption; guest
  password POST carries its own pre-auth HMAC CSRF.
- Constant-time token match; empty stored token = disabled = byte-identical mux 404.
- Optional per-share password: separate bcrypt hash + own attempt map; signed cookie
  = HMAC(token|passwordHash) keyed with web.session_secret, so rotate/change invalidates.
- Guest labels ride the v0.164.0 ruling; never expose internal state vocabulary.
- Token redacted in logs (/s/<redacted>); never in CHANGELOG/REPORT/CONTEXT.
- Admin modal: copy-link, QR (go-qrcode), set/clear password, rotate, disable.
- Tests: Groups A-G (14) + 3 red-proofs verified red.
This commit is contained in:
2026-07-24 12:08:43 +02:00
parent 8e5edb2865
commit 15206314ab
19 changed files with 1341 additions and 115 deletions
+40
View File
@@ -1,5 +1,45 @@
## Changelog
### v0.165.0 — Indítópult megosztása: guest launcher via capability URL (2026-07-24)
No agent coupling; MinAgent unchanged. New dependency: `github.com/skip2/go-qrcode`
(v0.0.0-20200617195104-da1b6568686e, MIT, pure Go, zero transitive deps) for the modal QR code.
The admin launcher gains an **"Indítópult megosztása"** button that mints a **capability URL**
(`https://<host>/s/<token>`, 160-bit token) serving a standalone, read-only guest launcher — same
tiles, opens apps in new tabs — with **no accounts and no admin session**. The link grants
**information only, zero control**: app names + public URLs; every privilege stays behind each app's
own auth and the controller admin password.
- **Capability-URL serving.** `/s/<token>` is added to the RequireAuth pre-auth allowlist (AFTER the
claim-gate block, so the claim gate stays supreme) and exempted from session CSRF (guests carry
their own pre-auth HMAC CSRF, like the claim POST). Token comparison is `subtle.ConstantTimeCompare`;
an empty stored token matches nothing, so a wrong/disabled token is **byte-identical to the mux
default 404** — nothing distinguishes it from an unknown route. Guest responses set `X-Robots-Tag:
noindex, nofollow`, `Referrer-Policy: no-referrer`, `Cache-Control: no-store`.
- **Optional per-share password.** A SEPARATE credential — its own bcrypt hash
(`settings.LauncherSharePasswordHash`, never the admin hash), its own per-IP 5/1-min attempt map
(never the admin login map). Passing it once mints a signed cookie = HMAC-SHA256 over
`token|passwordHash` (keyed with the persisted, box-scoped `web.session_secret`), so **rotating the
token OR changing the password invalidates every outstanding cookie** with zero bookkeeping.
- **Modal (admin):** copy-link, a QR code (`/launcher/share/qr.png`, ~256px, admin-authed),
"Jelszó beállítása/törlése", "Új link készítése" (rotation), "Megosztás kikapcsolása". POSTs under
`/launcher/share/*` ride the normal admin session + session CSRF.
- **Guest state labels ride the v0.164.0 ruling:** `StateStopped` ⇒ "A tulajdonos leállította";
any other non-clickable state ⇒ "Átmenetileg nem elérhető"; guests never see internal state
vocabulary (stopped/exited/degraded/unhealthy). Clickable ⇔ operational AND its public route is
published (`isOperationalState && !routeUnpublished`), so a guest tap never dead-ends on a 404.
- **Token is a secret:** never logged (the ServeHTTP debug line and the 404 WARN redact `/s/` paths
to `/s/<redacted>`), never written to CHANGELOG/REPORT/CONTEXT, constant-time comparison only.
- **Refactors:** `launcherApps()` extracted from `launcherHandler` (shared with the guest handler);
the tile visual extracted into a `launch_tile` partial (single markup source for admin + guest);
`isOperationalState` promoted to a package predicate (single source for the funcmap + guest rule).
- New files: `internal/web/share.go` (pure core), `internal/web/share_handlers.go` (HTTP surface),
`internal/web/share_test.go` (Groups AG + 3 red-proofs verified red), templates
`launcher_shared.html` + `launcher_share_password.html`.
- Design rulings (CONTEXT): member accounts are superseded by this capability-URL model;
per-member tile visibility is parked under the SSO arc.
### v0.164.0 — Deliberately stopped apps no longer alarm (banner + email) (2026-07-24)
No agent coupling; MinAgent unchanged. Operator finding on 9201: stopping an app via the UI