Skip to content

Use dynamic MFA method from login response#215

Merged
matin merged 1 commit intomatin:mainfrom
tobias-goertz:fix/dynamic-mfa-method
Mar 19, 2026
Merged

Use dynamic MFA method from login response#215
matin merged 1 commit intomatin:mainfrom
tobias-goertz:fix/dynamic-mfa-method

Conversation

@tobias-goertz
Copy link
Copy Markdown
Contributor

@tobias-goertz tobias-goertz commented Mar 18, 2026

Summary

  • Read mfaLastMethodUsed from the login response (customerMfaInfo) instead of hardcoding "email"
  • Pass it through to handle_mfa() and resume_login()
  • Users with SMS/phone MFA were getting MFA_CODE_INVALID because the verify endpoint received "email" instead of "phone"

Changes

  • login(): extract mfaLastMethodUsed from response, include in return_on_mfa state
  • handle_mfa(): accept mfa_method keyword argument (default "email" for backwards compat)
  • resume_login(): forward mfa_method from client_state

Test plan

  • Tested locally with SMS MFA account — previously failed with MFA_CODE_INVALID, now works
  • Email MFA accounts unaffected (same default value)

Summary by CodeRabbit

  • Improvements

    • Multi-factor authentication now remembers your last-used verification method and consistently applies it throughout the login flow (MFA prompt, verification, and resume). Email remains the default if no prior preference exists.
  • Chores

    • Package version updated.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 451b7c73-bccc-42b4-b134-026cc724e519

📥 Commits

Reviewing files that changed from the base of the PR and between fa5df3e and e9edcf6.

📒 Files selected for processing (2)
  • src/garth/sso.py
  • src/garth/version.py
✅ Files skipped from review due to trivial changes (1)
  • src/garth/version.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/garth/sso.py

Walkthrough

When MFA is required during login, the code extracts the last-used MFA method from customerMfaInfo (defaulting to "email") and propagates that method through the login flow, including the needs_mfa response, handle_mfa signature, verification, and token-exchange steps.

Changes

Cohort / File(s) Summary
MFA Method Propagation
src/garth/sso.py
Extracts last-used MFA method from customerMfaInfo (defaults to "email"), includes mfa_method in the needs_mfa response payload, updates handle_mfa signature to accept *, mfa_method: str = "email", and uses the provided mfa_method during verification and token exchange.
Version Bump
src/garth/version.py
Incremented __version__ from "0.7.9" to "0.7.10".

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SSO as SSO Service
    participant MFA as MFA Provider
    participant Auth as Auth Server

    Client->>SSO: login(credentials)
    SSO-->>Client: needs_mfa(mfa_method)
    Client->>SSO: submit_mfa(code, mfa_method)
    SSO->>MFA: verify(code, mfa_method)
    MFA-->>SSO: verification_result
    SSO->>Auth: exchange_for_token(verification_result)
    Auth-->>SSO: tokens
    SSO-->>Client: tokens
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • felipao-mx
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: using dynamic MFA method from login response instead of hardcoding 'email'.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/garth/sso.py`:
- Around line 136-137: mfa_method currently uses
mfa_info.get("mfaLastMethodUsed", "email") which only uses the default when the
key is missing, so falsy values like None or "" get preserved; change both
occurrences to coerce falsy values to "email" (e.g. set mfa_method =
mfa_info.get("mfaLastMethodUsed") or "email") and ensure that same sanitized
mfa_method is what you store in client_state and send to
/mobile/api/mfa/verifyCode so null/empty strings never get posted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cc9cbd9a-0ef1-4f0c-a9b3-a82d6f2d8d7b

📥 Commits

Reviewing files that changed from the base of the PR and between a3bac03 and 473bec6.

📒 Files selected for processing (1)
  • src/garth/sso.py

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/garth/sso.py (1)

205-221: ⚠️ Potential issue | 🟡 Minor

Normalize falsy mfa_method values at the verify boundary.

Line 245 can still forward persisted None/"" values, and Line 221 posts them verbatim. That leaves the old MFA_CODE_INVALID path open for resumed MFA sessions and any direct caller that passes a falsy method.

🛠️ Proposed fix
 def handle_mfa(
     client: http.Client,
     login_params: dict,
     prompt_mfa: Callable,
     *,
     mfa_method: str = "email",
 ) -> str:
+    mfa_method = mfa_method or "email"
     if inspect.iscoroutinefunction(prompt_mfa):
         mfa_code = asyncio.run(prompt_mfa())
@@
-    mfa_method = client_state.get("mfa_method", "email")
+    mfa_method = client_state.get("mfa_method") or "email"

Also applies to: 245-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/garth/sso.py` around lines 205 - 221, The handler handle_mfa currently
forwards falsy mfa_method values (None or empty string) to the verify API;
normalize the value before sending by replacing any falsy mfa_method with the
default "email" (e.g., mfa_method = mfa_method or "email") and use that
normalized variable in the client.post JSON payload; apply the same
normalization to the other MFA call block referenced (the one around the later
verify/submit code at the 245-248 area) so no direct caller or resumed session
can send a falsy mfa_method to the server.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/garth/sso.py`:
- Around line 205-221: The handler handle_mfa currently forwards falsy
mfa_method values (None or empty string) to the verify API; normalize the value
before sending by replacing any falsy mfa_method with the default "email" (e.g.,
mfa_method = mfa_method or "email") and use that normalized variable in the
client.post JSON payload; apply the same normalization to the other MFA call
block referenced (the one around the later verify/submit code at the 245-248
area) so no direct caller or resumed session can send a falsy mfa_method to the
server.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 41874b04-c766-4c48-ae95-519ee8db5aeb

📥 Commits

Reviewing files that changed from the base of the PR and between 473bec6 and fa5df3e.

📒 Files selected for processing (1)
  • src/garth/sso.py

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.91%. Comparing base (a3bac03) to head (e9edcf6).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #215   +/-   ##
=======================================
  Coverage   99.91%   99.91%           
=======================================
  Files          68       68           
  Lines        3566     3569    +3     
=======================================
+ Hits         3563     3566    +3     
  Misses          3        3           
Flag Coverage Δ
unittests 99.91% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@matin
Copy link
Copy Markdown
Owner

matin commented Mar 18, 2026

@tobias-goertz looks good. can you bump the version?

I'll merge and release

thanks for the contribution!

The MFA verification endpoint requires the correct mfaMethod value
("email", "phone", etc.) but it was hardcoded to "email". Users with
SMS/phone MFA got MFA_CODE_INVALID errors.

Read mfaLastMethodUsed from the login response's customerMfaInfo and
pass it through to handle_mfa() and resume_login().

Coerce falsy mfaLastMethodUsed to "email"

If the key exists but is null/empty, .get() returns None instead of
the default. Use `or "email"` to handle that case.

bump version
@tobias-goertz tobias-goertz force-pushed the fix/dynamic-mfa-method branch from fa5df3e to e9edcf6 Compare March 18, 2026 21:13
@tobias-goertz
Copy link
Copy Markdown
Contributor Author

@matin done, thanks!

@matin matin merged commit 9760c41 into matin:main Mar 19, 2026
23 checks passed
matin added a commit that referenced this pull request Mar 19, 2026
PR #215 re-introduced browser headers on the login POST and MFA
verify endpoints. These are mobile API endpoints that expect the
default GCM-iOS user agent — browser headers cause 401/429 from
Garmin. Same regression as 0.7.7, fixed in 0.7.8, re-introduced
in 0.7.10.

Browser headers should ONLY be on page requests (sign-in, embed).

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
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