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:
@@ -28,6 +28,17 @@ type Settings struct {
|
||||
// Auth
|
||||
PasswordHash string `json:"password_hash,omitempty"` // bcrypt hash, overrides controller.yaml
|
||||
|
||||
// Customer-claim arc (v0.122.0, F-4). Claimed is SET-ONLY (a claim or reset completed at
|
||||
// least once — never cleared). ClaimCode* cache the freshest hub-delivered code state (report
|
||||
// ACK; beats controller.yaml when its generation is newer). ClaimConsumedGeneration records
|
||||
// the last code generation successfully consumed — a code of a consumed generation is dead
|
||||
// even if its hash still matches (single-use).
|
||||
Claimed bool `json:"claimed,omitempty"`
|
||||
ClaimCodeHash string `json:"claim_code_hash,omitempty"`
|
||||
ClaimCodeGeneration int `json:"claim_code_generation,omitempty"`
|
||||
ClaimCodeIssuedAt string `json:"claim_code_issued_at,omitempty"` // RFC3339
|
||||
ClaimConsumedGeneration int `json:"claim_consumed_generation,omitempty"`
|
||||
|
||||
// Notification preferences (Phase 2 — define struct now, leave empty)
|
||||
Notifications *NotificationPrefs `json:"notifications,omitempty"`
|
||||
|
||||
@@ -405,6 +416,60 @@ func (s *Settings) SetPasswordHash(hash string) error {
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// ── Customer-claim arc (v0.122.0) ──────────────────────────────────────────────
|
||||
|
||||
// GetClaimed reports whether this box has completed a claim (set-only).
|
||||
func (s *Settings) GetClaimed() bool {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.Claimed
|
||||
}
|
||||
|
||||
// SetClaimed marks the box claimed (never un-claims) and saves.
|
||||
func (s *Settings) SetClaimed() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.Claimed {
|
||||
return nil
|
||||
}
|
||||
s.Claimed = true
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// GetClaimCode returns the cached hub-delivered code state (hash, generation, issuedAt RFC3339).
|
||||
func (s *Settings) GetClaimCode() (hash string, generation int, issuedAt string) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.ClaimCodeHash, s.ClaimCodeGeneration, s.ClaimCodeIssuedAt
|
||||
}
|
||||
|
||||
// SetClaimCode caches a hub-delivered code state (idempotent by generation — the caller guards).
|
||||
func (s *Settings) SetClaimCode(hash string, generation int, issuedAt string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.ClaimCodeHash = hash
|
||||
s.ClaimCodeGeneration = generation
|
||||
s.ClaimCodeIssuedAt = issuedAt
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// GetClaimConsumedGeneration returns the last successfully consumed code generation.
|
||||
func (s *Settings) GetClaimConsumedGeneration() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.ClaimConsumedGeneration
|
||||
}
|
||||
|
||||
// SetClaimConsumedGeneration records a consumed code generation (single-use enforcement).
|
||||
func (s *Settings) SetClaimConsumedGeneration(gen int) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if gen > s.ClaimConsumedGeneration {
|
||||
s.ClaimConsumedGeneration = gen
|
||||
}
|
||||
return s.save()
|
||||
}
|
||||
|
||||
// GetDBValidations returns a copy of the cached DB validations.
|
||||
func (s *Settings) GetDBValidations() map[string]DBValidationCache {
|
||||
s.mu.RLock()
|
||||
|
||||
Reference in New Issue
Block a user