mirror of
https://github.com/sstent/FitTrack_GarminSync.git
synced 2026-01-25 16:41:41 +00:00
55 lines
5.6 KiB
Markdown
55 lines
5.6 KiB
Markdown
---
|
|
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.
|