Skip to content

Migrate SSO to mobile JSON API, enable telemetry by default#200

Merged
matin merged 6 commits intomainfrom
mobile-sso-flow
Mar 18, 2026
Merged

Migrate SSO to mobile JSON API, enable telemetry by default#200
matin merged 6 commits intomainfrom
mobile-sso-flow

Conversation

@matin
Copy link
Copy Markdown
Owner

@matin matin commented Mar 18, 2026

Summary

  • Migrate SSO authentication from HTML form flow to Garmin's new mobile JSON API endpoints (/mobile/api/login, /mobile/api/mfa/verifyCode), fixing 403 "Client app validation failed" errors on OAuth2 token refresh
  • Enable telemetry by default with a built-in logfire write token and per-session tracking, so auth issues can be diagnosed from logfire logs
  • Bump version to 0.7.0 (breaking: pydantic>=2.0 required, logfire now a core dependency)

Breaking changes

  • Requires pydantic>=2.0.0 (drops v1 support)
  • logfire and pydantic-settings are now core dependencies
  • garth[telemetry] extra removed (telemetry is always available)
  • telemetry_service_name parameter removed from configure()
  • return_on_mfa client state dict now has login_params key instead of signin_params

Test plan

  • All 158 tests pass
  • Linting and mypy clean
  • VCR cassettes re-recorded for new SSO endpoints (MFA + non-MFA)
  • Manual end-to-end login verified with real Garmin credentials + MFA
  • CI passes

Closes #198

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Telemetry enabled by default with per-session IDs for easier diagnostics
    • Updated sign-in flow with improved mobile SSO and MFA handling
  • Documentation

    • Telemetry docs revised for default-enabled behavior and simplified env-var configuration
    • Auth issue template updated to request telemetry session IDs for troubleshooting
  • Chores

    • Package version bumped to 0.7.0

matin and others added 3 commits March 17, 2026 22:54
Garmin has migrated their SSO authentication endpoints. The old HTML
form-based flow (/sso/embed, /sso/signin with CSRF tokens) is being
replaced by a mobile JSON API flow (/mobile/api/login,
/mobile/api/mfa/verifyCode). Some users were hitting 403 "Client app
validation failed" errors on OAuth2 token refresh.

Changes:
- Rewrite login() to use /mobile/api/login JSON endpoint
- Rewrite handle_mfa() to use /mobile/api/mfa/verifyCode JSON endpoint
- Update get_oauth1_token() login-url to mobile.integration.{domain}
- Add audience parameter to OAuth2 exchange
- Add _parse_sso_response() for structured SSO error messages
- Remove CSRF token and HTML title parsing (no longer needed)
- Update User-Agent to GCM-iOS-5.22.1.4
- Fix logfire warning when telemetry enabled but not configured
- Re-record all login-related VCR cassettes

Closes #198

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Telemetry is now on by default so auth issues can be diagnosed from
logfire logs. Each session gets a unique session_id that users can
share when reporting issues.

Changes:
- Default telemetry to enabled with built-in logfire write token
- Use pydantic-settings (GARTH_TELEMETRY_ prefix) for config
- Use logfire.configure(local=True) to avoid overwriting host app config
- Include session_id in every log entry for per-session tracking
- Move logfire + pydantic-settings to core dependencies
- Remove service_name from public API (hardcoded to "garth")
- Update auth issue template to ask for session ID
- Disable telemetry in test conftest to prevent network requests

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Users need their session_id to share when reporting auth issues.
Print it once at Client init so it's visible without extra code.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

Warning

Ignoring CodeRabbit configuration file changes. For security, only the configuration from the base branch is applied for open source repositories.

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: 18e7ae44-dd14-4d1b-9097-af5f9a344a94

📥 Commits

Reviewing files that changed from the base of the PR and between 0f10334 and a692648.

📒 Files selected for processing (7)
  • .coderabbit.yaml
  • CLAUDE.md
  • docs/telemetry.md
  • src/garth/data/body_battery/events.py
  • src/garth/http.py
  • src/garth/sso.py
  • tests/test_sso.py
💤 Files with no reviewable changes (1)
  • src/garth/data/body_battery/events.py
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/garth/http.py

Walkthrough

Refactors telemetry into a Pydantic BaseSettings model (enabled by default with session_id), rewrites SSO login to a JSON mobile API flow with explicit MFA ticket handling, updates telemetry wiring in the HTTP client, upgrades dependencies (pydantic, logfire), updates docs/templates, and bumps version to 0.7.0.

Changes

Cohort / File(s) Summary
Telemetry & HTTP client
src/garth/telemetry.py, src/garth/http.py, tests/test_telemetry.py, tests/conftest.py, pyproject.toml
Telemetry migrated from dataclass to Pydantic BaseSettings, enabled by default, adds UUID session_id, new logfire local instance path, response hook injects session_id and request/response payloads, removed service_name configure param; client logs session_id on init; tests disable telemetry via autouse fixture; pydantic and logfire deps updated.
SSO / MFA flows
src/garth/sso.py, tests/test_sso.py, tests/test_http.py
Replaces HTML-based SSO with JSON mobile API endpoints, adds _parse_sso_response, introduces CLIENT_ID, login_params, handle_mfa posting to /mobile/api/mfa/verifyCode, returns a ticket for new _complete_login which performs OAuth1/OAuth2 exchanges; updates signatures and test expectations (signin_params → login_params).
Docs, templates, and metadata
docs/telemetry.md, README.md, .github/ISSUE_TEMPLATE/auth-issue.yml, CLAUDE.md, .coderabbit.yaml
Docs updated to show telemetry enabled-by-default, env-var config (GARTH_TELEMETRY_*), session_id exposure in payloads/logs and redaction guidance; README feature bullet added; issue template requests Telemetry session ID; review guidance added for DEFAULT_TOKEN.
Version & small changes
src/garth/version.py, src/garth/data/body_battery/events.py
Version bumped to 0.7.0; removed module-level MAX_WORKERS constant from body_battery events.
Tests
tests/* (multiple)
Tests updated to new telemetry defaults and SSO/MFA shapes, added tests for parse_sso_response, MFA handling, session_id uniqueness, and adjusted HTTP test expectations.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SSO_Mobile_API as MobileAPI
    participant OAuth_Service as OAuthServer
    participant Telemetry

    Client->>MobileAPI: POST /mobile/api/login (email,password,clientId,serviceUrl)
    MobileAPI-->>Client: {type: "SUCCESSFUL", serviceTicketId}
    Client->>OAuth_Server: GET /oauth/preauthorized?ticket=serviceTicketId
    OAuth_Server-->>Client: 200 + OAuth1 token
    Client->>OAuth_Server: POST /oauth2/exchange (audience, oauth1_token[, mfa_token])
    OAuth_Server-->>Client: OAuth2 token
    Note over Client,Telemetry: Telemetry.session_id attached to request/response payloads
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the two main changes: migrating SSO to mobile JSON API and enabling telemetry by default, which align directly with the changeset.
Linked Issues check ✅ Passed The PR successfully addresses all primary objectives from issue #198: it migrates SSO to mobile JSON API endpoints, enables telemetry for diagnosing auth issues, and restores reliable 2FA login by moving away from the failing HTML form-based flow.
Out of Scope Changes check ✅ Passed Changes like version bump to 0.7.0, Pydantic v2 dependency upgrade, user-agent update, and documentation additions are all reasonable and necessary follow-ups to the core migration work specified in the linked issue.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mobile-sso-flow
📝 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.

@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.97%. Comparing base (2427594) to head (a692648).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##              main     #200      +/-   ##
===========================================
- Coverage   100.00%   99.97%   -0.03%     
===========================================
  Files           68       68              
  Lines         3508     3492      -16     
===========================================
- Hits          3508     3491      -17     
- Misses           0        1       +1     
Flag Coverage Δ
unittests 99.97% <100.00%> (-0.03%) ⬇️

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.

- Wrap raise_for_status() in handle_mfa() with GarthHTTPError for
  consistent exception types across all SSO functions
- Bump version to 0.7.0 (breaking: pydantic>=2.0 required, logfire
  now a core dependency, removed [telemetry] extra)
- Add telemetry to README features
- Sync uv.lock

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@matin matin force-pushed the mobile-sso-flow branch from 1114a7c to 7e60884 Compare March 18, 2026 06:15
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
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: 4

🤖 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/http.py`:
- Around line 48-49: Unconditional printing of the telemetry session in the
client initializer (the block checking self.telemetry.enabled and printing
telemetry.session_id) should be removed to avoid spurious stdout during
import/Client() construction; replace the print with a debug log using a module
logger (e.g., logger = logging.getLogger(__name__) and logger.debug(f"Garth
session: {self.telemetry.session_id}")) or eliminate the emission entirely so no
stdout is produced during normal initialization.

In `@src/garth/sso.py`:
- Around line 87-91: The bootstrap GET to
client.sess.get(f"{sso_base}/mobile/sso/en/sign-in", params={"clientId":
CLIENT_ID}, timeout=client.timeout) must be validated: after the request, check
the response.status_code and if it's not in the 2xx range, log or raise an
explicit error including response.status_code and response.text (and the
sso_base/CLIENT_ID context) and abort before proceeding to post credentials so
bootstrap failures are surfaced immediately instead of masking them as a later
generic login failure.

🪄 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: 08f22288-da20-4041-a40b-ec1b2b3187fe

📥 Commits

Reviewing files that changed from the base of the PR and between 2427594 and 0f10334.

⛔ Files ignored due to path filters (9)
  • tests/cassettes/test_login_command.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_login_email_password_fail.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_login_mfa_fail.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_login_return_on_mfa.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_login_success.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_login_success_mfa.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_login_success_mfa_async.yaml is excluded by !tests/**/cassettes/**
  • tests/cassettes/test_resume_login.yaml is excluded by !tests/**/cassettes/**
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • .github/ISSUE_TEMPLATE/auth-issue.yml
  • README.md
  • docs/telemetry.md
  • pyproject.toml
  • src/garth/http.py
  • src/garth/sso.py
  • src/garth/telemetry.py
  • src/garth/version.py
  • tests/conftest.py
  • tests/test_http.py
  • tests/test_sso.py
  • tests/test_telemetry.py

src/garth/sso.py Outdated
Comment on lines 87 to 91
client.sess.get(
f"{sso_base}/mobile/sso/en/sign-in",
params={"clientId": CLIENT_ID},
timeout=client.timeout,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Check the bootstrap request before posting credentials.

The initial /mobile/sso/en/sign-in call is now a required cookie/bootstrap step, but its status is ignored. If that request starts returning 4xx/5xx, the code falls through to a later generic login failure, which makes auth regressions much harder to diagnose.

Suggested fix
-    client.sess.get(
+    bootstrap = client.sess.get(
         f"{sso_base}/mobile/sso/en/sign-in",
         params={"clientId": CLIENT_ID},
         timeout=client.timeout,
     )
+    try:
+        bootstrap.raise_for_status()
+    except HTTPError as e:
+        raise GarthHTTPError(msg="SSO bootstrap failed", error=e)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
client.sess.get(
f"{sso_base}/mobile/sso/en/sign-in",
params={"clientId": CLIENT_ID},
timeout=client.timeout,
)
bootstrap = client.sess.get(
f"{sso_base}/mobile/sso/en/sign-in",
params={"clientId": CLIENT_ID},
timeout=client.timeout,
)
try:
bootstrap.raise_for_status()
except HTTPError as e:
raise GarthHTTPError(msg="SSO bootstrap failed", error=e)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/garth/sso.py` around lines 87 - 91, The bootstrap GET to
client.sess.get(f"{sso_base}/mobile/sso/en/sign-in", params={"clientId":
CLIENT_ID}, timeout=client.timeout) must be validated: after the request, check
the response.status_code and if it's not in the 2xx range, log or raise an
explicit error including response.status_code and response.text (and the
sso_base/CLIENT_ID context) and abort before proceeding to post credentials so
bootstrap failures are surfaced immediately instead of masking them as a later
generic login failure.

Repository owner deleted a comment from coderabbitai bot Mar 18, 2026
Repository owner deleted a comment from coderabbitai bot Mar 18, 2026
- Replace magic strings with SSO_SUCCESSFUL/SSO_MFA_REQUIRED constants
- Consolidate duplicate USER_AGENT (single def in http.py, ref from sso.py)
- Remove unused MAX_WORKERS duplicate from body_battery/events.py
- Use client.get()/client.post() in SSO instead of client.sess directly
  (consistent error handling, sets last_resp)
- Replace print() with logger.info() for session ID
- Add bootstrap GET validation
- Add test for unexpected SSO response type (100% sso.py coverage)
- Add Philosophy section to CLAUDE.md
- Add CodeRabbit config to allow embedded logfire write token
- Document why telemetry is on by default (Garmin SSO migration context)

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
@sonarqubecloud
Copy link
Copy Markdown

@matin matin merged commit 5c06118 into main Mar 18, 2026
26 of 27 checks passed
@matin matin deleted the mobile-sso-flow branch March 18, 2026 07:22
@coderabbitai coderabbitai bot mentioned this pull request Mar 18, 2026
3 tasks
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.

HTTP 401 when signing in with 2FA

1 participant