hub v0.66.0 + ISO v1.20.0: customer self-bind (R-27 slice 1)
Let a customer bind their own freshly-installed appliance without the operator: operator "Send self-bind link" mints a 7-day tokenized capability link, emailed (Hungarian, sibling sender) to the customer, who opens a public /bind/<token> page and proves two factors — the console pairing code shown on the box screen + their retrieval passphrase — and the hub stages the bind via the same BindAppliance (provenance customer_selfbind). The box's ~30s appliance poll delivers. Viktor's three rulings verbatim: console pairing code (no appliance list ever rendered), operator-sent tokenized link, 5-attempt lockout -> "call support". Wrong code == wrong passphrase (one generic failure, no oracle, both factors compared unconditionally); expiry falls back to operator-bind unchanged. THE TRAP: one public prefix /bind/, exempt from auth+CSRF at both /login gate sites via a single isPublicBindPath predicate (tight trailing-slash match; ServeMux ..-cleans; handler rejects '/' in token). 9 tests (Scenarios A-F + F1/F2); 4 red-proofs verified red-then-green (lockout, oracle, widened-prefix, single-active). GC verdict: no appliance GC -> the 7-day TTL stands alone. Controller/agent untouched; R-27b deferred. Green: full hub build/vet/test (17 ok) + bash -n + hub confirm gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017qDiBqKKQ5vPB5fXBqu7Kp
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/configgen"
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/store"
|
||||
)
|
||||
|
||||
@@ -28,6 +29,7 @@ type applianceRow struct {
|
||||
CPU string
|
||||
MemGB string
|
||||
SSHFingerprints []string
|
||||
PairingCode string // v0.66.0 (R-27): the code the customer reads off the box console + types on /bind
|
||||
FirstSeen *time.Time
|
||||
LastSeen *time.Time
|
||||
Stale bool
|
||||
@@ -61,9 +63,10 @@ func sshFingerprint(line string) string {
|
||||
// applianceToRow builds the view model (parses hw_summary + computes SSH fingerprints).
|
||||
func applianceToRow(a store.ApplianceRegistration, now time.Time, customerName func(string) string) applianceRow {
|
||||
row := applianceRow{
|
||||
ID: a.ID,
|
||||
UUID: a.UUID,
|
||||
Bound: a.Status == store.ApplianceBound,
|
||||
ID: a.ID,
|
||||
UUID: a.UUID,
|
||||
PairingCode: configgen.FormatPairingCode(a.PairingCode),
|
||||
Bound: a.Status == store.ApplianceBound,
|
||||
}
|
||||
if a.MACSet != "" {
|
||||
row.MACs = strings.Split(a.MACSet, ",")
|
||||
|
||||
Reference in New Issue
Block a user