diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index e13bcf3a2..9c299dfc6 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -24,9 +24,13 @@ jobs: ignore_words_list: erro,clienta,hastable,iif,groupd,testin,groupe,cros,ans,deriver,te,userA,ede,additionals # Non-English UI translations trip codespell on real foreign words # (de: "Sie", "oder", "ist"). Only en/common.json is the source of - # truth that should be spell-checked. Add each new locale dir here - # when a language is added under client/ui/i18n/locales/. - skip: go.mod,go.sum,**/proxy/web/**,**/pnpm-lock.yaml,**/package-lock.json,client/ui/i18n/locales/de/**,client/ui/i18n/locales/hu/** + # truth that should be spell-checked. List each translated locale + # dir below and add new ones as languages are added under + # client/ui/i18n/locales/. Single-star globs are matched per path + # segment by codespell and behave the same across versions; the + # recursive "**" form did not take effect with the codespell shipped + # by this action. + skip: go.mod,go.sum,*/proxy/web/*,*pnpm-lock.yaml,*package-lock.json,*/locales/de/*,*/locales/hu/* golangci: strategy: fail-fast: false 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/internal/engine_sessionwatch_js.go b/client/internal/engine_sessionwatch_js.go index 98a0c1da7..50e148ab9 100644 --- a/client/internal/engine_sessionwatch_js.go +++ b/client/internal/engine_sessionwatch_js.go @@ -35,5 +35,10 @@ func (w noopSessionWatcher) Update(deadline time.Time) error { return nil } -func (noopSessionWatcher) Dismiss() {} -func (noopSessionWatcher) Close() {} +func (noopSessionWatcher) Dismiss() { + // No-op: only suppresses the timer-driven final-warning, which this stub never arms. +} + +func (noopSessionWatcher) Close() { + // No-op: no timers to stop and no state to unwind; the recorder is cleared via Update(zero). +} diff --git a/client/server/server.go b/client/server/server.go index 685314819..20f80743e 100644 --- a/client/server/server.go +++ b/client/server/server.go @@ -959,6 +959,10 @@ func (s *Server) SwitchProfile(callerCtx context.Context, msg *proto.SwitchProfi s.config = config + if msg != nil && msg.ProfileName != nil { + s.publishProfileListChanged(*msg.ProfileName) + } + return &proto.SwitchProfileResponse{}, nil } 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 32f4c71c2..d37f0f865 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 c07bc76be..0f146bc7b 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 f177b7890..717f601fa 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_click_other.go b/client/ui/tray_click_other.go index 231986a47..df2dc5afe 100644 --- a/client/ui/tray_click_other.go +++ b/client/ui/tray_click_other.go @@ -2,12 +2,7 @@ package main -// bindTrayClick is a no-op on macOS and Linux. On macOS the native -// NSStatusItem auto-shows the menu on left-click; on Linux the -// StatusNotifierItem host paints the menu independently. Binding an -// OnClick→OpenMenu handler is both unnecessary there and actively harmful on -// macOS, where OpenMenu routes through NSStatusItem's blocking [button -// mouseDown:] on the serial main GCD queue and freezes the tray and webview -// until the menu closes (commit c77e5cef8). Windows opts in via the sibling -// tray_click_windows.go file. -func bindTrayClick(*Tray) {} +func bindTrayClick(*Tray) { + // No-op: macOS/Linux native trays open the menu on click themselves. + // Only Windows needs an explicit handler (tray_click_windows.go). +} diff --git a/client/ui/tray_events.go b/client/ui/tray_events.go index 804430347..35bb280e7 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 // SessionExpiration 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.openSessionExpiration() diff --git a/client/ui/tray_theme_other.go b/client/ui/tray_theme_other.go index a0d20f7ab..86a8eae73 100644 --- a/client/ui/tray_theme_other.go +++ b/client/ui/tray_theme_other.go @@ -2,10 +2,7 @@ package main -// startTrayTheme is a no-op off Linux: macOS uses template icons (the OS -// recolours them per menubar appearance) and Windows ships colored PNGs, so -// neither needs the freedesktop colour-scheme probe that the Linux build -// uses to choose between the black and white monochrome silhouettes. Left -// callable so NewTray can invoke it unconditionally; panelDark stays nil and -// panelIsDark returns its default. -func (t *Tray) startTrayTheme() {} +func (t *Tray) startTrayTheme() { + // No-op off Linux: macOS template icons and Windows colored PNGs need no + // colour-scheme probe. panelDark stays nil; panelIsDark uses its default. +}