77import shutil
88import time
99import prctl
10+ import keyring
1011import subprocess
1112import configparser
1213from collections import namedtuple
@@ -62,7 +63,7 @@ def __init__(self):
6263 tray_menu .addAction (show_action )
6364 tray_menu .addAction (hide_action )
6465 tray_menu .addAction (quit_action )
65- self .tray_icon .setToolTip ("NordVPN NM " )
66+ self .tray_icon .setToolTip ("NordVPN" )
6667 self .tray_icon .setContextMenu (tray_menu )
6768 self .tray_icon .show ()
6869
@@ -74,12 +75,15 @@ def __init__(self):
7475 self .show ()
7576
7677 def quitAppEvent (self ):
78+ """
79+ Quit GUI from system tray
80+ """
7781 qApp .quit ()
7882
79- """
80- Override default close event
81- """
8283 def closeEvent (self , event ):
84+ """
85+ Override default close event
86+ """
8387 event .ignore ()
8488 self .hide ()
8589 self .tray_icon .showMessage (
@@ -89,10 +93,10 @@ def closeEvent(self, event):
8993 2500
9094 )
9195
92- """
93- Resume from SystemTray
94- """
9596 def resume (self , activation_reason ):
97+ """
98+ Resume from SystemTray
99+ """
96100 if activation_reason == 3 :
97101 self .show ()
98102
@@ -331,16 +335,22 @@ def login_ui(self):
331335 self .horizontalLayout_2 .addWidget (self .password_input )
332336 self .verticalLayout .addLayout (self .horizontalLayout_2 )
333337 self .horizontalLayout_3 .addLayout (self .verticalLayout )
334- spacerItem = QtWidgets .QSpacerItem (80 , 20 , QtWidgets .QSizePolicy .Preferred , QtWidgets .QSizePolicy .Minimum )
338+ spacerItem = QtWidgets .QSpacerItem (57 , 20 , QtWidgets .QSizePolicy .Preferred , QtWidgets .QSizePolicy .Minimum )
335339 self .horizontalLayout_3 .addItem (spacerItem )
340+ self .verticalLayout_6 = QtWidgets .QVBoxLayout ()
341+ self .verticalLayout_6 .setObjectName ("verticalLayout_6" )
336342 self .login_btn = QtWidgets .QPushButton (self .centralwidget )
337- sizePolicy = QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Fixed , QtWidgets .QSizePolicy .Fixed )
343+ sizePolicy = QtWidgets .QSizePolicy (QtWidgets .QSizePolicy .Preferred , QtWidgets .QSizePolicy .Fixed )
338344 sizePolicy .setHorizontalStretch (0 )
339345 sizePolicy .setVerticalStretch (0 )
340346 sizePolicy .setHeightForWidth (self .login_btn .sizePolicy ().hasHeightForWidth ())
341347 self .login_btn .setSizePolicy (sizePolicy )
342348 self .login_btn .setObjectName ("login_btn" )
343- self .horizontalLayout_3 .addWidget (self .login_btn )
349+ self .verticalLayout_6 .addWidget (self .login_btn )
350+ self .remember_checkBox = QtWidgets .QCheckBox (self .centralwidget )
351+ self .remember_checkBox .setObjectName ("remember_checkBox" )
352+ self .verticalLayout_6 .addWidget (self .remember_checkBox )
353+ self .horizontalLayout_3 .addLayout (self .verticalLayout_6 )
344354 self .verticalLayout_2 .addLayout (self .horizontalLayout_3 )
345355 self .gridLayout .addLayout (self .verticalLayout_2 , 0 , 0 , 1 , 1 )
346356 self .setCentralWidget (self .centralwidget )
@@ -351,15 +361,16 @@ def login_ui(self):
351361 self .retranslate_login_ui ()
352362 QtCore .QMetaObject .connectSlotsByName (self )
353363
354- self .check_configs () # do configs exist else create
364+ self .check_configs () # do configs exist else create
355365
356- #buttons here
366+ # buttons here
357367 self .password_input .returnPressed .connect (self .login_btn .click )
358368 self .login_btn .clicked .connect (self .verify_credentials )
359369
360370 def check_configs (self ):
361371 """
362372 Checks if config directories and files exist and creates them if they do not
373+ If username is found in config execute get_credentials()
363374 """
364375 try :
365376 if not os .path .isdir (self .base_dir ):
@@ -369,13 +380,33 @@ def check_configs(self):
369380 if not os .path .isdir (self .scripts_path ):
370381 os .mkdir (self .scripts_path )
371382 if not os .path .isfile (self .conf_path ):
372- self .config ['USER' ] = {}
373- self .config ['SETTINGS' ] = {'MAC_RANDOMIZER' : 'False' , 'KILL_SWITCH' : 'False' , 'AUTO_CONNECT' : 'False' }
383+ self .config ['USER' ] = {
384+ 'USER_NAME' : 'None' }
385+ self .config ['SETTINGS' ] = {
386+ 'MAC_RANDOMIZER' : 'False' ,
387+ 'KILL_SWITCH' : 'False' ,
388+ 'AUTO_CONNECT' : 'False' }
374389 self .write_conf ()
375390
391+ self .config .read (self .conf_path )
392+ if self .config .get ('USER' , 'USER_NAME' ) != 'None' :
393+ self .statusbar .showMessage ("Fetching Saved Credentials" , 1000 )
394+ self .username = self .config .get ('USER' , 'USER_NAME' )
395+ self .remember_checkBox .setChecked (True )
396+ self .user_input .setText (self .username )
397+ self .get_credentials ()
398+
376399 except PermissionError :
377400 self .statusbar .showMessage ("Insufficient Permissions to create config folder" , 2000 )
378401
402+ def get_credentials (self ):
403+ try :
404+ keyring .get_keyring ()
405+ password = keyring .get_password ('NordVPN' , self .username )
406+ self .password_input .setText (password )
407+ except Exception as ex :
408+ self .statusbar .showMessage ("Error fetching keyring" , 1000 )
409+
379410 def write_conf (self ):
380411 """
381412 Writes config file
@@ -399,8 +430,7 @@ def parse_conf(self):
399430
400431 def verify_credentials (self ):
401432 """
402- Requests a token, salt and key from Nord api
403- Sends a final hash of (salt+password)+key and token to Nord api
433+ Requests a token from NordApi by sending the email and password in json format
404434 Verifies responses and updates GUI
405435 """
406436 if self .user_input .text () and self .password_input .text ():
@@ -410,6 +440,27 @@ def verify_credentials(self):
410440 else :
411441 self .statusbar .showMessage ('Username or password field cannot be empty' , 2000 )
412442 try :
443+ # check whether credentials should be saved
444+ if self .remember_checkBox .isChecked ():
445+ try :
446+ keyring .set_password ("NordVPN" , self .username , self .password )
447+ self .config ['USER' ]['USER_NAME' ] = self .username
448+ self .write_conf ()
449+ except Exception as ex :
450+ self .statusbar .showMessage ("Error accessing keyring" , 1000 )
451+ time .sleep (1 )
452+
453+ # Delete credentials if found
454+ else :
455+ try :
456+ keyring .delete_password ("NordVPN" , self .username )
457+ self .config ['USER' ]['USER_NAME' ] = 'None'
458+ self .write_conf ()
459+ except Exception as ex :
460+ self .statusbar .showMessage ("No saved credentials to delete" , 1000 )
461+ time .sleep (0.5 )
462+
463+ # post username and password to api endpoint
413464 json_data = {'username' : self .username , 'password' : self .password }
414465 resp = requests .post ('https://api.nordvpn.com/v1/users/tokens' , json = json_data , timeout = 5 )
415466 if resp .status_code == 201 :
@@ -418,7 +469,6 @@ def verify_credentials(self):
418469 time .sleep (0.5 )
419470 self .hide ()
420471 self .main_ui ()
421-
422472 else :
423473 self .statusbar .showMessage ('Invalid Username or Password' , 2000 )
424474
@@ -1198,6 +1248,7 @@ def retranslate_login_ui(self):
11981248 self .password_label .setText (
11991249 _translate ("MainWindow" , "<html><head/><body><p align=\" right\" >Password: </p></body></html>" ))
12001250 self .login_btn .setText (_translate ("MainWindow" , "Login" ))
1251+ self .remember_checkBox .setText (_translate ("MainWindow" , "Remember" ))
12011252
12021253
12031254if __name__ == '__main__' :
0 commit comments