Skip to content

Fix WHOOP sync Cognito refresh and revoked token handling - #1355

Merged
Asherlc merged 7 commits into
mainfrom
Asherlc/whoop-sync-cognito-auth
Jun 24, 2026
Merged

Asherlc merged 7 commits into
mainfrom
Asherlc/whoop-sync-cognito-auth

Conversation

@Asherlc

@Asherlc Asherlc commented Jun 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Reuse stored WHOOP access tokens during sync when they are still valid, instead of calling Cognito refresh on every sync step.
  • Treat Cognito NotAuthorizedException during refresh as a revoked refresh token: delete stored tokens and surface RefreshTokenRevokedError so the UI prompts reconnect.
  • Add unit and integration coverage for token reuse, refresh failure, and revoked-token cleanup.

Test plan

  • pnpm vitest run src/providers/whoop.test.ts
  • pnpm vitest run src/providers/whoop-sync.integration.test.ts

Made with Cursor


Summary by cubic

Reuse valid WHOOP Cognito access tokens and centralize token handling so sync stops refreshing on every step. Refresh failures now trigger a clean reconnect, WHOOP uses a custom-auth flow (no developer OAuth), and tokens are validated at the boundaries.

  • Bug Fixes

    • Reuse stored access tokens when still valid; skip Cognito refresh and token writes.
    • On Cognito NotAuthorizedException, delete stored tokens and surface RefreshTokenRevokedError so the app prompts WHOOP reconnect.
    • Validate whoopAuth.saveTokens input; reject empty access/refresh tokens with clear errors.
    • Zod-validate Cognito refresh payloads before persisting refreshed credentials.
  • Refactors

    • Centralized WHOOP token logic in resolveWhoopTokens and saveWhoopAuthTokens; extracted WHOOP request logger.
    • Treated WHOOP as custom-auth: moved overrides to src/lib/custom-auth-providers, used providerRequiresStoredTokens in the worker, passed CUSTOM_AUTH_PROVIDERS to ProviderModel, and allowed custom-auth in provider-auth-policy; removed the developer OAuth path and related env checks.
    • CI: Ignore **/skills/stripe-projects symlinks in Stryker and extend integration test timeout to 12 minutes.

Written for commit f0a1cb0. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved WHOOP authentication token refresh and error handling for better reliability during account sync.
    • Enhanced token validation and storage logic for custom authentication providers.
  • Tests

    • Added comprehensive test coverage for custom authentication provider handling and WHOOP token resolution workflows.

…ation.

Sync was refreshing on every step even when the access token was still valid, and Cognito NotAuthorizedException was surfaced as a misleading password error instead of prompting reconnect.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copilot AI review requested due to automatic review settings June 23, 2026 23:51
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@sourcery-ai sourcery-ai Bot left a comment

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.

Sorry @Asherlc, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

WHOOP token resolution is extracted from WhoopProvider and sync-orchestrator.ts into a new src/providers/whoop/resolve-tokens.ts module. A new src/lib/custom-auth-providers.ts utility introduces CUSTOM_AUTH_PROVIDERS, CUSTOM_AUTH_SYNC_PROVIDER_IDS, and providerRequiresStoredTokens. Auth policy, job runner, and server routers are updated to use these new shared utilities.

Changes

WHOOP Custom Auth Extraction

Layer / File(s) Summary
custom-auth-providers utility module
src/lib/custom-auth-providers.ts, src/lib/custom-auth-providers.test.ts
Creates CUSTOM_AUTH_PROVIDERS map, CUSTOM_AUTH_SYNC_PROVIDER_IDS set, and providerRequiresStoredTokens helper; covered by three Vitest assertions.
WHOOP resolve-tokens module
src/providers/whoop/resolve-tokens.ts, src/providers/whoop/resolve-tokens.test.ts, package.json
Adds parseWhoopUserIdFromScopes, buildWhoopTokenSet, saveWhoopAuthTokens, and resolveWhoopTokens (reuse valid token, refresh on expiry, delete+throw on NotAuthorizedException). Exports new subpath from package.json. Full unit test coverage.
WhoopProvider authSetup removal + sync-orchestrator delegation
src/providers/whoop/provider.ts, src/providers/whoop/sync-orchestrator.ts
Removes WhoopProvider.authSetup() and all OAuth/login imports. Refactors createWhoopClient to call resolveWhoopTokens instead of inlining token load/refresh/save; extracts request logging into createWhoopRequestLogger.
Auth policy exemption + job runner token check
src/providers/provider-auth-policy.ts, src/providers/provider-auth-policy.test.ts, src/jobs/process-sync-job.ts, src/auth/resolve-tokens.ts
checkPerUserAuthCompliance() gains an early-return for CUSTOM_AUTH_SYNC_PROVIDER_IDS members. process-sync-job.ts replaces provider.authSetup !== undefined with providerRequiresStoredTokens(provider). WHOOP OAuth credentials removed from auth policy test stubs.
Server router wiring
packages/server/src/routers/sync-helpers.ts, packages/server/src/routers/sync.ts, packages/server/src/routers/whoop-auth.ts
sync-helpers.ts re-exports CUSTOM_AUTH_PROVIDERS from the shared module. sync.ts passes CUSTOM_AUTH_PROVIDERS to ProviderModel in triggerSync. whoop-auth.ts replaces generic saveTokens with saveWhoopAuthTokens.
Updated whoop unit and integration tests
src/providers/whoop.test.ts, src/providers/whoop-sync.integration.test.ts, packages/server/src/routers/sync.test.ts
Adds deleteTokens to token mock, two new sync token-resolution tests, flips a saveTokens assertion, removes authSetup/getUserIdentity test block, replaces rate-limit test, and tightens integration test assertions around revocation and token clearance.
Stryker ignore pattern update
stryker.config.json, stryker.ci.config.json
Adds **/skills/stripe-projects to ignorePatterns in both Stryker configs.

Sequence Diagram

sequenceDiagram
  participant SyncJob as process-sync-job
  participant ProviderModel
  participant resolveWhoopTokens
  participant TokenDB as SyncDatabase
  participant WhoopClient

  SyncJob->>ProviderModel: providerRequiresStoredTokens(provider)
  ProviderModel-->>SyncJob: true (whoop in CUSTOM_AUTH_SYNC_PROVIDER_IDS)
  SyncJob->>resolveWhoopTokens: { db, fetchFn, userId }
  resolveWhoopTokens->>TokenDB: loadTokens("whoop")
  alt token valid + userId parseable from scopes
    resolveWhoopTokens-->>SyncJob: WhoopAuthToken (no network)
  else token expired
    resolveWhoopTokens->>WhoopClient: refreshAccessToken(refreshToken)
    alt NotAuthorizedException
      resolveWhoopTokens->>TokenDB: deleteTokens("whoop")
      resolveWhoopTokens-->>SyncJob: throws RefreshTokenRevokedError
    else success
      resolveWhoopTokens->>TokenDB: saveTokens("whoop", newTokenSet)
      resolveWhoopTokens-->>SyncJob: WhoopAuthToken
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Asherlc/dofek#1284: Both PRs modify checkPerUserAuthCompliance in src/providers/provider-auth-policy.ts—this PR adds CUSTOM_AUTH_SYNC_PROVIDER_IDS exemptions for whoop; that PR introduces amazfit-zepp as a per-user authSetup-required provider.
  • Asherlc/dofek#1335: Both PRs modify syncRouter.triggerSync in packages/server/src/routers/sync.ts—this PR changes ProviderModel construction for custom-auth filtering; that PR changes how enqueueSyncJob returning null is handled.

Suggested labels

area/server, area/providers, area/infra, type/refactor

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Title is in imperative mood, 57 characters, and clearly describes the main changes regarding WHOOP Cognito token handling without area prefix (not relevant given multi-area scope).
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.


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.

@github-actions

github-actions Bot commented Jun 23, 2026 •

Copy link
Copy Markdown
Contributor

Storybook previews for b62fe422 are ready:

This comment updates automatically on each PR push.

Asherlc and others added 3 commits June 23, 2026 16:58
Centralize token save/resolve in resolveWhoopTokens, drop dead OAuth authSetup, and treat WHOOP as custom-auth so sync-all and the worker require stored tokens correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
provider-auth-policy importing from src/providers/custom-auth-providers.ts violated no-provider-cross-imports in dependency-cruiser.

Co-authored-by: Cursor <cursoragent@cursor.com>
Stryker's copyfile fails on git-tracked symlinks pointing at directories; exclude them via ignorePatterns so mutation test shards can run.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/server/src/routers/whoop-auth.ts`:
- Around line 143-148: The saveWhoopAuthTokens function is persisting tokens
without validating that they are non-empty, which creates an inconsistency with
resolveWhoopTokens that treats empty refresh tokens as disconnected. Add
validation to the tRPC input schema before the saveWhoopAuthTokens call to
reject requests where accessToken or refreshToken are empty strings. This
ensures that empty tokens cannot be persisted to the database, maintaining
consistency with the coding guidelines that prohibit empty strings as absent
values.

In `@src/providers/whoop/resolve-tokens.ts`:
- Around line 1-3: The WHOOP token payload obtained from the refresh call at
line 72 is persisted directly to storage in lines 78-84 without runtime
validation. Create a Zod schema that validates the structure and types of the
token response payload (ensuring it matches the expected shape with properties
like access_token, refresh_token, expires_in, etc.), then parse the refreshed
token data through this schema before persisting it to ensure malformed API
responses cannot corrupt stored credentials. Handle schema validation errors by
throwing an appropriate error that prevents the invalid token from being saved.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: bbc5ab92-b708-40b3-af49-102e32016f22

📥 Commits

Reviewing files that changed from the base of the PR and between 73844b8 and 1030319.

📒 Files selected for processing (19)
  • package.json
  • packages/server/src/routers/sync-helpers.ts
  • packages/server/src/routers/sync.test.ts
  • packages/server/src/routers/sync.ts
  • packages/server/src/routers/whoop-auth.ts
  • src/auth/resolve-tokens.ts
  • src/jobs/process-sync-job.ts
  • src/lib/custom-auth-providers.test.ts
  • src/lib/custom-auth-providers.ts
  • src/providers/provider-auth-policy.test.ts
  • src/providers/provider-auth-policy.ts
  • src/providers/whoop-sync.integration.test.ts
  • src/providers/whoop.test.ts
  • src/providers/whoop/provider.ts
  • src/providers/whoop/resolve-tokens.test.ts
  • src/providers/whoop/resolve-tokens.ts
  • src/providers/whoop/sync-orchestrator.ts
  • stryker.ci.config.json
  • stryker.config.json
💤 Files with no reviewable changes (1)
  • src/providers/provider-auth-policy.test.ts

Comment thread packages/server/src/routers/whoop-auth.ts
Comment thread src/providers/whoop/resolve-tokens.ts
Asherlc and others added 3 commits June 23, 2026 17:18
Organize imports, apply formatting, and replace banned `as SyncDatabase` casts with a typed mock helper in resolve-tokens tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
Reject empty access/refresh tokens in whoopAuth.saveTokens input and Zod-parse Cognito refresh payloads before persisting refreshed credentials.

Co-authored-by: Cursor <cursoragent@cursor.com>
The suite was passing all 87 files but hitting the 10-minute cap during coverage; also seed WHOOP integration tokens with a stored userId to avoid unnecessary Cognito refreshes.

Co-authored-by: Cursor <cursoragent@cursor.com>
@Asherlc
Asherlc enabled auto-merge (squash) June 24, 2026 00:58
@Asherlc
Asherlc merged commit ca16819 into main Jun 24, 2026
72 checks passed
@Asherlc
Asherlc deleted the Asherlc/whoop-sync-cognito-auth branch June 24, 2026 01:02
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