Skip to content

OAUTH-001C1H: add one-use Strava OAuth state#442

Merged
daliu merged 1 commit into
mainfrom
codex/issue-441-strava-oauth-state
Jul 22, 2026
Merged

OAUTH-001C1H: add one-use Strava OAuth state#442
daliu merged 1 commit into
mainfrom
codex/issue-441-strava-oauth-state

Conversation

@daliu

@daliu daliu commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Outcome

Strava connection now starts with a server-issued, ten-minute, one-use challenge that is bound to the signed-in UID and Firebase Auth session marker. The server stores only a SHA-256 digest and atomically consumes one valid challenge before credentials, provider access, or connection writes.

Officer impact: Members still start Strava from My Account, but a failed, expired, mismatched, or repeated callback now follows one plain retry path and cannot reach Strava or change the connection.

Officer documentation: docs/officers/EVENTS_SHOP_MEMBERS.md; matching current-source design and risk records in SYSTEM_DESIGN.md and SECURITY.md.

Deployment evidence: Source and synthetic local tests only. Website publication: not performed. runmprc.com revision verification: not performed. Firebase deployment/readback: not performed. Strava/provider configuration or contact: not performed. Production data and live behavior: not inspected or verified.

Security and transitions

  • stravaBeginAuthorization: App Check -> Auth and positive safe-integer auth_time -> 32 random bytes -> one digest-only fixed secret record.
  • Repeated begin overwrites the prior record for the same UID.
  • stravaExchangeCode: App Check -> Auth -> exact code/state parsing -> Firestore transaction -> one delete -> existing exchange path.
  • Missing, malformed, wrong-account/session, mismatched, expired, replayed, and concurrently lost state share one fixed permission-denied public result.
  • Provider, credential, or persistence failure after consumption never restores the challenge.
  • Browser state generation, session storage, and browser-authoritative verification are removed.
  • The existing callback address scrub and stale-account/service protections remain in place.
  • No schema backfill or TTL policy is needed: one fixed record is overwritten on begin and deleted on use. Existing Rules deny the entire secrets subcollection to every browser role.

Verification

  • RED on exact base 89a03741d94d1509ab4b49a203f9b0244a7b3797: the old exchange reached mocked Strava without a server challenge; 1 intended failure, 486 skipped.
  • Functions focused Strava: 576/576 passed.
  • Functions full: 63 suites passed, 2 skipped; 6,057 passed, 64 skipped, 6,121 total.
  • Functions ESLint: passed.
  • Focused Account/Strava browser and callback: 113/113 passed.
  • Frontend full: 14 suites and 669/669 passed.
  • Frontend exact lint baseline: 111 files, 117 reviewed legacy errors, 6 reviewed legacy warnings.
  • SPA/workflow/release/artifact/dependency checks: 65/65 passed.
  • Firestore Rules emulator: 378/378 passed on demo project demo-rules-test with Java 17.
  • Commerce command journal real-emulator regression: 63/63 passed on demo project demo-pay002b2-test.
  • Diagnostic optimized build: compiled successfully without sitemap regeneration.
  • git diff --check: passed; no package, lockfile, Rules, workflow, sitemap, provider, or production-data change.

Review and residual risk

Independent frontend/officer and backend security reviews are clean after correcting stale current-truth documentation, retry-time expiry, full state-error uniformity, and credential-failure consumption proof. The initial capability callback still suppresses browser App Check startup, so fail-closed callback compatibility remains separate work under #88. Firestore race proof uses an optimistic transaction mock rather than deployed Firebase. Native App Check enforcement, scope/account policy, refresh concurrency, reconciliation, revoke/audit, IAM/encryption, provider setup, deployment, and live proof remain out of scope.

Closes #441.
Parent #88 remains open.

@netlify

netlify Bot commented Jul 22, 2026

Copy link
Copy Markdown

Deploy Preview for luminous-fox-7c393f ready!

Name Link
🔨 Latest commit 21b6755
🔍 Latest deploy log https://app.netlify.com/projects/luminous-fox-7c393f/deploys/6a607f22cec82000088faea3
😎 Deploy Preview https://deploy-preview-442--luminous-fox-7c393f.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@daliu

daliu commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

SELF-REVIEWED by /root at exact head 21b6755 against exact base 89a0374.

No actionable findings remain. I reviewed the complete remote 11-file diff for App Check/Auth ordering, request and stored-record bounds, UID/auth_time binding, digest-only storage, expiry on transaction retry, begin/exchange and concurrent-consumer races, full public state-denial uniformity, provider/write ordering, replay, no restoration, redacted failures/logs, browser single-flight and stale-context behavior, callback cleanup, Rules coverage, migration impact, source-vs-live wording, and officer continuity.

Hosted CI run 29904118112 passed all five jobs at the exact head: frontend lint/test/SPA/workflow/build, Functions lint/test, Firestore Rules emulator, commerce concurrency emulator, and artifact scrubber. Netlify produced a preview only; its Header rules and Pages changed subchecks were neutral/skipped because those surfaces did not change, while Redirect rules and the preview passed. No production website, runmprc.com revision, Firebase deployment/readback, Strava/provider configuration, production data, or live OAuth behavior was verified.

Review-required branch protection has no independent human approval, by design of the requested self-review workflow. The source is mergeable only through the authorized maintainer bypass after this exact-head evidence.

@daliu
daliu merged commit a6b40d5 into main Jul 22, 2026
9 checks passed
@daliu
daliu deleted the codex/issue-441-strava-oauth-state branch July 22, 2026 08:31
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.

OAUTH-001C1H — Replace browser-local Strava OAuth state with a one-use server challenge

1 participant