From e276f8c8ed282eec2ae4eef8afbc997062b492ba Mon Sep 17 00:00:00 2001 From: ZEYI TONG Date: Sat, 21 Dec 2024 16:00:27 -0500 Subject: [PATCH 1/6] Signup & Login --- physiolabxr/__init__.py | 74 +++++++++++++++---------- physiolabxr/_ui/login.ui | 117 +++++++++++++++++++++++++++++++++++++++ physiolabxr/ui/Login.py | 110 ++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 30 deletions(-) create mode 100644 physiolabxr/_ui/login.ui create mode 100644 physiolabxr/ui/Login.py diff --git a/physiolabxr/__init__.py b/physiolabxr/__init__.py index ffdb3d4f..39f937fe 100644 --- a/physiolabxr/__init__.py +++ b/physiolabxr/__init__.py @@ -1,16 +1,17 @@ - - def physiolabxr(): import multiprocessing import sys + import webbrowser from PyQt6 import QtWidgets from PyQt6.QtGui import QIcon - from PyQt6.QtWidgets import QSystemTrayIcon, QMenu + from PyQt6.QtWidgets import QSystemTrayIcon, QMenu, QInputDialog, QMessageBox from physiolabxr.configs.configs import AppConfigs from physiolabxr.configs.NetworkManager import NetworkManager from physiolabxr.ui.SplashScreen import SplashScreen + from physiolabxr.ui.Login import LoginDialog + import firebase_admin AppConfigs(_reset=False) # create the singleton app configs object NetworkManager() @@ -30,30 +31,43 @@ def physiolabxr(): splash = SplashScreen() splash.show() - # load default settings - from physiolabxr.utils.setup_utils import run_setup_check - run_setup_check() - from physiolabxr.startup.startup import load_settings - load_settings(revert_to_default=False, reload_presets=False) - # main window init - print("Creating main window") - from physiolabxr.ui.MainWindow import MainWindow - window = MainWindow(app=app) - - window.setWindowIcon(QIcon(AppConfigs()._app_logo)) - # make tray menu - menu = QMenu() - exit_action = menu.addAction('Exit') - exit_action.triggered.connect(window.close) - - print("Closing splash screen, showing main window") - # splash screen destroy - splash.close() - window.show() - - print("Entering exec loop") - try: - sys.exit(app.exec()) - except KeyboardInterrupt: - print('App terminate by KeyboardInterrupt') - sys.exit() + # Initialize Firebase Admin SDK + if not firebase_admin._apps: + firebase_admin.initialize_app() + # Initialize the LoginDialog + login_dialog = LoginDialog() + + # Check if the user successfully logs in + if login_dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted: + # load default settings + from physiolabxr.utils.setup_utils import run_setup_check + run_setup_check() + from physiolabxr.startup.startup import load_settings + load_settings(revert_to_default=False, reload_presets=False) + # main window init + print("Creating main window") + from physiolabxr.ui.MainWindow import MainWindow + window = MainWindow(app=app) + + window.setWindowIcon(QIcon(AppConfigs()._app_logo)) + # make tray menu + menu = QMenu() + exit_action = menu.addAction('Exit') + exit_action.triggered.connect(window.close) + + print("Closing splash screen, showing main window") + # splash screen destroy + splash.close() + window.show() + + print("Entering exec loop") + try: + sys.exit(app.exec()) + except KeyboardInterrupt: + print('App terminate by KeyboardInterrupt') + sys.exit() + else: + # Exit if login is unsuccessful + splash.close() + QMessageBox.critical(None, "Access Denied", "Login required to access the application.") + sys.exit() \ No newline at end of file diff --git a/physiolabxr/_ui/login.ui b/physiolabxr/_ui/login.ui new file mode 100644 index 00000000..de935094 --- /dev/null +++ b/physiolabxr/_ui/login.ui @@ -0,0 +1,117 @@ + + + Dialog + + + + 0 + 0 + 425 + 358 + + + + Dialog + + + + + 130 + 10 + 191 + 20 + + + + Welcome to PhysioLabXR! + + + + + + 40 + 150 + 331 + 41 + + + + Log In + + + + + + 40 + 250 + 331 + 41 + + + + Sign Up + + + + + + 40 + 70 + 331 + 21 + + + + + + + 40 + 50 + 58 + 16 + + + + Email: + + + + + + 40 + 100 + 58 + 16 + + + + Password: + + + + + + 40 + 120 + 331 + 21 + + + + + + + 40 + 220 + 251 + 16 + + + + Doesn't have an account with us yet? + + + + + + diff --git a/physiolabxr/ui/Login.py b/physiolabxr/ui/Login.py new file mode 100644 index 00000000..a54afaa4 --- /dev/null +++ b/physiolabxr/ui/Login.py @@ -0,0 +1,110 @@ +import sys +import webbrowser +import requests +import firebase_admin +from firebase_admin import auth +from PyQt6 import QtCore, QtWidgets +from PyQt6.QtWidgets import QDialog, QMessageBox + + +class LoginDialog(QDialog): + def __init__(self): + super().__init__() + self.setupUi() + + def setupUi(self): + self.setObjectName("Dialog") + self.resize(425, 358) + + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(130, 10, 191, 20)) + self.label.setObjectName("label") + + self.pushButton = QtWidgets.QPushButton(self) + self.pushButton.setGeometry(QtCore.QRect(40, 150, 331, 41)) + self.pushButton.setObjectName("pushButton") + + self.pushButton_2 = QtWidgets.QPushButton(self) + self.pushButton_2.setGeometry(QtCore.QRect(40, 250, 331, 41)) + self.pushButton_2.setObjectName("pushButton_2") + + self.lineEdit = QtWidgets.QLineEdit(self) + self.lineEdit.setGeometry(QtCore.QRect(40, 70, 331, 21)) + self.lineEdit.setObjectName("lineEdit") + + self.label_2 = QtWidgets.QLabel(self) + self.label_2.setGeometry(QtCore.QRect(40, 50, 58, 16)) + self.label_2.setObjectName("label_2") + + self.label_3 = QtWidgets.QLabel(self) + self.label_3.setGeometry(QtCore.QRect(40, 100, 58, 16)) + self.label_3.setObjectName("label_3") + + self.lineEdit_2 = QtWidgets.QLineEdit(self) + self.lineEdit_2.setGeometry(QtCore.QRect(40, 120, 331, 21)) + self.lineEdit_2.setObjectName("lineEdit_2") + self.lineEdit_2.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) # Hide password input + + self.label_4 = QtWidgets.QLabel(self) + self.label_4.setGeometry(QtCore.QRect(40, 220, 251, 16)) + self.label_4.setObjectName("label_4") + + self.retranslateUi() + self.pushButton.clicked.connect(self.handle_login) + self.pushButton_2.clicked.connect(self.handle_signup) + + def retranslateUi(self): + self.setWindowTitle("Login") + self.label.setText("Welcome to PhysioLabXR!") + self.pushButton.setText("Log In") + self.pushButton_2.setText("Sign Up") + self.label_2.setText("Email:") + self.label_3.setText("Password:") + self.label_4.setText("Don't have an account yet?") + + def handle_login(self): + """Handle user login using Firebase Authentication.""" + email = self.lineEdit.text() + password = self.lineEdit_2.text() + + if not email or not password: + QMessageBox.warning(self, "Error", "Please enter both email and password.") + return + + try: + # Firebase REST API endpoint for sign-in + url = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword" + api_key = "AIzaSyD7CJXqoCPtv2GzMQpGLKwTo4MacPqjqnw" + data = { + "email": email, + "password": password, + "returnSecureToken": True, + } + response = requests.post(f"{url}?key={api_key}", json=data) + + if response.status_code == 200: + user_data = response.json() + QMessageBox.information(self, "Login Successful", f"Welcome back, {user_data['email']}!") + self.accept() # Close the dialog and indicate successful login + else: + error_message = response.json().get("error", {}).get("message", "Unknown error occurred.") + QMessageBox.critical(self, "Login Failed", f"An error occurred: {error_message}") + except Exception as e: + QMessageBox.critical(self, "Error", f"An unexpected error occurred: {e}") + + def handle_signup(self): + """Redirect user to the signup webpage.""" + signup_url = "https://storage.googleapis.com/physiolabxr.org/signup.html" + webbrowser.open(signup_url) + QMessageBox.information(self, "Redirecting", "You will be redirected to the signup page.") + + +if __name__ == "__main__": + # Initialize Firebase Admin SDK if not already initialized + if not firebase_admin._apps: + cred = firebase_admin.credentials.Certificate("path/to/serviceAccountKey.json") # Replace with your Google credentials path + firebase_admin.initialize_app(cred) + + app = QtWidgets.QApplication(sys.argv) + dialog = LoginDialog() + dialog.exec() From f6e63cc3bf2436567ff83aeafb121a9565228367 Mon Sep 17 00:00:00 2001 From: ZEYI TONG Date: Sat, 21 Dec 2024 17:38:31 -0500 Subject: [PATCH 2/6] modify Login.py to use environment variable to hide api key --- physiolabxr/ui/Login.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/physiolabxr/ui/Login.py b/physiolabxr/ui/Login.py index a54afaa4..bab305fc 100644 --- a/physiolabxr/ui/Login.py +++ b/physiolabxr/ui/Login.py @@ -1,8 +1,9 @@ +import os import sys import webbrowser import requests +from dotenv import load_dotenv import firebase_admin -from firebase_admin import auth from PyQt6 import QtCore, QtWidgets from PyQt6.QtWidgets import QDialog, QMessageBox @@ -74,7 +75,8 @@ def handle_login(self): try: # Firebase REST API endpoint for sign-in url = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword" - api_key = "AIzaSyD7CJXqoCPtv2GzMQpGLKwTo4MacPqjqnw" + load_dotenv() + api_key = os.getenv("FIREBASE_API_KEY") data = { "email": email, "password": password, From 1e7da61550418f695ef497c1b7814dec01d94c0c Mon Sep 17 00:00:00 2001 From: apocalyvec Date: Tue, 28 Jan 2025 14:05:08 -0500 Subject: [PATCH 3/6] update requirements --- requirements.dev.txt | 2 ++ requirements.txt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/requirements.dev.txt b/requirements.dev.txt index 2678e44f..11c9a738 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -22,3 +22,5 @@ imblearn toml grpcio grpcio-tools +python-dotenv +firebase-admin \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 010c90b3..ee186391 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,6 @@ matplotlib imblearn toml grpcio -grpcio-tools \ No newline at end of file +grpcio-tools +python-dotenv +firebase-admin \ No newline at end of file From 8359666e8d596bb02e647515eee6ecad49837a8f Mon Sep 17 00:00:00 2001 From: apocalyvec Date: Tue, 28 Jan 2025 14:05:37 -0500 Subject: [PATCH 4/6] update login ui --- physiolabxr/__init__.py | 2 ++ physiolabxr/ui/Login.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/physiolabxr/__init__.py b/physiolabxr/__init__.py index 39f937fe..054cd346 100644 --- a/physiolabxr/__init__.py +++ b/physiolabxr/__init__.py @@ -10,6 +10,7 @@ def physiolabxr(): from physiolabxr.configs.configs import AppConfigs from physiolabxr.configs.NetworkManager import NetworkManager from physiolabxr.ui.SplashScreen import SplashScreen + from physiolabxr.ui.SplashScreen import SplashLoadingTextNotifier from physiolabxr.ui.Login import LoginDialog import firebase_admin @@ -32,6 +33,7 @@ def physiolabxr(): splash.show() # Initialize Firebase Admin SDK + SplashLoadingTextNotifier().set_loading_text("Logging in...") if not firebase_admin._apps: firebase_admin.initialize_app() # Initialize the LoginDialog diff --git a/physiolabxr/ui/Login.py b/physiolabxr/ui/Login.py index bab305fc..43fb87dd 100644 --- a/physiolabxr/ui/Login.py +++ b/physiolabxr/ui/Login.py @@ -2,6 +2,8 @@ import sys import webbrowser import requests +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QFont from dotenv import load_dotenv import firebase_admin from PyQt6 import QtCore, QtWidgets @@ -18,7 +20,7 @@ def setupUi(self): self.resize(425, 358) self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(130, 10, 191, 20)) + self.label.setGeometry(QtCore.QRect(50, 10, 191, 20)) self.label.setObjectName("label") self.pushButton = QtWidgets.QPushButton(self) @@ -55,8 +57,14 @@ def setupUi(self): self.pushButton_2.clicked.connect(self.handle_signup) def retranslateUi(self): - self.setWindowTitle("Login") - self.label.setText("Welcome to PhysioLabXR!") + self.setWindowTitle("Welcome") + self.label.setText("Log into your PhysioLabXR account") + font = QFont() + font.setBold(True) # Make the text bold + font.setPointSize(20) # Set the font size + self.label.setFont(font) + self.label.adjustSize() + self.pushButton.setText("Log In") self.pushButton_2.setText("Sign Up") self.label_2.setText("Email:") From 93c95778bbf6a2ec989feadbcefd3be7cb0da46c Mon Sep 17 00:00:00 2001 From: ZEYI TONG Date: Wed, 12 Feb 2025 00:17:21 -0500 Subject: [PATCH 5/6] resolve conflicts in __init__.py --- physiolabxr/__init__.py | 85 ++++++++++++++++++++-------------- physiolabxr/_ui/login.ui | 17 ++++++- physiolabxr/_ui/mainwindow.ui | 24 ++++++++-- physiolabxr/configs/configs.py | 5 +- physiolabxr/ui/Login.py | 66 +++++++++++++++++++++++--- physiolabxr/ui/MainWindow.py | 36 +++++++++++++- requirements.txt | 1 - 7 files changed, 185 insertions(+), 49 deletions(-) diff --git a/physiolabxr/__init__.py b/physiolabxr/__init__.py index 054cd346..3bef454c 100644 --- a/physiolabxr/__init__.py +++ b/physiolabxr/__init__.py @@ -1,3 +1,6 @@ +import os + + def physiolabxr(): import multiprocessing import sys @@ -6,6 +9,7 @@ def physiolabxr(): from PyQt6 import QtWidgets from PyQt6.QtGui import QIcon from PyQt6.QtWidgets import QSystemTrayIcon, QMenu, QInputDialog, QMessageBox + from PyQt6.QtCore import QSettings from physiolabxr.configs.configs import AppConfigs from physiolabxr.configs.NetworkManager import NetworkManager @@ -13,6 +17,7 @@ def physiolabxr(): from physiolabxr.ui.SplashScreen import SplashLoadingTextNotifier from physiolabxr.ui.Login import LoginDialog import firebase_admin + from firebase_admin import auth, credentials AppConfigs(_reset=False) # create the singleton app configs object NetworkManager() @@ -32,44 +37,56 @@ def physiolabxr(): splash = SplashScreen() splash.show() + # Initialize Firebase Admin SDK SplashLoadingTextNotifier().set_loading_text("Logging in...") + + service_account_path = "/Users/zeyitong/Desktop/physiolabxr-8cbb7-firebase-adminsdk-pb9po-b5c9df1c77.json" + # Initialize Firebase Admin SDK (with credentials) + if not firebase_admin._apps: - firebase_admin.initialize_app() - # Initialize the LoginDialog + try: + cred = credentials.Certificate(service_account_path) # Replace with actual path + firebase_admin.initialize_app(cred) + except Exception as e: + QMessageBox.critical(None, "Firebase Init Failed", f"Could not initialize Firebase: {e}") + sys.exit(1) + login_dialog = LoginDialog() - # Check if the user successfully logs in - if login_dialog.exec() == QtWidgets.QDialog.DialogCode.Accepted: - # load default settings - from physiolabxr.utils.setup_utils import run_setup_check - run_setup_check() - from physiolabxr.startup.startup import load_settings - load_settings(revert_to_default=False, reload_presets=False) - # main window init - print("Creating main window") - from physiolabxr.ui.MainWindow import MainWindow - window = MainWindow(app=app) - - window.setWindowIcon(QIcon(AppConfigs()._app_logo)) - # make tray menu - menu = QMenu() - exit_action = menu.addAction('Exit') - exit_action.triggered.connect(window.close) - - print("Closing splash screen, showing main window") - # splash screen destroy - splash.close() - window.show() - - print("Entering exec loop") - try: - sys.exit(app.exec()) - except KeyboardInterrupt: - print('App terminate by KeyboardInterrupt') + # **🌟 Improved Auto-login Handling** + auto_login_success = login_dialog.auto_login() + + if not auto_login_success: # If auto-login fails, prompt login + if login_dialog.exec() != QtWidgets.QDialog.DialogCode.Accepted: + splash.close() + QMessageBox.critical(None, "Access Denied", "Login required to access the application.") sys.exit() - else: - # Exit if login is unsuccessful - splash.close() - QMessageBox.critical(None, "Access Denied", "Login required to access the application.") + + # load default settings + from physiolabxr.utils.setup_utils import run_setup_check + run_setup_check() + from physiolabxr.startup.startup import load_settings + load_settings(revert_to_default=False, reload_presets=False) + # main window init + print("Creating main window") + from physiolabxr.ui.MainWindow import MainWindow + window = MainWindow(app=app) + + window.setWindowIcon(QIcon(AppConfigs()._app_logo)) + # make tray menu + menu = QMenu() + exit_action = menu.addAction('Exit') + exit_action.triggered.connect(window.close) + + print("Closing splash screen, showing main window") + # splash screen destroy + splash.close() + window.show() + + print("Entering exec loop") + try: + sys.exit(app.exec()) + except KeyboardInterrupt: + print('App terminate by KeyboardInterrupt') sys.exit() \ No newline at end of file diff --git a/physiolabxr/_ui/login.ui b/physiolabxr/_ui/login.ui index de935094..6121fa23 100644 --- a/physiolabxr/_ui/login.ui +++ b/physiolabxr/_ui/login.ui @@ -43,7 +43,7 @@ 40 - 250 + 270 331 41 @@ -102,7 +102,7 @@ 40 - 220 + 240 251 16 @@ -111,6 +111,19 @@ Doesn't have an account with us yet? + + + + 80 + 200 + 251 + 20 + + + + Remember my account on this device + + diff --git a/physiolabxr/_ui/mainwindow.ui b/physiolabxr/_ui/mainwindow.ui index 1e2e0136..39f61305 100644 --- a/physiolabxr/_ui/mainwindow.ui +++ b/physiolabxr/_ui/mainwindow.ui @@ -62,8 +62,8 @@ 0 0 - 1554 - 857 + 1524 + 810 @@ -293,7 +293,7 @@ 0 0 1600 - 21 + 37 @@ -303,6 +303,7 @@ + @@ -312,8 +313,15 @@ + + + Account + + + + @@ -346,6 +354,16 @@ Settings + + + Sign out + + + + + Sign Out + + tabWidget diff --git a/physiolabxr/configs/configs.py b/physiolabxr/configs/configs.py index c75c89ef..64ba490d 100644 --- a/physiolabxr/configs/configs.py +++ b/physiolabxr/configs/configs.py @@ -79,7 +79,7 @@ class AppConfigs(metaclass=Singleton): _app_data_name: str = 'RenaLabApp' app_data_path = os.path.join(QStandardPaths.writableLocation(QStandardPaths.StandardLocation.AppDataLocation), _app_data_name) - + print("⚠️" + app_data_path) # appearance configs theme: str = 'dark' # TODO: refactor this to an enum, replace config.value @@ -130,6 +130,9 @@ class AppConfigs(metaclass=Singleton): # Tobii Pro Fusion Eye Tracker Manager tobii_app_path: str = None + # user login + remembered_uid: str = None + _media_paths = ['physiolabxr/_media/icons', 'physiolabxr/_media/logo', 'physiolabxr/_media/gifs'] _supported_media_formats = ['.svg', '.gif'] _ui_path = 'physiolabxr/_ui' diff --git a/physiolabxr/ui/Login.py b/physiolabxr/ui/Login.py index 43fb87dd..9653bd8c 100644 --- a/physiolabxr/ui/Login.py +++ b/physiolabxr/ui/Login.py @@ -6,8 +6,10 @@ from PyQt6.QtGui import QFont from dotenv import load_dotenv import firebase_admin +from firebase_admin import auth from PyQt6 import QtCore, QtWidgets from PyQt6.QtWidgets import QDialog, QMessageBox +from physiolabxr.configs.configs import AppConfigs class LoginDialog(QDialog): @@ -15,6 +17,7 @@ def __init__(self): super().__init__() self.setupUi() + def setupUi(self): self.setObjectName("Dialog") self.resize(425, 358) @@ -28,7 +31,7 @@ def setupUi(self): self.pushButton.setObjectName("pushButton") self.pushButton_2 = QtWidgets.QPushButton(self) - self.pushButton_2.setGeometry(QtCore.QRect(40, 250, 331, 41)) + self.pushButton_2.setGeometry(QtCore.QRect(40, 270, 331, 41)) self.pushButton_2.setObjectName("pushButton_2") self.lineEdit = QtWidgets.QLineEdit(self) @@ -49,9 +52,13 @@ def setupUi(self): self.lineEdit_2.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) # Hide password input self.label_4 = QtWidgets.QLabel(self) - self.label_4.setGeometry(QtCore.QRect(40, 220, 251, 16)) + self.label_4.setGeometry(QtCore.QRect(40, 240, 251, 16)) self.label_4.setObjectName("label_4") + self.checkBox = QtWidgets.QCheckBox(self) + self.checkBox.setGeometry(QtCore.QRect(80, 200, 251, 20)) + self.checkBox.setObjectName("checkBox") + self.retranslateUi() self.pushButton.clicked.connect(self.handle_login) self.pushButton_2.clicked.connect(self.handle_signup) @@ -70,9 +77,11 @@ def retranslateUi(self): self.label_2.setText("Email:") self.label_3.setText("Password:") self.label_4.setText("Don't have an account yet?") + self.checkBox.setText("Remember my account on this device") + def handle_login(self): - """Handle user login using Firebase Authentication.""" + """Handle user login using Firebase Authentication & Custom Token.""" email = self.lineEdit.text() password = self.lineEdit_2.text() @@ -82,37 +91,80 @@ def handle_login(self): try: # Firebase REST API endpoint for sign-in - url = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword" load_dotenv() api_key = os.getenv("FIREBASE_API_KEY") + url = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword" + data = { "email": email, "password": password, "returnSecureToken": True, } + response = requests.post(f"{url}?key={api_key}", json=data) if response.status_code == 200: user_data = response.json() - QMessageBox.information(self, "Login Successful", f"Welcome back, {user_data['email']}!") + uid = user_data.get("localId") + + # Generate a custom token using Firebase Admin SDK + custom_token = auth.create_custom_token(uid).decode("utf-8") + + if self.checkBox.isChecked(): + AppConfigs().remembered_uid = uid # Store UID instead of email + + print("✅ Login Successful - UID stored:", uid) + self.accept() # Close the dialog and indicate successful login + else: error_message = response.json().get("error", {}).get("message", "Unknown error occurred.") QMessageBox.critical(self, "Login Failed", f"An error occurred: {error_message}") + except Exception as e: QMessageBox.critical(self, "Error", f"An unexpected error occurred: {e}") + def auto_login(self): + """Auto-login using stored UID instead of email.""" + uid = AppConfigs().remembered_uid + print(f"🔍 Checking stored UID in AppConfigs: {uid}") + + if not uid: + print("❌ No remembered UID found, requiring manual login.") + return False + + try: + # Verify user existence using Firebase Admin SDK + user = auth.get_user(uid) + if user: + QMessageBox.information(self, "Auto Login", f"Welcome back, {user.email}!") + self.accept() + return True + else: + print("❌ UID not found in Firebase, clearing remembered user.") + AppConfigs().remembered_uid = None + return False + + except firebase_admin.auth.UserNotFoundError: + print("❌ User not found in Firebase, clearing remembered user.") + AppConfigs().remembered_uid = None + return False + except Exception as e: + QMessageBox.critical(self, "Auto Login Failed", f"An unexpected error occurred: {e}") + AppConfigs().remembered_uid = None + return False + def handle_signup(self): """Redirect user to the signup webpage.""" signup_url = "https://storage.googleapis.com/physiolabxr.org/signup.html" webbrowser.open(signup_url) QMessageBox.information(self, "Redirecting", "You will be redirected to the signup page.") - if __name__ == "__main__": # Initialize Firebase Admin SDK if not already initialized if not firebase_admin._apps: - cred = firebase_admin.credentials.Certificate("path/to/serviceAccountKey.json") # Replace with your Google credentials path + cred = firebase_admin.credentials.Certificate( + "path/to/serviceAccountKey.json") # Replace with your Google credentials path firebase_admin.initialize_app(cred) app = QtWidgets.QApplication(sys.argv) diff --git a/physiolabxr/ui/MainWindow.py b/physiolabxr/ui/MainWindow.py index 16adfa67..22ddb6b4 100644 --- a/physiolabxr/ui/MainWindow.py +++ b/physiolabxr/ui/MainWindow.py @@ -44,6 +44,8 @@ import numpy as np +from firebase_admin import auth + # Define function to import external files when using PyInstaller. def resource_path(relative_path): @@ -127,6 +129,7 @@ def __init__(self, app, ask_to_close=True, *args, **kwargs): self.actionShow_Recordings.triggered.connect(self.fire_action_show_recordings) self.actionExit.triggered.connect(self.fire_action_exit) self.actionSettings.triggered.connect(self.fire_action_settings) + self.actionSign_Out.triggered.connect(self.on_sign_out_triggered) # create the settings window self.settings_widget = SettingsWidget(self) @@ -518,4 +521,35 @@ def resizeEvent(self, a0): self.adjust_notification_panel_location() def adjust_notification_panel_location(self): - self.notification_panel.move(self.width() - self.notification_panel.width() - 9, self.height() - self.notification_panel.height() - self.recording_file_size_label.height() - 12) # substract 64 to account for margin \ No newline at end of file + self.notification_panel.move(self.width() - self.notification_panel.width() - 9, self.height() - self.notification_panel.height() - self.recording_file_size_label.height() - 12) # substract 64 to account for margin + + def on_sign_out_triggered(self): + """Handles user sign-out, clears remembered user data, and quits the app.""" + reply = QtWidgets.QMessageBox.question( + self, + 'Sign Out', + 'Signing out will close the app and remove your saved account login information. Are you sure?', + QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No, + QtWidgets.QMessageBox.StandardButton.No + ) + + if reply == QtWidgets.QMessageBox.StandardButton.Yes: + try: + # Get the current remembered user UID + remembered_uid = AppConfigs().remembered_uid + + if remembered_uid: + # Revoke Firebase session (Optional: Only if using Firebase Admin) + auth.revoke_refresh_tokens(remembered_uid) + + # Clear remembered user data + AppConfigs().remembered_uid = None + + # Display message + QMessageBox.information(self, "Signed Out", "You have been signed out.") + + # Close the app + self.ask_to_close = False + self.close() + except Exception as e: + QMessageBox.critical(self, "Sign Out Failed", f"Error while signing out: {e}") diff --git a/requirements.txt b/requirements.txt index ee186391..c5bbc046 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,6 @@ setuptools psutil numba PyOpenGL -PyOpenGL_accelerate soundfile matplotlib imblearn From f410f32ccfcb90fc07789485057d7057a99317cd Mon Sep 17 00:00:00 2001 From: ZEYI TONG Date: Thu, 6 Mar 2025 20:22:00 -0500 Subject: [PATCH 6/6] Fixed API key problem --- physiolabxr/__init__.py | 19 +------ physiolabxr/configs/configs.py | 3 +- physiolabxr/ui/Login.py | 101 ++++++++++++++++++++------------- physiolabxr/ui/MainWindow.py | 3 +- 4 files changed, 66 insertions(+), 60 deletions(-) diff --git a/physiolabxr/__init__.py b/physiolabxr/__init__.py index 3bef454c..88c75781 100644 --- a/physiolabxr/__init__.py +++ b/physiolabxr/__init__.py @@ -16,9 +16,6 @@ def physiolabxr(): from physiolabxr.ui.SplashScreen import SplashScreen from physiolabxr.ui.SplashScreen import SplashLoadingTextNotifier from physiolabxr.ui.Login import LoginDialog - import firebase_admin - from firebase_admin import auth, credentials - AppConfigs(_reset=False) # create the singleton app configs object NetworkManager() @@ -37,27 +34,13 @@ def physiolabxr(): splash = SplashScreen() splash.show() - - # Initialize Firebase Admin SDK SplashLoadingTextNotifier().set_loading_text("Logging in...") - service_account_path = "/Users/zeyitong/Desktop/physiolabxr-8cbb7-firebase-adminsdk-pb9po-b5c9df1c77.json" - # Initialize Firebase Admin SDK (with credentials) - - if not firebase_admin._apps: - try: - cred = credentials.Certificate(service_account_path) # Replace with actual path - firebase_admin.initialize_app(cred) - except Exception as e: - QMessageBox.critical(None, "Firebase Init Failed", f"Could not initialize Firebase: {e}") - sys.exit(1) - login_dialog = LoginDialog() - # **🌟 Improved Auto-login Handling** auto_login_success = login_dialog.auto_login() - if not auto_login_success: # If auto-login fails, prompt login + if not auto_login_success: if login_dialog.exec() != QtWidgets.QDialog.DialogCode.Accepted: splash.close() QMessageBox.critical(None, "Access Denied", "Login required to access the application.") diff --git a/physiolabxr/configs/configs.py b/physiolabxr/configs/configs.py index 64ba490d..805deec1 100644 --- a/physiolabxr/configs/configs.py +++ b/physiolabxr/configs/configs.py @@ -131,7 +131,8 @@ class AppConfigs(metaclass=Singleton): tobii_app_path: str = None # user login - remembered_uid: str = None + remembered_token: str = None + refresh_token: str = None _media_paths = ['physiolabxr/_media/icons', 'physiolabxr/_media/logo', 'physiolabxr/_media/gifs'] _supported_media_formats = ['.svg', '.gif'] diff --git a/physiolabxr/ui/Login.py b/physiolabxr/ui/Login.py index 9653bd8c..3368b829 100644 --- a/physiolabxr/ui/Login.py +++ b/physiolabxr/ui/Login.py @@ -12,6 +12,8 @@ from physiolabxr.configs.configs import AppConfigs +FIREBASE_API_KEY = "AIzaSyD7CJXqoCPtv2GzMQpGLKwTo4MacPqjqnw" + class LoginDialog(QDialog): def __init__(self): super().__init__() @@ -79,9 +81,8 @@ def retranslateUi(self): self.label_4.setText("Don't have an account yet?") self.checkBox.setText("Remember my account on this device") - def handle_login(self): - """Handle user login using Firebase Authentication & Custom Token.""" + """Handle user login using Firebase Authentication & Store Refresh Token.""" email = self.lineEdit.text() password = self.lineEdit_2.text() @@ -90,68 +91,88 @@ def handle_login(self): return try: - # Firebase REST API endpoint for sign-in - load_dotenv() - api_key = os.getenv("FIREBASE_API_KEY") - url = "https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword" - - data = { - "email": email, - "password": password, - "returnSecureToken": True, - } - - response = requests.post(f"{url}?key={api_key}", json=data) - - if response.status_code == 200: - user_data = response.json() - uid = user_data.get("localId") + url = f"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key={FIREBASE_API_KEY}" + data = {"email": email, "password": password, "returnSecureToken": True} + response = requests.post(url, json=data) + response_data = response.json() - # Generate a custom token using Firebase Admin SDK - custom_token = auth.create_custom_token(uid).decode("utf-8") + if "idToken" in response_data: + id_token = response_data["idToken"] + refresh_token = response_data["refreshToken"] + local_id = response_data["localId"] if self.checkBox.isChecked(): - AppConfigs().remembered_uid = uid # Store UID instead of email + AppConfigs().remembered_token = id_token + AppConfigs().refresh_token = refresh_token - print("✅ Login Successful - UID stored:", uid) + print(f"✅ Login Successful - User: {local_id}") - self.accept() # Close the dialog and indicate successful login + self.accept() # Close the dialog on success else: - error_message = response.json().get("error", {}).get("message", "Unknown error occurred.") + error_message = response_data.get("error", {}).get("message", "Unknown error occurred.") QMessageBox.critical(self, "Login Failed", f"An error occurred: {error_message}") except Exception as e: QMessageBox.critical(self, "Error", f"An unexpected error occurred: {e}") + def refresh_id_token(self): + """Refresh Firebase ID Token using the refresh token.""" + refresh_token = AppConfigs().refresh_token + + if not refresh_token: + print("❌ No refresh token found, user needs to log in again.") + return None + + try: + url = f"https://securetoken.googleapis.com/v1/token?key={FIREBASE_API_KEY}" + data = {"grant_type": "refresh_token", "refresh_token": refresh_token} + response = requests.post(url, json=data) + response_data = response.json() + + if "id_token" in response_data: + new_id_token = response_data["id_token"] + new_refresh_token = response_data["refresh_token"] + + # ✅ Update stored tokens + AppConfigs().remembered_token = new_id_token + AppConfigs().refresh_token = new_refresh_token + + print("🔄 Token refreshed successfully") + return new_id_token + + else: + print("❌ Failed to refresh token") + return None + + except Exception as e: + QMessageBox.critical(self, "Token Refresh Failed", f"An unexpected error occurred: {e}") + return None + def auto_login(self): - """Auto-login using stored UID instead of email.""" - uid = AppConfigs().remembered_uid - print(f"🔍 Checking stored UID in AppConfigs: {uid}") + """Auto-login using stored and refreshed token.""" + id_token = self.refresh_id_token() or AppConfigs().remembered_token - if not uid: - print("❌ No remembered UID found, requiring manual login.") + if not id_token: + print("❌ No valid token found, requiring manual login.") return False try: - # Verify user existence using Firebase Admin SDK - user = auth.get_user(uid) - if user: - QMessageBox.information(self, "Auto Login", f"Welcome back, {user.email}!") + url = f"https://identitytoolkit.googleapis.com/v1/accounts:lookup?key={FIREBASE_API_KEY}" + data = {"idToken": id_token} + response = requests.post(url, json=data) + + if response.status_code == 200: + print("✅ Auto-login successful") self.accept() return True else: - print("❌ UID not found in Firebase, clearing remembered user.") - AppConfigs().remembered_uid = None + print("❌ Invalid token, requiring manual login.") + AppConfigs().remembered_token = None return False - except firebase_admin.auth.UserNotFoundError: - print("❌ User not found in Firebase, clearing remembered user.") - AppConfigs().remembered_uid = None - return False except Exception as e: QMessageBox.critical(self, "Auto Login Failed", f"An unexpected error occurred: {e}") - AppConfigs().remembered_uid = None return False def handle_signup(self): diff --git a/physiolabxr/ui/MainWindow.py b/physiolabxr/ui/MainWindow.py index 22ddb6b4..82c49a97 100644 --- a/physiolabxr/ui/MainWindow.py +++ b/physiolabxr/ui/MainWindow.py @@ -543,7 +543,8 @@ def on_sign_out_triggered(self): auth.revoke_refresh_tokens(remembered_uid) # Clear remembered user data - AppConfigs().remembered_uid = None + AppConfigs().remembered_token = None + AppConfigs().refresh_token = None # Display message QMessageBox.information(self, "Signed Out", "You have been signed out.")