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
@@ -1181,6 +1181,28 @@ a.stat-card:hover {
.launch-cell--off { cursor: default; }
.launch-tile--off { opacity: .4; }
.launch-cell--off .launch-name { color: var(--text-3); }
.launch-off-label { font-size: .8rem; color: var(--text-3); text-align: center; }
/* Launcher share modal (v0.165.0) */
.share-open-btn { margin-left: auto; }
.share-lede { color: var(--text-2); font-size: .9rem; margin-bottom: 1rem; }
.share-field { margin-bottom: 1rem; }
.share-label { display: block; font-size: .85rem; color: var(--text-2); margin-bottom: .35rem; }
.share-note { color: var(--text-2); font-size: .9rem; }
.share-link-row { display: flex; gap: .5rem; align-items: center; }
.share-link-row input { flex: 1; min-width: 0; }
.share-qr { display: flex; flex-direction: column; align-items: center; gap: .35rem; margin-bottom: 1rem; }
.share-qr img { border: 1px solid var(--line); border-radius: var(--radius); background: #fff; padding: .35rem; }
.share-qr-hint { color: var(--text-3); font-size: .8rem; }
.share-actions { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: .5rem; }
/* Standalone guest launcher + password gate (v0.165.0) — no sidebar/nav */
.guest-content { max-width: 960px; margin: 0 auto; padding: 2rem 1.25rem; }
.guest-gate-body { display: flex; align-items: center; justify-content: center; min-height: 100vh; }
.guest-gate { width: 100%; display: flex; justify-content: center; padding: 1.25rem; }
.guest-gate-card { width: 100%; max-width: 360px; background: var(--bg-1); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.75rem; text-align: center; }
.guest-gate-card form { display: flex; flex-direction: column; gap: .75rem; margin-top: 1rem; }
.gate-error { color: var(--crit); font-size: .9rem; margin-top: .5rem; }
/* Login page */
.login-body {