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 c12b6de6b..8335c2cb5 100644 --- a/client/internal/engine_authsession.go +++ b/client/internal/engine_authsession.go @@ -10,6 +10,7 @@ import ( "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" ) @@ -56,8 +57,8 @@ func (e *Engine) ApplySessionDeadline(ts *timestamppb.Timestamp) { cProto.SystemEvent_ERROR, cProto.SystemEvent_AUTHENTICATION, "session deadline rejected", - "The session expiry time from the server could not be applied. Re-login may be required.", - map[string]string{"reason": err.Error()}, + "", + 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 39dd57f87..9758011e3 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 1e7b0c28f..9b423461c 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 80f5766ff..05e206219 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()