Skip to content

fix: detect email-based MFA in widget SSO login#371

Merged
cyberjunky merged 1 commit into
cyberjunky:masterfrom
jedrazb:fix/email-mfa-widget-login
Jun 14, 2026
Merged

fix: detect email-based MFA in widget SSO login#371
cyberjunky merged 1 commit into
cyberjunky:masterfrom
jedrazb:fix/email-mfa-widget-login

Conversation

@jedrazb

@jedrazb jedrazb commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Problem

The widget+cffi login strategy only treats the post-login page as an MFA challenge when its HTML <title> contains the literal substring "MFA":

if "MFA" in title:
    ...
    raise _MFARequired()

if title != "Success":
    raise GarminConnectConnectionError(f"Widget login: unexpected title '{title}'")

That works for authenticator-app (TOTP) MFA, whose page is titled Enter MFA code for login. But email-based one-time-code MFA returns a page titled GARMIN Authentication Application, which contains no "MFA". Login therefore falls through to the generic Widget login: unexpected title '...' error and the user is never prompted for the emailed code.

Garmin permanently enforces MFA on ECG-capable devices (e.g. Venu 4, Fenix 8), where email is a common method — so affected accounts can't authenticate via the widget flow at all. This is the same symptom previously reported for the old garth flow in #242.

Fix

Broaden the MFA detection in _widget_web_login to recognize the email-MFA title as well, reusing the already-computed title_lower:

if "mfa" in title_lower or "authentication application" in title_lower:

No change is needed to MFA completion: both variants are already verified through the same /sso/verifyMFA/loginEnterMfaCode endpoint in _complete_mfa_widget.

Verification

  • ✅ Verified end-to-end against a live email-MFA accountlogin() now prompts for the emailed code and authenticates successfully.
  • ✅ Adds in-process unit tests (no network) for both MFA title variants and a negative case ensuring unrelated titles still raise the unexpected title error.
  • ✅ Full in-process suite passes (test_widget_mfa, test_login_recovery, test_garmin_unit, test_retry_decorator → 101 passed); ruff check garminconnect/client.py clean.

Scope

One-line detection change plus tests. No behavioral change for TOTP MFA, successful logins, or the credential/error paths (the negative test guards against over-broad matching).

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Enhanced multi-factor authentication detection during Garmin Connect login to recognize additional authentication page variants, improving reliability and reducing unexpected errors during the sign-in process.

The widget+cffi login strategy only recognized an MFA challenge when the
returned page <title> contained the literal substring "MFA", which matches
the authenticator-app (TOTP) page "Enter MFA code for login". Email-based
one-time-code MFA instead returns a page titled "GARMIN Authentication
Application", so login fell through to the generic "unexpected title" error
and never prompted for the code.

Garmin permanently enforces MFA on ECG-capable devices (e.g. Venu 4,
Fenix 8) and email is a common method there, so affected accounts could not
authenticate at all via the widget flow.

Broaden the detection to also match the email-MFA title. No completion change
is needed — both variants are already verified through the same
/sso/verifyMFA/loginEnterMfaCode endpoint. Verified end-to-end against a live
email-MFA account.

Adds in-process tests covering both MFA title variants and a negative case
ensuring unrelated titles still raise the unexpected-title error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR refines widget SSO login MFA detection to recognize both authenticator-app and email one-time-code page title variants with case-insensitive matching, replacing a narrow substring check. It includes a new test module that validates both positive (MFA detection) and negative (unexpected title rejection) behaviors using a fully mocked session.

Changes

Widget MFA Detection

Layer / File(s) Summary
Widget MFA detection logic update
garminconnect/client.py
The _widget_web_login method replaces its "MFA" substring check with a case-insensitive check for either "mfa" or "authentication application" in the response title, with explanatory comments on the two MFA variants and notes about child restriction edge cases.
MFA detection validation tests
tests/test_widget_mfa.py
New test module with a minimal _FakeSession mock that returns CSRF HTML for GET and parameterized page bodies for POST, a context manager to wire the mock and stub sleeps, and parameterized tests asserting _MFARequired is raised for both known MFA titles with correct widget state fields set, plus a negative test confirming unknown titles trigger GarminConnectConnectionError.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: detecting email-based MFA in widget SSO login, which directly addresses the core problem described in the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cyberjunky

cyberjunky commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Are you 100% sure? I tested each and every strategy with MFA in latest version, I even wrote a script to do it. Its in the package.

@cyberjunky

Copy link
Copy Markdown
Owner

And I have only email based MFA enabled, never used app.

@jedrazb

jedrazb commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Hey @cyberjunky I have fenix 8 with ECG recordings and Garmin forced me to use MFA (email was default). I ran into this exact issue Taxuspt/garmin_mcp#109 and the fix in this branch got me authenticated successfully.

Let me know if you need more details

@cyberjunky
cyberjunky merged commit b3d7fd8 into cyberjunky:master Jun 14, 2026
1 check passed
cyberjunky added a commit that referenced this pull request Jun 14, 2026
… bump 0.3.6

Follow-up to PR #371 (email-MFA detection in the widget flow):
- add tests for _complete_mfa_widget — the completion step that actually
  logs an email-/TOTP-MFA user in (success ticket exchange, missing
  context, rejected/expired code). PR #371 covered detection only.
- add test_widget_mfa to the [[tool.mypy.overrides]] allowlist so
  `mypy garminconnect tests` (local lint) stays green; CI was unaffected
  (it only type-checks garminconnect/).
- bump version to 0.3.6.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants