Files
FitTrack_GarminSync/specs/009-fix-garminconnect-login-with-garth/spec.md

5.6 KiB

description
description
Specification for resolving garminconnect login failure and implementing garth MFA

009: Fix Garmin Connect Login Failure and Implement Garth MFA

1. Problem Statement

The GarminSync backend service has been encountering persistent login failures with garminconnect due to recent changes in Garmin's Single Sign-On (SSO) process. The specific error observed in the logs is Login failed: Unexpected title: GARMIN Authentication Application. This issue prevents users from authenticating with Garmin Connect, especially those with Multi-Factor Authentication (MFA) enabled, severely impacting the service's core functionality. The existing implementation in backend/src/services/garmin_auth_service.py was relying on garminconnect's internal login method, which proved brittle against Garmin's evolving authentication flow.

2. Proposed Solution

The solution involves refactoring the authentication mechanism within the GarminAuthService to primarily leverage the garth library for direct login and robust MFA handling. garth is known for its resilience to Garmin's authentication changes and its explicit support for MFA flows. Once garth successfully establishes a session, garminconnect will implicitly pick up this session, thereby bypassing garminconnect's problematic internal login process.

3. Technical Details

3.1. Modified Files

  • backend/src/services/garmin_auth_service.py:
    • Imports: Replaced garminconnect.Garmin import with garth and garth.exc.GarthException.
    • _perform_login method: Refactored to use garth.Client().login(email=username, password=password). This method now returns a garth.Client instance and is responsible for initiating the core garth login. It also raises GarthException if MFA is required, which is then handled by the calling method.
    • initial_login method: Modified to call the refactored _perform_login. It now handles GarthException to detect when MFA is required, returning a structured dictionary response ({"success": False, "mfa_required": True, ...}) to indicate the need for MFA input. The return type was updated from Optional[GarminCredentials] to Dict[str, Any].
    • complete_mfa_login method: A new asynchronous method added to GarminAuthService. This method takes username, password, and mfa_code, and uses garth.Client().login(email=username, password=password, mfa_token=mfa_code) to complete the MFA-enabled login. It returns structured dictionary responses for success or failure, including GarminCredentials on successful authentication.
    • GarminCredentials Instantiation: Accesses to display_name, full_name, and unit_system attributes are now directly from the garth.Client instance (e.g., client.display_name) rather than a garminconnect.Garmin instance, as garth populates these.
    • Static Analysis Fixes: Corrected typing imports to include Any and Dict, and removed unused TextIO. Suppressed mypy attr-defined errors for garth.Client attributes using # type: ignore comments.
  • backend/src/api/garmin_sync.py: Sorted imports using ruff.

3.2. Authentication Flow Changes

The new authentication flow in the backend service is as follows:

  1. Initial Login Attempt: The initial_login method attempts a login using garth.
  2. MFA Detection: If garth detects that MFA is required, initial_login returns a response indicating this, prompting the client (e.g., CLI) to request an MFA code from the user.
  3. MFA Completion: The client then calls the complete_mfa_login method with the provided MFA code. This method attempts to finalize the garth login.
  4. Session Establishment: Upon successful login (either initial or after MFA), garth automatically manages the session tokens. garminconnect.Garmin instances, when initialized without credentials, will then implicitly use this established garth session.

4. Acceptance Criteria

4.1. Functional Requirements

  • FR1: Users with Garmin Connect accounts (both with and without MFA enabled) shall be able to successfully authenticate with the GarminSync backend service.
  • FR2: The initial_login endpoint/method shall correctly detect and indicate when MFA is required for a user account.
  • FR3: The complete_mfa_login endpoint/method shall successfully process a provided MFA code to complete the authentication for MFA-enabled accounts.
  • FR4: Upon successful authentication, the backend service shall return GarminCredentials containing valid session and token information.
  • FR5: The garminconnect library, when used for subsequent API calls (e.g., fetching activities), shall successfully utilize the session established by garth without requiring a separate login.

4.2. Quality Attributes

  • QA1 - Robustness: The authentication flow shall be resilient to changes in Garmin's SSO page structure (as handled by garth).
  • QA2 - Security: While garmin_password_plaintext is still present, the change ensures the primary authentication uses garth's secure methods. (Note: Removal of plaintext password storage is a future task).
  • QA3 - Maintainability: The code changes adhere to Python best practices and pass static analysis checks (ruff, mypy).

5. Verification

The fix can be verified by deploying the updated backend service and attempting to log in with various Garmin Connect accounts, including those protected by MFA, using a compatible client (e.g., the CLI). Success is measured by the ability to authenticate and subsequently fetch data via garminconnect methods. Static analysis with ruff check and mypy passed.