working
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,86 +1,38 @@
|
||||
import garth
|
||||
import garminconnect
|
||||
from garth.exc import GarthException
|
||||
from datetime import datetime, timedelta
|
||||
from uuid import uuid4
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from src.utils.helpers import setup_logger
|
||||
from datetime import datetime, timedelta
|
||||
from garth.exc import GarthException
|
||||
from src.models.api_token import APIToken
|
||||
from src.services.postgresql_manager import PostgreSQLManager
|
||||
from src.utils.config import config
|
||||
from src.utils.helpers import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
class AuthMixin:
|
||||
def login(self):
|
||||
"""Login to Garmin Connect, handling MFA."""
|
||||
logger.info(f"Starting login process for Garmin user: {self.username}")
|
||||
|
||||
"""Login to Garmin Connect, returning status instead of raising exceptions."""
|
||||
logger.info(f"Starting login for: {self.username}")
|
||||
try:
|
||||
# result1 is status, result2 is the mfa_state dict or tokens
|
||||
result1, result2 = garth.login(self.username, self.password, return_on_mfa=True)
|
||||
|
||||
if result1 == "needs_mfa":
|
||||
logger.info("MFA required for Garmin authentication.")
|
||||
self.initiate_mfa(result2)
|
||||
raise Exception("MFA Required: Please provide verification code")
|
||||
|
||||
logger.info(f"Successfully logged in to Garmin Connect as {self.username}")
|
||||
self.initiate_mfa(result2) # Fixed below
|
||||
return "mfa_required"
|
||||
|
||||
self.update_tokens(result1, result2)
|
||||
self.is_connected = True
|
||||
|
||||
return "success"
|
||||
except GarthException as e:
|
||||
logger.error(f"GarthException during login for {self.username}: {e}")
|
||||
raise Exception(f"Garmin authentication failed: {e}")
|
||||
|
||||
def initiate_mfa(self, mfa_state):
|
||||
"""Saves MFA state to the database."""
|
||||
logger.info(f"Initiating MFA process for user: {self.username}")
|
||||
db_manager = PostgreSQLManager(config.DATABASE_URL)
|
||||
with db_manager.get_db_session() as session:
|
||||
token_record = session.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if not token_record:
|
||||
token_record = APIToken(token_type='garmin')
|
||||
session.add(token_record)
|
||||
|
||||
token_record.mfa_state = json.dumps(mfa_state)
|
||||
token_record.mfa_expires_at = datetime.now() + timedelta(minutes=10)
|
||||
session.commit()
|
||||
logger.info(f"MFA state saved for user: {self.username}")
|
||||
|
||||
def handle_mfa(self, verification_code: str):
|
||||
"""Completes authentication using MFA code."""
|
||||
logger.info(f"Handling MFA for user: {self.username}")
|
||||
db_manager = PostgreSQLManager(config.DATABASE_URL)
|
||||
with db_manager.get_db_session() as session:
|
||||
token_record = session.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if not token_record or not token_record.mfa_state:
|
||||
raise Exception("No pending MFA session found.")
|
||||
|
||||
if token_record.mfa_expires_at and datetime.now() > token_record.mfa_expires_at:
|
||||
raise Exception("MFA session expired.")
|
||||
|
||||
mfa_state = json.loads(token_record.mfa_state)
|
||||
|
||||
try:
|
||||
oauth1, oauth2 = garth.resume_login(mfa_state, verification_code)
|
||||
self.update_tokens(oauth1, oauth2)
|
||||
|
||||
token_record.mfa_state = None
|
||||
token_record.mfa_expires_at = None
|
||||
session.commit()
|
||||
|
||||
self.is_connected = True
|
||||
logger.info(f"MFA authentication successful for user: {self.username}")
|
||||
return True
|
||||
except GarthException as e:
|
||||
logger.error(f"MFA handling failed for {self.username}: {e}")
|
||||
raise
|
||||
logger.error(f"Login failed: {e}")
|
||||
return "error"
|
||||
|
||||
def update_tokens(self, oauth1, oauth2):
|
||||
"""Saves OAuth tokens to the database."""
|
||||
logger.info(f"Updating tokens for user: {self.username}")
|
||||
"""Saves the Garmin OAuth tokens to the database."""
|
||||
logger.info(f"Updating Garmin tokens for user: {self.username}")
|
||||
|
||||
db_manager = PostgreSQLManager(config.DATABASE_URL)
|
||||
with db_manager.get_db_session() as session:
|
||||
token_record = session.query(APIToken).filter_by(token_type='garmin').first()
|
||||
@@ -90,10 +42,63 @@ class AuthMixin:
|
||||
|
||||
token_record.garth_oauth1_token = json.dumps(oauth1)
|
||||
token_record.garth_oauth2_token = json.dumps(oauth2)
|
||||
token_record.updated_at = datetime.now()
|
||||
|
||||
# Clear MFA state as it's no longer needed
|
||||
token_record.mfa_state = None
|
||||
token_record.mfa_expires_at = None
|
||||
|
||||
session.commit()
|
||||
logger.info(f"Tokens successfully updated for user: {self.username}")
|
||||
logger.info("Garmin tokens updated successfully.")
|
||||
|
||||
def load_tokens(self):
|
||||
"""Load garth tokens to resume a session."""
|
||||
logger.info(f"Starting token loading process for user: {self.username}")
|
||||
# ... (rest of the load_tokens method remains the same)
|
||||
def initiate_mfa(self, mfa_state):
|
||||
"""Saves ONLY serializable parts of the MFA state to the database."""
|
||||
logger.info(f"Initiating MFA process for user: {self.username}")
|
||||
|
||||
# FIX: Extract serializable data. We cannot dump the 'client' object directly.
|
||||
serializable_state = {
|
||||
"signin_params": mfa_state["signin_params"],
|
||||
"cookies": mfa_state["client"].sess.cookies.get_dict(),
|
||||
"domain": mfa_state["client"].domain
|
||||
}
|
||||
|
||||
db_manager = PostgreSQLManager(config.DATABASE_URL)
|
||||
with db_manager.get_db_session() as session:
|
||||
token_record = session.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if not token_record:
|
||||
token_record = APIToken(token_type='garmin')
|
||||
session.add(token_record)
|
||||
|
||||
# Save the dictionary as a string
|
||||
token_record.mfa_state = json.dumps(serializable_state)
|
||||
token_record.mfa_expires_at = datetime.now() + timedelta(minutes=10)
|
||||
session.commit()
|
||||
|
||||
def handle_mfa(self, verification_code: str, session_id: str = None):
|
||||
"""Reconstructs the Garth state and completes authentication."""
|
||||
db_manager = PostgreSQLManager(config.DATABASE_URL)
|
||||
with db_manager.get_db_session() as session:
|
||||
token_record = session.query(APIToken).filter_by(token_type='garmin').first()
|
||||
if not token_record or not token_record.mfa_state:
|
||||
raise Exception("No pending MFA session found.")
|
||||
|
||||
saved_data = json.loads(token_record.mfa_state)
|
||||
|
||||
# FIX: Reconstruct the Garth Client and State object
|
||||
from garth.http import Client
|
||||
client = Client(domain=saved_data["domain"])
|
||||
client.sess.cookies.update(saved_data["cookies"])
|
||||
|
||||
mfa_state = {
|
||||
"client": client,
|
||||
"signin_params": saved_data["signin_params"]
|
||||
}
|
||||
|
||||
try:
|
||||
oauth1, oauth2 = garth.resume_login(mfa_state, verification_code)
|
||||
self.update_tokens(oauth1, oauth2)
|
||||
# ... rest of your session cleanup ...
|
||||
return True
|
||||
except GarthException as e:
|
||||
logger.error(f"MFA handling failed: {e}")
|
||||
raise
|
||||
@@ -32,3 +32,11 @@ class GarminClient(AuthMixin, DataMixin):
|
||||
except:
|
||||
self.is_connected = False
|
||||
return False
|
||||
|
||||
def get_profile_info(self):
|
||||
"""Get user profile information."""
|
||||
if not self.is_connected:
|
||||
self.login()
|
||||
if self.is_connected:
|
||||
return garth.UserProfile.get()
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user