@@ -20,7 +20,10 @@ import (
20
20
21
21
"fyne.io/fyne/v2"
22
22
"fyne.io/fyne/v2/app"
23
+ "fyne.io/fyne/v2/canvas"
24
+ "fyne.io/fyne/v2/container"
23
25
"fyne.io/fyne/v2/dialog"
26
+ "fyne.io/fyne/v2/layout"
24
27
"fyne.io/fyne/v2/theme"
25
28
"fyne.io/fyne/v2/widget"
26
29
"fyne.io/systray"
@@ -51,7 +54,7 @@ const (
51
54
)
52
55
53
56
func main () {
54
- daemonAddr , showSettings , showNetworks , showDebug , errorMsg , saveLogsInFile := parseFlags ()
57
+ daemonAddr , showSettings , showNetworks , showLoginURL , showDebug , errorMsg , saveLogsInFile := parseFlags ()
55
58
56
59
// Initialize file logging if needed.
57
60
var logFile string
@@ -77,13 +80,13 @@ func main() {
77
80
}
78
81
79
82
// Create the service client (this also builds the settings or networks UI if requested).
80
- client := newServiceClient (daemonAddr , logFile , a , showSettings , showNetworks , showDebug )
83
+ client := newServiceClient (daemonAddr , logFile , a , showSettings , showNetworks , showLoginURL , showDebug )
81
84
82
85
// Watch for theme/settings changes to update the icon.
83
86
go watchSettingsChanges (a , client )
84
87
85
88
// Run in window mode if any UI flag was set.
86
- if showSettings || showNetworks || showDebug {
89
+ if showSettings || showNetworks || showDebug || showLoginURL {
87
90
a .Run ()
88
91
return
89
92
}
@@ -104,14 +107,15 @@ func main() {
104
107
}
105
108
106
109
// parseFlags reads and returns all needed command-line flags.
107
- func parseFlags () (daemonAddr string , showSettings , showNetworks , showDebug bool , errorMsg string , saveLogsInFile bool ) {
110
+ func parseFlags () (daemonAddr string , showSettings , showNetworks , showLoginURL , showDebug bool , errorMsg string , saveLogsInFile bool ) {
108
111
defaultDaemonAddr := "unix:///var/run/netbird.sock"
109
112
if runtime .GOOS == "windows" {
110
113
defaultDaemonAddr = "tcp://127.0.0.1:41731"
111
114
}
112
115
flag .StringVar (& daemonAddr , "daemon-addr" , defaultDaemonAddr , "Daemon service address to serve CLI requests [unix|tcp]://[path|host:port]" )
113
116
flag .BoolVar (& showSettings , "settings" , false , "run settings window" )
114
117
flag .BoolVar (& showNetworks , "networks" , false , "run networks window" )
118
+ flag .BoolVar (& showLoginURL , "login-url" , false , "show login URL in a popup window" )
115
119
flag .BoolVar (& showDebug , "debug" , false , "run debug window" )
116
120
flag .StringVar (& errorMsg , "error-msg" , "" , "displays an error message window" )
117
121
flag .BoolVar (& saveLogsInFile , "use-log-file" , false , fmt .Sprintf ("save logs in a file: %s/netbird-ui-PID.log" , os .TempDir ()))
@@ -253,6 +257,7 @@ type serviceClient struct {
253
257
exitNodeStates []exitNodeState
254
258
mExitNodeDeselectAll * systray.MenuItem
255
259
logFile string
260
+ wLoginURL fyne.Window
256
261
}
257
262
258
263
type menuHandler struct {
@@ -263,7 +268,7 @@ type menuHandler struct {
263
268
// newServiceClient instance constructor
264
269
//
265
270
// This constructor also builds the UI elements for the settings window.
266
- func newServiceClient (addr string , logFile string , a fyne.App , showSettings bool , showNetworks bool , showDebug bool ) * serviceClient {
271
+ func newServiceClient (addr string , logFile string , a fyne.App , showSettings bool , showNetworks bool , showLoginURL bool , showDebug bool ) * serviceClient {
267
272
ctx , cancel := context .WithCancel (context .Background ())
268
273
s := & serviceClient {
269
274
ctx : ctx ,
@@ -286,6 +291,8 @@ func newServiceClient(addr string, logFile string, a fyne.App, showSettings bool
286
291
s .showSettingsUI ()
287
292
case showNetworks :
288
293
s .showNetworksUI ()
294
+ case showLoginURL :
295
+ s .showLoginURL ()
289
296
case showDebug :
290
297
s .showDebugUI ()
291
298
}
@@ -445,36 +452,36 @@ func (s *serviceClient) getSettingsForm() *widget.Form {
445
452
}
446
453
}
447
454
448
- func (s * serviceClient ) login () error {
455
+ func (s * serviceClient ) login (openURL bool ) ( * proto. LoginResponse , error ) {
449
456
conn , err := s .getSrvClient (defaultFailTimeout )
450
457
if err != nil {
451
458
log .Errorf ("get client: %v" , err )
452
- return err
459
+ return nil , err
453
460
}
454
461
455
462
loginResp , err := conn .Login (s .ctx , & proto.LoginRequest {
456
463
IsUnixDesktopClient : runtime .GOOS == "linux" || runtime .GOOS == "freebsd" ,
457
464
})
458
465
if err != nil {
459
466
log .Errorf ("login to management URL with: %v" , err )
460
- return err
467
+ return nil , err
461
468
}
462
469
463
- if loginResp .NeedsSSOLogin {
470
+ if loginResp .NeedsSSOLogin && openURL {
464
471
err = open .Run (loginResp .VerificationURIComplete )
465
472
if err != nil {
466
473
log .Errorf ("opening the verification uri in the browser failed: %v" , err )
467
- return err
474
+ return nil , err
468
475
}
469
476
470
477
_ , err = conn .WaitSSOLogin (s .ctx , & proto.WaitSSOLoginRequest {UserCode : loginResp .UserCode })
471
478
if err != nil {
472
479
log .Errorf ("waiting sso login failed with: %v" , err )
473
- return err
480
+ return nil , err
474
481
}
475
482
}
476
483
477
- return nil
484
+ return loginResp , nil
478
485
}
479
486
480
487
func (s * serviceClient ) menuUpClick () error {
@@ -486,7 +493,7 @@ func (s *serviceClient) menuUpClick() error {
486
493
return err
487
494
}
488
495
489
- err = s .login ()
496
+ _ , err = s .login (true )
490
497
if err != nil {
491
498
log .Errorf ("login failed with: %v" , err )
492
499
return err
@@ -558,7 +565,7 @@ func (s *serviceClient) updateStatus() error {
558
565
defer s .updateIndicationLock .Unlock ()
559
566
560
567
// notify the user when the session has expired
561
- if status .Status == string (internal .StatusNeedsLogin ) {
568
+ if status .Status == string (internal .StatusSessionExpired ) {
562
569
s .onSessionExpire ()
563
570
}
564
571
@@ -732,7 +739,6 @@ func (s *serviceClient) onTrayReady() {
732
739
go s .eventHandler .listen (s .ctx )
733
740
}
734
741
735
-
736
742
func (s * serviceClient ) attachOutput (cmd * exec.Cmd ) * os.File {
737
743
if s .logFile == "" {
738
744
// attach child's streams to parent's streams
@@ -871,17 +877,9 @@ func (s *serviceClient) onUpdateAvailable() {
871
877
872
878
// onSessionExpire sends a notification to the user when the session expires.
873
879
func (s * serviceClient ) onSessionExpire () {
880
+ s .sendNotification = true
874
881
if s .sendNotification {
875
- title := "Connection session expired"
876
- if runtime .GOOS == "darwin" {
877
- title = "NetBird connection session expired"
878
- }
879
- s .app .SendNotification (
880
- fyne .NewNotification (
881
- title ,
882
- "Please re-authenticate to connect to the network" ,
883
- ),
884
- )
882
+ s .eventHandler .runSelfCommand ("login-url" , "true" )
885
883
s .sendNotification = false
886
884
}
887
885
}
@@ -955,9 +953,9 @@ func (s *serviceClient) updateConfig() error {
955
953
ServerSSHAllowed : & sshAllowed ,
956
954
RosenpassEnabled : & rosenpassEnabled ,
957
955
DisableAutoConnect : & disableAutoStart ,
956
+ DisableNotifications : & notificationsDisabled ,
958
957
LazyConnectionEnabled : & lazyConnectionEnabled ,
959
958
BlockInbound : & blockInbound ,
960
- DisableNotifications : & notificationsDisabled ,
961
959
}
962
960
963
961
if err := s .restartClient (& loginRequest ); err != nil {
@@ -991,6 +989,87 @@ func (s *serviceClient) restartClient(loginRequest *proto.LoginRequest) error {
991
989
return nil
992
990
}
993
991
992
+ // showLoginURL creates a borderless window styled like a pop-up in the top-right corner using s.wLoginURL.
993
+ func (s * serviceClient ) showLoginURL () {
994
+
995
+ resp , err := s .login (false )
996
+ if err != nil {
997
+ log .Errorf ("failed to fetch login URL: %v" , err )
998
+ return
999
+ }
1000
+ verificationURL := resp .VerificationURIComplete
1001
+ if verificationURL == "" {
1002
+ verificationURL = resp .VerificationURI
1003
+ }
1004
+
1005
+ if verificationURL == "" {
1006
+ log .Error ("no verification URL provided in the login response" )
1007
+ return
1008
+ }
1009
+
1010
+ resIcon := fyne .NewStaticResource ("netbird.png" , iconAbout )
1011
+
1012
+ if s .wLoginURL == nil {
1013
+ s .wLoginURL = s .app .NewWindow ("NetBird Session Expired" )
1014
+ s .wLoginURL .Resize (fyne .NewSize (400 , 200 ))
1015
+ s .wLoginURL .SetIcon (resIcon )
1016
+ }
1017
+ // add a description label
1018
+ label := widget .NewLabel ("Your NetBird session has expired.\n Please re-authenticate to continue using NetBird." )
1019
+
1020
+ btn := widget .NewButtonWithIcon ("Re-authenticate" , theme .ViewRefreshIcon (), func () {
1021
+
1022
+ conn , err := s .getSrvClient (defaultFailTimeout )
1023
+ if err != nil {
1024
+ log .Errorf ("get client: %v" , err )
1025
+ return
1026
+ }
1027
+
1028
+ if err := openURL (verificationURL ); err != nil {
1029
+ log .Errorf ("failed to open login URL: %v" , err )
1030
+ return
1031
+ }
1032
+
1033
+ _ , err = conn .WaitSSOLogin (s .ctx , & proto.WaitSSOLoginRequest {UserCode : resp .UserCode })
1034
+ if err != nil {
1035
+ log .Errorf ("Waiting sso login failed with: %v" , err )
1036
+ label .SetText ("Waiting login failed, please create \n a debug bundle in the settings and contact support." )
1037
+ return
1038
+ }
1039
+
1040
+ label .SetText ("Re-authentication successful.\n Reconnecting" )
1041
+ time .Sleep (300 * time .Millisecond )
1042
+ _ , err = conn .Up (s .ctx , & proto.UpRequest {})
1043
+ if err != nil {
1044
+ label .SetText ("Reconnecting failed, please create \n a debug bundle in the settings and contact support." )
1045
+ log .Errorf ("Reconnecting failed with: %v" , err )
1046
+ return
1047
+ }
1048
+
1049
+ label .SetText ("Connection successful.\n Closing this window." )
1050
+ time .Sleep (time .Second )
1051
+
1052
+ s .wLoginURL .Close ()
1053
+ })
1054
+
1055
+ img := canvas .NewImageFromResource (resIcon )
1056
+ img .FillMode = canvas .ImageFillContain
1057
+ img .SetMinSize (fyne .NewSize (64 , 64 ))
1058
+ img .Resize (fyne .NewSize (64 , 64 ))
1059
+
1060
+ // center the content vertically
1061
+ content := container .NewVBox (
1062
+ layout .NewSpacer (),
1063
+ img ,
1064
+ label ,
1065
+ btn ,
1066
+ layout .NewSpacer (),
1067
+ )
1068
+ s .wLoginURL .SetContent (container .NewCenter (content ))
1069
+
1070
+ s .wLoginURL .Show ()
1071
+ }
1072
+
994
1073
func openURL (url string ) error {
995
1074
var err error
996
1075
switch runtime .GOOS {
0 commit comments