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:
@@ -1557,6 +1557,33 @@ self_update:
|
||||
|
||||
### 8. Authentication & Settings
|
||||
|
||||
#### Customer-claim gate (`internal/web/claim.go`, v0.122.0 — closes DRILL-day0-vm F-4/F-5)
|
||||
|
||||
The dashboard password is **customer-owned**, set through a one-time claim code the hub emails to
|
||||
the registered address (no operator-set path, no open-until-set window). This closes the fresh-box
|
||||
race where a new `felhom.<domain>` cert appears in CT logs minutes before any password exists.
|
||||
|
||||
- **States** (precedence): a SET password (settings→config) always wins — the gate never shows.
|
||||
Else a delivered **claim-code hash + not-yet-claimed** → GATED: every route serves the claim
|
||||
page (`302 → /claim`) or `401` JSON (API); only `/claim*`, `/static/*`, `/api/health` pass. Else
|
||||
(no password, no hash) → **legacy-open** with a red transition banner until the hub delivers a
|
||||
hash (transitional only, never the fresh-box state).
|
||||
- **Claim/reset flow**: `GET /claim` (code + new password ×2, min 12) → `POST /claim` verifies the
|
||||
code (bcrypt match AND generation not yet consumed AND ≤ 72 h old), sets the customer's password
|
||||
via `settings.SetPasswordHash`, marks `Claimed` (set-only), consumes the generation (single-use),
|
||||
invalidates sessions, issues a fresh one. `POST /claim/request-new-code` (the "Új kód kérése" /
|
||||
login-page "Elfelejtett jelszó") forwards to the hub, which emails a fresh code to the
|
||||
registered address only. Reset rides the same page (a claimed box reaches `/claim` pre-auth).
|
||||
- **Anti-brute-force**: per-source + global counter, 5 failures → 15-minute lockout (both scopes),
|
||||
raising the allowlisted `claim_lockout` event. Pre-auth CSRF is an HMAC over `web.session_secret`
|
||||
(fixes the CTRL-007 bare-double-submit weakness).
|
||||
- **Delivery**: the hub bakes `web.claim_code_{hash,generation,issued_at}` into the Day-0
|
||||
controller.yaml (gate-from-first-boot) and serves the freshest state in the report ACK
|
||||
(`report/claim_sync.go` caches it idempotently by generation — newer advances, same/older/nil
|
||||
never rewrites, a hub outage never clears). The report carries `claimed` (hub ingests set-only).
|
||||
- **Escape hatch**: `felhom-controller --print-reset-code` prints a one-time local code (generation
|
||||
above cached/baked/consumed); the same gate consumes it. Root-gated by `docker exec` reachability.
|
||||
|
||||
#### Session Auth (`internal/web/auth.go`)
|
||||
|
||||
- bcrypt password verification with configurable source priority: `settings.json` → `controller.yaml` → no auth (open access)
|
||||
|
||||
Reference in New Issue
Block a user