Skip to content

STRAVA-001 — Harden and modernize the existing Strava OAuth connection #88

Open

Description

@daliu

Problem

The repository has a functional Strava prototype, but OAuth state is browser-local, provider error bodies can leak, scopes are broader than club-link needs, token refresh is not concurrency safe, and provider capacity/configuration is unverified.

Scope

  • Use server-issued, UID/session-bound, one-use OAuth state with TTL.
  • Bound callback/code inputs and return generic client errors without provider bodies.
  • Request only read for club-link use; make activity:read a separate explicit feature consent and persist actual granted scopes.
  • Enforce one athlete ID owner; keep tokens server-only; make refresh rotation transactional/idempotent.
  • Adopt the current OAuth revoke path and retry-safe deletion behavior.
  • Verify provider athlete capacity and use non-production credentials/accounts for tests.

Acceptance criteria

  • CSRF/state replay, wrong/missing scope, duplicate athlete, concurrent refresh, revoke retry, rate limit, and redaction tests pass.
  • Disconnect/deauthorization removes local tokens and connection data according to policy.
  • No Strava token/provider body is exposed to clients, logs, custom claims, or issues.
  • Local/CI cannot call the production Strava host.
  • The integration never grants website membership or discounts.

Dependencies and sequence

  • SAFETY-001/SAFETY-001 — Preserve callbacks and fail closed on local Firebase isolation #99 is the callback/privacy foundation and is complete in source. OAUTH-001C depends on that guard.
  • OAUTH-001A follows SEC-001 and the AUTH-003 capability boundary. Production cutover still requires the exact Firebase deployment evidence owned by CI-001 — Establish trustworthy quality gates and protected deployment #105.
  • AUTH-001 remains a live-rollout gate for associating Strava with a verified website account; it does not block isolated synthetic A/B/C implementation and tests.
  • ABUSE-001A is not a blanket prerequisite. It may enforce App Check on its agreed callable group while explicitly deferring stravaExchangeCode. OAUTH-001C then proves the safe handoff before that one callable moves into enforcement.
  • OAUTH-001B follows A and requires the named provider/IAM/encryption owner decisions before hosted closure.

Official references

Coordination

Parent: #79

Read the slice-specific sequence below before claiming. Do not claim the whole tracker unless one reviewable outcome truly completes A, B, and C. A or C may be assigned and claimed independently after only that slice's dependencies are complete; C follows merged #99 and the recorded ABUSE-001A deferral, not completion of all ABUSE work. B is owner action for provider/IAM/encryption decisions and follows A. Before editing, assign the exact slice and post a timestamped CLAIMED comment naming the slice, agent, and branch. Creation of this tracker is not a claim. Preserve parallel work tracked by stable IDs in GITHUB_ISSUES.md.

Canonical OAUTH-001 map — 2026-07-13

This issue is the canonical live tracker for repository outcome OAUTH-001. The bounded OAUTH-001A/B/C rows in GITHUB_ISSUE_SLICES.md are parts of this issue, not separate tickets to publish by default.

  • OAUTH-001A — refresh concurrency: implement server-only transaction/version-safe refresh so a retry or concurrent call cannot overwrite a newly rotated refresh token.
  • OAUTH-001B — scope, revoke, storage, and audit: minimize and verify scopes/account binding; implement retry-safe disconnect/provider revocation; record the named IAM/application-encryption decision; add redacted durable access/refresh/failure/revoke audit.
  • OAUTH-001C — protected callback handoff: preserve SAFETY-001 — Preserve callbacks and fail closed on local Firebase isolation #99's initial capability guard; keep OAuth code/state out of third-party startup; retain one-use UID/session-bound state verification and server-only exchange; keep stravaExchangeCode excluded from App Check enforcement until the handoff is proven; then test missing/invalid/valid App Check plus encoded, case, and trailing-slash callback variants.

Dependencies and gates:

  • A follows SEC-001 and the AUTH-003 capability boundary.
  • B follows A and requires named owner review for provider configuration plus IAM/encryption policy.
  • C follows SAFETY-001 — Preserve callbacks and fail closed on local Firebase isolation #99. ABUSE-001A must defer stravaExchangeCode until C's safe handoff and callback regression evidence merge.
  • No test may use a real member token or the production Strava host.
  • None of these outcomes grants website membership, dues status, discounts, or admin access.

Split/claim rule: do not publish a duplicate OAUTH/Strava ticket. A and C are proposed implementation slices; B remains owner action because it contains provider/IAM/encryption decisions. If one pull request cannot safely review all three outcomes, keep #88 as the tracker, create and cross-link one atomic child first, then assign and post a timestamped CLAIMED marker for that child before editing.

Activity

  1. added
    priority:P0Launch blocker or urgent security risk
    type:securitySecurity or privacy boundary
    area:authAuthentication and authorization
    size:MMedium multi-file issue
    status:proposedDesigned but dependencies or decisions remain
    needs-external-configRequires provider console or external configuration
    on Jul 12, 2026
  2. added a commit that references this issue on Jul 13, 2026
  3. daliu commented on Jul 13, 2026

    @daliu
    ContributorAuthor

    Canonical mapping evidence (2026-07-13): PR #129 merged to final main as f6e86be. This issue body and the root OAUTH-001/OAUTH-001A/B/C maps now agree on P0 labels, A/C proposed, B owner_action, slice-specific dependencies, ABUSE deferral/cutover, App Check callback evidence, and the no-duplicate split/claim rule. #88 remains open, unassigned, and unclaimed; do not publish a parallel Strava/OAUTH ticket without first making #88 the tracker and cross-linking one atomic child.

  4. daliu commented on Jul 14, 2026

    @daliu
    ContributorAuthor

    Atomic child #223 published for only the server authorization-exchange redaction in functions/strava.js plus a new focused Functions test. It does not complete OAUTH-001C and leaves all callback/state/App Check/refresh/scope/revoke/IAM/provider/deployment work here.

  5. daliu commented on Jul 14, 2026

    @daliu
    ContributorAuthor

    OAUTH-001C1A child #223 is COMPLETE and its exact two-path claim is RELEASED.

    PR #225 squash-merged reviewed head 51e46b34e0a71bf2976d34dd4fb1bbd8bec773b4 as exact main ff8e9068d8a01f7512d0119e675f9b5df6c69a3a. PR CI 29307783519 and post-merge CI 29307918575 passed all five required jobs. The child redacts authorization-exchange failures while preserving the successful server-only contract.

    This was source/tests only: no protected release, Firebase deployment, Strava/provider call or configuration, production-data action, migration, production-site publication, or live-behavior proof.

    Canonical #88 remains open for bounded/one-use code and state, token/scope/account validation, native App Check handoff, refresh redaction/concurrency, disconnect/revoke/audit, encryption/IAM, provider capacity/configuration, Firebase deployment, and live proof.

  6. daliu commented on Jul 14, 2026

    @daliu
    ContributorAuthor

    Atomic refresh-redaction child #229 is published, assigned, and CLAIMED as OAUTH-001A1 by /root at 2026-07-14T05:34:34Z on codex/issue-229-strava-refresh-redaction, exact base 35ca312e285417d7d84c67d45a7d1bec01860372.

    This is not a duplicate tracker and does not complete OAUTH-001A. It owns only the existing refreshToken failure hunk in functions/strava.js plus focused synthetic support/tests in functions/strava.test.js. It removes provider status/body/raw-exception exposure while preserving successful behavior. Refresh concurrency/versioning, capability work, validation, scopes, revoke/audit, encryption/IAM, provider setup, deployment, and live proof remain here.

  7. daliu commented on Jul 14, 2026

    @daliu
    ContributorAuthor

    #229 / PR #231 is the claimed atomic OAUTH-001A1 refresh-error-redaction child. It is rebased onto exact main 6f36953c3668c45b653abc28593a266568de37db; exact scope remains only functions/strava.js refresh failure handling and focused functions/strava.test.js coverage. Hosted CI is in progress. Full OAUTH-001A concurrency/capability work and all other #88 residuals remain open and are not advanced by this child.

  8. 65 remaining items

  9. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    ATOMIC CHILD CLAIMED: #604 / OAUTH-001A2J by /root on branch codex/issue-604-strava-response-status, exact base dff1a40194bd1f25938ac16564a6ce27bbb9eab5. Scope is only safe admission of Strava HTTP ok evidence by generalizing the existing disconnect helper across exchange, refresh, activities, and statistics plus named tests. It does not claim normal wire-byte exploitability, response-byte streaming, refresh concurrency, provider governance, deployment, or completion of #88.

  10. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001A2J / #604 COMPLETE and CLAIM RELEASED. PR #605 merged exact reviewed head 604e51c as exact main a62c147. Exact-main CI 30734583158 passed all five required jobs.

    Officer impact: no current visible change; safe Strava HTTP success admission becomes relevant only after a future protected Firebase Functions release.

    Officer documentation: None — server-only containment; existing SOURCE ONLY / NOT LIVE guide remains accurate.

    Deployment evidence: unpublished exact-head preview only; exact-main Netlify production attempt errored unpublished; GitHub deployments are empty; live marker remains #473; no Firebase deployment, provider action, production data, or live verification. All #604 claims are released.

  11. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    Atomic child #606 is CLAIMED by /root at 2026-08-02T05:59:37Z on exact base a62c147.

    Scope is only stale Strava refresh compare-and-set persistence in functions/strava.js, its focused transaction/test mock hunks in functions/strava.test.js, and one named SECURITY.md current-truth hunk. This does not claim or close #88 and excludes provider-call serialization, response byte limits, client disconnect behavior, deployment, provider configuration, real data, and live verification.

    Claim marker: #606 (comment)

  12. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001A1H child #606 is COMPLETE / CLOSED / CLAIM RELEASED at exact main 24ec0e5 through PR #607. PR CI 30735558743 and exact-main CI 30735667378 passed all five jobs. No Firebase, provider, production-data, or live deployment claim.

    The merged source transactionally fences a refresh response on the exact access/refresh/expiry tuple. Provider-call serialization, identical-tuple ABA, acknowledgement/provider reconciliation, IAM/encryption/audit, protected deployment, and live proof remain open; #88 stays open.

  13. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    Atomic P0 child #608 (OAUTH-001B9 — Refuse stale Strava disconnect cleanup) is now claimed on exact main 24ec0e5789c0ff26cba49371d298aea5c0cdf3ce.

    It owns only the server disconnect snapshot/version compare-before-delete transaction, focused Functions tests/mock support, and the existing RISK-024 current-truth row. The separate client fulfilled-response validator is reserved for a later B10 child. No source result, merge, Firebase deployment, provider action, production-data action, or live behavior is claimed by this coordination marker.

  14. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001B9 child #608 is COMPLETE / CLOSED / CLAIM RELEASED via PR #609. Exact release SHA: 9d8bfaae0a4f9e573053b4035954e933ae626875. PR and exact-main CI passed all five jobs; reviewed/merge trees are identical. This is merged source only: Firebase and Strava were not deployed or exercised, the main Netlify attempt remained unpublished, and live behavior is unverified. Parent #88 remains open for the remaining OAuth work.

  15. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    ATOMIC CHILD CLAIMED: #610 / OAUTH-001B10 by /root on branch codex/issue-610-strava-disconnect-confirmation, exact base 9d8bfaae0a4f9e573053b4035954e933ae626875. It owns only the exact client fulfilled-confirmation admission, focused frontend tests, RISK-024 current truth, and the existing no-terminal disconnect procedure hunk. It does not claim provider revocation proof, Functions/server state, reconciliation, audit, IAM/encryption, deployment, live behavior, or completion of #88.

  16. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001B10 child #610 is COMPLETE / CLOSED / CLAIM RELEASED.

    PR #611 merged as exact main 63d29bedf379a857421501a5c0938c2901618095; reviewed head and merge share tree a4c23c8b69a6076f01c3f4be3f06efed50fa9a72. PR CI 30737652078 and post-main CI 30737752006 passed all five jobs. No GitHub deployment exists; Netlify main failed unpublished and the live marker remains #473, so no live-production claim is made. The remote branch, assignee, and in-progress label are cleared.

  17. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    ATOMIC CHILD CLAIMED: #612 / OAUTH-001C1J by /root at 2026-08-02T07:34:49Z on branch codex/issue-612-strava-exchange-confirmation, exact base 63d29bedf379a857421501a5c0938c2901618095.

    Scope is only exact browser admission of the existing server exchange confirmation, focused frontend tests, RISK-009 current truth, and the existing callback-failure officer procedure. It does not claim scope/account policy, Functions behavior, provider/local reconciliation, audit, IAM/encryption, provider configuration, deployment, live proof, or completion of #88.

  18. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001C1J #612 — COMPLETE / CLOSED / CLAIM RELEASED

    PR #613 merged as exact main 644d9f1fe3e6c06427412fd3ae37973dbbfeca20 (tree 031b49e53a5b728944c2abeac18d58ffcd6f1a6d). Exact-main CI 30738868932 passed all five jobs; GitHub deployments are []. The main Netlify attempt 6a6ef8d14154fc0008c2aabb failed unpublished, leaving #473 control deploy 6a6dc9ea588b0c0008036312 live. No Firebase/provider/data/live action or #612 runmprc.com verification occurred.

    The exact-confirmation client boundary is complete; response bounds/timeouts, scope/account policy, reconciliation, audit, IAM/encryption, deployment, and live evidence remain open under #88.

    #612 claim released at 644d9f1fe3e6c06427412fd3ae37973dbbfeca20; its four paths are unowned again.

  19. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001A2K #614 — CLAIMED after reviewable split

    Exact base: 644d9f1fe3e6c06427412fd3ae37973dbbfeca20.

    This child now owns only the 64 KiB native streamed-body boundary for authorization-exchange and refresh token JSON replies in functions/strava.js, focused/preserved tests in functions/strava.test.js, and one separate SECURITY.md row. The pre-implementation four-response concept was split: activities/statistics response caps and targeted unread-sibling cleanup remain unclaimed for a later #88 child because their compatibility and resource lifecycle differ.

    No browser/officer doc, disconnect/revoke, package/workflow, Rules/schema, timeout/abort, Firebase deployment, provider, production-data, or live path is claimed.

  20. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    OAUTH-001A2K / #614 COMPLETE, CLOSED, CLAIM RELEASED

    PR #615 merged as exact main b94622bd42775d84704d5fc05ee87da81f5a76e0 (reviewed tree 3683a9ccb982b6a2bb1c67a8e0fd27a86f38bcc9). PR CI 30740379852 and exact-main CI 30740495924 each passed all five jobs; all exact-head reviews were GO.

    This child bounds authorization-exchange and refresh JSON responses to 65,536 content-decoded bytes / 4,096 chunks before parse or persistence. It does not deploy Firebase or the website, configure/contact Strava, inspect production data, or prove live behavior. Exact-main GitHub deployments are empty; only the exact-head unpublished Netlify preview is ready.

    The claim on functions/strava.js, functions/strava.test.js, and the named OAUTH-001A2K SECURITY.md row is released at b94622bd42775d84704d5fc05ee87da81f5a76e0.

    A later separately named child may own activities/statistics response limits plus targeted cleanup of the concurrently started sibling body. That outcome remains unclaimed here and must preserve #571 and all other active claims.

    Full closure evidence: #614 (comment)

  21. daliu commented on Aug 2, 2026

    @daliu
    ContributorAuthor

    ATOMIC CHILD CLAIMED: #616 / OAUTH-001A2L by /root at 2026-08-02T09:08:26Z on branch codex/issue-616-strava-data-response-bytes, exact base b94622bd42775d84704d5fc05ee87da81f5a76e0.

    It owns only bounded native JSON adoption for the existing recent-activities/statistics GET responses, targeted cleanup of the unread successful statistics sibling after post-fulfillment activity failure, focused/preserved Functions tests, and one named SECURITY.md row. The chosen 1 MiB/64 KiB limits are explicit MPRC application budgets, not Strava guarantees.

    It excludes Promise-all transport-rejection sibling acquisition/timeouts, non-success-body cleanup, token paths, browser/callback/disconnect, scope/account/reconciliation/audit/IAM/encryption, #571, packages/workflows/Rules/schema, Firebase/provider configuration, production data, deployment, live proof, and completion of #88.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:authAuthentication and authorizationarea:stravaStrava integrationneeds-external-configRequires provider console or external configurationpriority:P0Launch blocker or urgent security risksize:MMedium multi-file issuestatus:proposedDesigned but dependencies or decisions remaintype:securitySecurity or privacy boundary

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions