From 6e23ed4da75fd5eb50c938c581e66140029ac698 Mon Sep 17 00:00:00 2001 From: Zoltan Papp Date: Mon, 8 Jun 2026 17:10:15 +0200 Subject: [PATCH] [client] Add error event publishing for rejected session deadlines (#6358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [client] Surface session deadline rejections via SystemEvent and add timer arm debug logs When sessionwatch.Watcher.Update rejects a deadline (pre-epoch, too far in the future, or past the clock-skew tolerance) it silently zeroes the status recorder, leaving the UI with no "expires in" row and no indication of why. Publish a SystemEvent_ERROR on the AUTHENTICATION channel so the rejection appears in the UI event feed and the user knows re-login may be required. Also add Debugf log lines in armTimerLocked so that warning and final-warning timer fire-times are visible in logs without having to add instrumentation after the fact. https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u * [client] Remove verbose arm-timer debug logs from sessionwatch The per-arm Debugf lines added noise on every deadline update. Rejection logging already happens at the call site in engine_authsession.go; the watcher itself needs no extra instrumentation. https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u * [client] Leave userMessage empty on deadline-rejected event; use metadata key Daemon-layer PublishEvent userMessage strings are not localized — the UI reads metadata keys and builds its own locale-aware copy (same pattern as the session-warning events in event.go). Drop the hardcoded English sentence from the deadline-rejected event and instead surface the rejection reason via a new MetaSessionDeadlineRejected metadata key so the UI can detect and localize it. https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u * [client] Revert silent deadline-rejected event; restore userMessage MetaSessionDeadlineRejected had no UI consumer: the tray only does metadata-driven localisation for MetaSessionWarning events; all other SystemEvents display userMessage directly (tray_events.go). Leaving userMessage empty made the rejection invisible to the user. Restore the English userMessage so the generic event path shows something, and remove the unused MetaSessionDeadlineRejected constant. https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u * [client] Localize session deadline rejected notification via metadata key Follow the same pattern as session-warning events: the daemon emits an empty userMessage and puts the signal in a typed metadata key (MetaSessionDeadlineRejected); the UI tray detects the key and builds a locale-aware OS notification from i18n strings. Changes: - sessionwatch/event.go: add MetaSessionDeadlineRejected constant - engine_authsession.go: empty userMessage, use the new metadata key - ui/authsession/warning.go: re-export MetaDeadlineRejected for UI consumers - ui/tray_events.go: gate on isDeadlineRejected alongside isSessionWarning; new branch calls t.notify with localized title/body - i18n locales (en/de/hu): add notify.sessionDeadlineRejected.{title,body} https://claude.ai/code/session_01Y3bQoNgcVjTD4zDTvv7a8u --------- Co-authored-by: Claude --- client/internal/auth/sessionwatch/event.go | 8 ++++++++ client/internal/engine_authsession.go | 9 +++++++++ client/ui/authsession/warning.go | 9 +++++---- client/ui/i18n/locales/de/common.json | 2 ++ client/ui/i18n/locales/en/common.json | 2 ++ client/ui/i18n/locales/hu/common.json | 2 ++ client/ui/tray_events.go | 18 ++++++++++++++---- 7 files changed, 42 insertions(+), 8 deletions(-) diff --git a/client/internal/auth/sessionwatch/event.go b/client/internal/auth/sessionwatch/event.go index 7a0ec8ce7..3e55b26dd 100644 --- a/client/internal/auth/sessionwatch/event.go +++ b/client/internal/auth/sessionwatch/event.go @@ -33,6 +33,14 @@ const ( // for the T-10 event, FinalWarningLead for the T-2 event) so the UI // can show "expires in ~N minutes" without hardcoding either constant. MetaSessionLeadMinutes = "lead_minutes" + // MetaSessionDeadlineRejected is attached to the ERROR/AUTHENTICATION + // SystemEvent the daemon emits when it discards a deadline from the + // management server (pre-epoch, too far in the future, or past the + // clock-skew tolerance). The value is the rejection reason string. + // userMessage is left empty; the UI detects the event via this key + // and builds a localized notification — same pattern as the session + // warnings above. + MetaSessionDeadlineRejected = "session_deadline_rejected" ) // expiresAtLayout is the wire format used for MetaSessionExpiresAt. diff --git a/client/internal/engine_authsession.go b/client/internal/engine_authsession.go index 49e276b77..8335c2cb5 100644 --- a/client/internal/engine_authsession.go +++ b/client/internal/engine_authsession.go @@ -9,6 +9,8 @@ import ( log "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/timestamppb" + cProto "github.com/netbirdio/netbird/client/proto" + "github.com/netbirdio/netbird/client/internal/auth/sessionwatch" "github.com/netbirdio/netbird/client/system" ) @@ -51,6 +53,13 @@ func (e *Engine) ApplySessionDeadline(ts *timestamppb.Timestamp) { // of sync with the warning timers. if err := e.sessionWatcher.Update(deadline); err != nil { log.Errorf("auth session deadline rejected: %v, clearing", err) + e.statusRecorder.PublishEvent( + cProto.SystemEvent_ERROR, + cProto.SystemEvent_AUTHENTICATION, + "session deadline rejected", + "", + map[string]string{sessionwatch.MetaSessionDeadlineRejected: err.Error()}, + ) } } diff --git a/client/ui/authsession/warning.go b/client/ui/authsession/warning.go index a9e8fa65f..304b903d2 100644 --- a/client/ui/authsession/warning.go +++ b/client/ui/authsession/warning.go @@ -19,10 +19,11 @@ import ( // side) so UI-side consumers don't have to import the daemon-internal // package directly. const ( - MetaWarning = sessionwatch.MetaSessionWarning - MetaFinal = sessionwatch.MetaSessionFinal - MetaExpiresAt = sessionwatch.MetaSessionExpiresAt - MetaLeadMinutes = sessionwatch.MetaSessionLeadMinutes + MetaWarning = sessionwatch.MetaSessionWarning + MetaFinal = sessionwatch.MetaSessionFinal + MetaExpiresAt = sessionwatch.MetaSessionExpiresAt + MetaLeadMinutes = sessionwatch.MetaSessionLeadMinutes + MetaDeadlineRejected = sessionwatch.MetaSessionDeadlineRejected ) // Warning is the typed payload emitted on the session-warning Wails diff --git a/client/ui/i18n/locales/de/common.json b/client/ui/i18n/locales/de/common.json index 2db418475..841dafeeb 100644 --- a/client/ui/i18n/locales/de/common.json +++ b/client/ui/i18n/locales/de/common.json @@ -55,6 +55,8 @@ "notify.sessionWarning.failed": "NetBird-Sitzung konnte nicht verlängert werden", "notify.sessionWarning.successTitle": "NetBird-Sitzung verlängert", "notify.sessionWarning.successBody": "Ihre Sitzung wurde erneuert.", + "notify.sessionDeadlineRejected.title": "Sitzungsfrist abgelehnt", + "notify.sessionDeadlineRejected.body": "Der Server hat eine ungültige Sitzungsfrist übermittelt. Bitte melden Sie sich erneut an.", "common.cancel": "Abbrechen", "common.save": "Speichern", diff --git a/client/ui/i18n/locales/en/common.json b/client/ui/i18n/locales/en/common.json index 2698e2b12..f88cf3555 100644 --- a/client/ui/i18n/locales/en/common.json +++ b/client/ui/i18n/locales/en/common.json @@ -55,6 +55,8 @@ "notify.sessionWarning.failed": "Failed to extend NetBird session", "notify.sessionWarning.successTitle": "NetBird session extended", "notify.sessionWarning.successBody": "Your session has been refreshed.", + "notify.sessionDeadlineRejected.title": "Session deadline rejected", + "notify.sessionDeadlineRejected.body": "The server sent an invalid session deadline. Please sign in again.", "common.cancel": "Cancel", "common.save": "Save", diff --git a/client/ui/i18n/locales/hu/common.json b/client/ui/i18n/locales/hu/common.json index af6cfdea6..127dc5da9 100644 --- a/client/ui/i18n/locales/hu/common.json +++ b/client/ui/i18n/locales/hu/common.json @@ -55,6 +55,8 @@ "notify.sessionWarning.failed": "A NetBird munkamenet meghosszabbítása sikertelen", "notify.sessionWarning.successTitle": "NetBird munkamenet meghosszabbítva", "notify.sessionWarning.successBody": "A munkamenet frissítve.", + "notify.sessionDeadlineRejected.title": "Munkamenet-határidő elutasítva", + "notify.sessionDeadlineRejected.body": "A szerver érvénytelen munkamenet-határidőt küldött. Kérjük, jelentkezzen be újra.", "common.cancel": "Mégse", "common.save": "Mentés", diff --git a/client/ui/tray_events.go b/client/ui/tray_events.go index 1a628f0bc..4668d555d 100644 --- a/client/ui/tray_events.go +++ b/client/ui/tray_events.go @@ -22,11 +22,12 @@ func (t *Tray) onSystemEvent(ev *application.CustomEvent) { if !ok { return } - // Session-warning events carry no UserMessage — the tray builds the - // localised notification body locally from metadata. Every other - // event needs a non-empty UserMessage to show anything meaningful. + // Session-warning and deadline-rejected events carry no UserMessage — + // the tray builds the localised notification body locally from metadata. + // Every other event needs a non-empty UserMessage to show anything meaningful. isSessionWarning := se.Metadata[authsession.MetaWarning] == "true" - if !isSessionWarning && se.UserMessage == "" { + isDeadlineRejected := se.Metadata[authsession.MetaDeadlineRejected] != "" + if !isSessionWarning && !isDeadlineRejected && se.UserMessage == "" { return } if shouldSkipSystemEvent(se) { @@ -52,6 +53,15 @@ func (t *Tray) onSystemEvent(ev *application.CustomEvent) { // - T-FinalWarningLead (MetaSessionFinal=true) → auto-open the // SessionAboutToExpire dialog. No OS notification here; the // dialog is the last-chance reminder, doubling up would be noise. + if isDeadlineRejected { + t.notify( + t.loc.T("notify.sessionDeadlineRejected.title"), + t.loc.T("notify.sessionDeadlineRejected.body"), + notifyIDSessionExpired, + ) + return + } + if se.Metadata != nil && se.Metadata[authsession.MetaWarning] == "true" { if se.Metadata[authsession.MetaFinal] == "true" { t.openSessionAboutToExpire()