hub: customer-claim password arc parts 1+2 — code engine, emails, ACK, configgen bake, UI (v0.50.0)
Closes DRILL-day0-vm F-4 hub-side: per-customer claim state (customer_claims,
bcrypt-only custody), the claim engine (issue at real config retrieve = Day-0
bake; first-report issue for live boxes; resend rotates generation; reset
rate-limited 3/day), three Hungarian emails via the dispatcher, report-ACK
claim object {code_hash, generation, issued_at} + set-only claimed ingest,
web.claim_code_* baked into generated controller.yaml, Setup-tab status chip
+ resend button, POST /api/v1/claim/reset-request (self-scoped), claim_lockout
event allowlisted. 13 new tests; full repo green.
Claude-Session: https://claude.ai/code/session_01NptTCFtu7dz2Ru89qHRagN
This commit is contained in:
@@ -224,3 +224,24 @@ func isEventEnabled(enabledEvents []string, eventType string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SendClaimEmail delivers a customer-claim arc email (claim / reset / claimed confirmation) to
|
||||
// the REGISTERED customer address — the claim.Mailer implementation. Every send result is
|
||||
// logged + recorded in notification_log; the code itself never is (rule: plaintext exists only
|
||||
// inside the send).
|
||||
func (d *Dispatcher) SendClaimEmail(kind, customerID, email, domain, code string) error {
|
||||
if d.resendAPIKey == "" {
|
||||
d.logger.Printf("[ERROR] claim %s email for %s NOT sent: no Resend API key configured", kind, customerID)
|
||||
return fmt.Errorf("notify: no resend api key")
|
||||
}
|
||||
subject, body := FormatClaimEmail(kind, customerID, domain, code)
|
||||
eventType := "claim_" + kind
|
||||
if err := d.sendEmailFn(email, subject, body); err != nil {
|
||||
d.logger.Printf("[ERROR] claim %s email to customer %s failed: %v", kind, customerID, err)
|
||||
d.store.LogNotification(customerID, eventType, "info", subject, "failed", err.Error(), "customer")
|
||||
return err
|
||||
}
|
||||
d.logger.Printf("[INFO] claim %s email sent to the registered address of %s", kind, customerID)
|
||||
d.store.LogNotification(customerID, eventType, "info", subject, "sent", "", "customer")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ Message: %s`, customerID, eventType, severity, now, message)
|
||||
|
||||
// customerMessages maps event_type → Hungarian customer message.
|
||||
var customerMessages = map[string]string{
|
||||
// Customer-claim arc (v0.50.0)
|
||||
"claim_lockout": "Túl sok hibás beállító/visszaállító kód próbálkozás történt — a beállító oldal 15 percre zárolva lett. Ha nem te próbálkoztál, jelezd az üzemeltetőnek.",
|
||||
// Backup events
|
||||
"backup_completed": "A biztonsági mentés sikeresen elkészült.",
|
||||
"backup_failed": "A biztonsági mentés sikertelen! Kérjük, ellenőrizd a rendszert.",
|
||||
@@ -161,3 +163,67 @@ Felhom.eu monitoring`
|
||||
|
||||
return subject, body
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Customer-claim password arc (v0.50.0) — Hungarian code-delivery emails
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
// FormatClaimEmail returns (subject, textBody) for a claim-arc email. kind is one of
|
||||
// "claim" | "reset" | "claimed" (claim.EmailKind values). The code appears ONLY in the
|
||||
// returned body — callers must never log it.
|
||||
func FormatClaimEmail(kind, customerID, domain, code string) (string, string) {
|
||||
dashboardURL := "https://felhom." + domain
|
||||
switch kind {
|
||||
case "reset":
|
||||
subject := "[Felhom] Jelszó-visszaállítási kód"
|
||||
body := fmt.Sprintf(`Kedves Ügyfél!
|
||||
|
||||
Jelszó-visszaállítást kértél a Felhom vezérlőpultodhoz.
|
||||
|
||||
Visszaállító kód: %s
|
||||
|
||||
A kód 72 óráig érvényes, és egyszer használható fel. Add meg a vezérlőpult
|
||||
"Elfelejtett jelszó" oldalán, majd válassz új jelszót:
|
||||
|
||||
%s
|
||||
|
||||
Ha nem te kérted, hagyd figyelmen kívül — a jelenlegi jelszavad változatlan.
|
||||
|
||||
Üdvözlettel,
|
||||
Felhom.eu`, code, dashboardURL)
|
||||
return subject, body
|
||||
case "claimed":
|
||||
subject := "[Felhom] A vezérlőpultod mostantól jelszóval védett"
|
||||
body := fmt.Sprintf(`Kedves Ügyfél!
|
||||
|
||||
A Felhom vezérlőpultod beállítása elkészült — a vezérlőpultod mostantól
|
||||
jelszóval védett. A megadott jelszóval tudsz bejelentkezni:
|
||||
|
||||
%s
|
||||
|
||||
Ha nem te végezted a beállítást, azonnal vedd fel a kapcsolatot az üzemeltetővel.
|
||||
|
||||
Üdvözlettel,
|
||||
Felhom.eu`, dashboardURL)
|
||||
return subject, body
|
||||
default: // "claim"
|
||||
subject := "[Felhom] Elindult a Felhom szervered — beállító kód"
|
||||
body := fmt.Sprintf(`Kedves Ügyfél!
|
||||
|
||||
Elindult a Felhom szervered. A vezérlőpult első használatához add meg az
|
||||
alábbi beállító kódot, majd válassz saját jelszót:
|
||||
|
||||
Beállító kód: %s
|
||||
|
||||
A kód 72 óráig érvényes, és egyszer használható fel. A vezérlőpultot itt éred el:
|
||||
|
||||
%s
|
||||
|
||||
Ha nem kaptad volna meg időben, a vezérlőpult "Új kód kérése" gombjával
|
||||
kérhetsz frisset — az mindig erre az e-mail címre érkezik.
|
||||
|
||||
Üdvözlettel,
|
||||
Felhom.eu`, code, dashboardURL)
|
||||
return subject, body
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user