Skip to content

Commit ab1c685

Browse files
committed
add remember username/password functionality
1 parent 4e04477 commit ab1c685

File tree

1 file changed

+68
-17
lines changed

1 file changed

+68
-17
lines changed

nord_nm_gui.py

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import shutil
88
import time
99
import prctl
10+
import keyring
1011
import subprocess
1112
import configparser
1213
from 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

12031254
if __name__ == '__main__':

0 commit comments

Comments
 (0)