v0.137.0: guard empty-email notification save (prevents alert-delivery wipe)

Saving the notifications form with a blank email box while events are enabled
wiped the customer's hub-side alert address (SyncPreferences pushed empty) —
the 2026-07-15 demo incident. settingsNotificationsHandler now refuses that
save before SetNotificationPrefs + hub sync, re-renders a Hungarian error, and
repaints the submitted checkboxes. Empty email + zero events (clear-all) still
proceeds. Tests + red-proof (remove guard -> stored email wiped to empty).
This commit is contained in:
2026-07-15 19:40:01 +02:00
parent 0ef6648e12
commit c124d0adf2
4 changed files with 201 additions and 0 deletions
+23
View File
@@ -1423,6 +1423,29 @@ func (s *Server) settingsNotificationsHandler(w http.ResponseWriter, r *http.Req
enabledEvents = append(enabledEvents, "expected_backup_missed", "expected_dbdump_missed")
}
// EMPTY-EMAIL WIPE GUARD (2026-07-15 demo incident): a blank email box saved while events are
// still enabled would store an empty Email AND push it to the hub via SyncPreferences, wiping
// the customer's provisioning-seeded alert address — enabled events with nowhere to send them.
// The only way to reach this state is the bug, so refuse the save outright (BEFORE
// SetNotificationPrefs and BEFORE any hub sync), leaving the stored email untouched, and ask for
// an address. The intentional "turn everything off" case (empty email + ZERO events) falls
// through below — clearing the email is legitimate there and the empty hub push is correct.
if email == "" && len(enabledEvents) > 0 {
s.logger.Printf("[WARN] [web] Refused notification save: empty email with %d enabled event(s) — would wipe hub-side alert delivery", len(enabledEvents))
data := s.notificationsPageData()
// Repaint the customer's just-submitted intent (their ticked events + chosen cooldown, empty
// email) so they only need to add an address, not re-tick everything. Overlay the stored
// prefs — do NOT persist this; it is render-only.
data["NotificationPrefs"] = &settings.NotificationPrefs{
Email: email,
EnabledEvents: enabledEvents,
CooldownHours: cooldownHours,
}
data["NotificationError"] = "Adj meg egy értesítési e-mail címet bekapcsolt értesítésekhez szükséges egy cím, ahova küldhetjük őket."
s.executeTemplate(w, r, "settings_notifications", data)
return
}
prefs := &settings.NotificationPrefs{
Email: email,
EnabledEvents: enabledEvents,