controller: customer-claim password gate v0.122.0 (closes DRILL-day0-vm F-4/F-5)

The customer sets + owns the dashboard password via a hub-emailed one-time
claim code. An unclaimed box (code hash present, no password) serves ONLY the
claim page — every other route → claim page (302) or 401, so a Day-0 box is
never open on the internet. A set password disables the gate (auth wins).
Reset rides the same code engine (login "Elfelejtett jelszó"). Legacy-open
(no password, no hash) shows a red transition banner until the hub delivers a
hash. Report ACK caches the code state idempotently by generation; report
carries claimed (set-only). --print-reset-code root escape hatch. Requires
hub v0.50.0. Gate-coverage signature test + 4 red-proofs proven.
This commit is contained in:
2026-07-12 18:42:39 +02:00
parent dec6fef20d
commit 3cf49c7fd5
18 changed files with 1117 additions and 1 deletions
+34
View File
@@ -1,5 +1,39 @@
## Changelog
### v0.122.0 — customer-claim password gate (closes DRILL-day0-vm F-4/F-5) (2026-07-12) — MinAgent: 0.81.0
The customer sets + OWNS the dashboard password; the old "no password → open dashboard" is gone.
An unclaimed box (hub-delivered claim-code hash present, no password) serves ONLY the claim page
— every other route answers the claim page (302 → `/claim`) or `401` (API), so a Day-0 box is
never open on the public internet (closes F-4; F-5's unauthenticated geo toggle closes with it).
Requires the hub's v0.50.0 claim engine (code generation + email + ACK/config delivery).
- **`internal/web/claim.go`** — the gate + pages. `claimGateActive()` (no password + code hash +
not claimed), `effectiveClaimCode()` (ACK-cached settings beats the config bake by generation),
the claim page (`GET /claim`), submit (`POST /claim`: verify code → set own password → claimed
→ consume generation → session), and "kérj új kódot / Elfelejtett jelszó" (`POST
/claim/request-new-code` → hub `reset-request`). Code checks: bcrypt match AND generation not
yet consumed (single-use) AND ≤ 72 h old. Per-source + global brute-force limiter (5 tries →
15-min lockout, fake-clock tested); a lockout raises the allowlisted `claim_lockout` event.
Pre-auth CSRF is an HMAC over `web.session_secret` (closes the CTRL-007 bare-double-submit
weakness), min password length 12.
- **Gate wiring** (`auth.go`, `csrf.go`, `server.go`, `cmd`): the gate sits atop `RequireAuth`; a
SET password disables it entirely (password auth wins — claimed boxes never regress). `/claim*`
+ `/static/*` stay reachable pre-auth (the code is the strong factor). Legacy-open (no password,
no hash) passes through with a red transition banner (`layout.html`) until the hub delivers a
hash. Login page gains an "Elfelejtett jelszó" link.
- **`internal/report/claim_sync.go`** — caches the ACK's `claim` {hash, generation} into
settings.json IDEMPOTENTLY BY GENERATION (offsite-descriptor one-way shape: newer generation
advances; same/older/nil never rewrites, a hub outage never clears). The report carries
`claimed` (set-only hub-side). `config.web.claim_code_*` baked by the hub gates from first boot.
- **`internal/settings`** — `Claimed` (set-only), `ClaimCode*` cache, `ClaimConsumedGeneration`
(single-use). **`--print-reset-code`** root escape hatch: prints a one-time local code (a
generation above cached/baked/consumed), the same gate consumes it.
- Tests: gate-coverage signature test (every route → claim/401, a deploy POST mutates nothing) +
happy-path/reuse-refused/expired/lockout+window-reopen; four §10 red-proofs proven
(mutate→FAIL→revert): gate skip-line, single-use generation (hub + controller), reset non-DoS,
rate-limiter.
### v0.121.0 — backups page truth pass (dead sections removed, real Tier-3 state, SQLite-honest DB) (2026-07-12) — MinAgent: 0.81.0
Pure UI/data-plumbing on `/backups`; no backup-engine behavior change, no agent-API change, MinAgent