Auto-acquire and renew the Strava session from login credentials #6

Open
opened 2026-10-02 10:49:43 +00:00 by mat · 1 comment
Owner

Context

High-resolution mode currently requires a manually copied session cookie: sign in to strava.com in a browser, dig out _strava4_session from devtools, and set it as STRAVA_SESSION_COOKIE. The CookieManager then exchanges that static cookie for short-lived CloudFront cookies and refreshes those automatically every 12h — but the _strava4_session value itself is never renewed.

When the Strava session expires, high-res tiles silently degrade to anonymous mode (zoom 11) until a human repeats the copy-paste ritual and restarts the container. On deployments managed by CI (e.g. tofu-maison), it also means updating a secret and re-applying.

Proposal

Let the proxy acquire and renew the session itself from credentials:

  • New env vars, e.g. STRAVA_EMAIL + STRAVA_PASSWORD.
  • Flow: GET https://www.strava.com/login → extract the CSRF token from the form → POST credentials → capture _strava4_session from the Set-Cookie response → hand it to the existing CookieManager.
  • On session-expiry failure (RefreshFailed persists / no CloudFront cookies returned), re-run the login flow with a backoff, reusing the existing cooldown pattern.
  • STRAVA_SESSION_COOKIE keeps working as-is: explicit cookie takes precedence when both are set; credentials mode only kicks in when no cookie is provided.

Risks / open questions

  • The login flow may involve reCAPTCHA or 2FA on some accounts — if login fails, fall back to anonymous mode and log loudly, never crash-loop.
  • Strava can change the login form anytime; the existing mock-Strava test harness should get a login-flow scenario so breakage is caught in CI.
  • Credentials must stay strictly server-side; visitors still never receive any cookie.

Acceptance

  • With STRAVA_EMAIL/STRAVA_PASSWORD set (and no cookie), /api/config reports authenticated: true, maxZoom: 15.
  • After a simulated session expiry in the mock-Strava tests, the proxy re-logins and recovers high-res tiles without a restart.
  • With neither cookie nor credentials set, behaviour is unchanged (anonymous, zoom 11).
## Context High-resolution mode currently requires a manually copied session cookie: sign in to strava.com in a browser, dig out `_strava4_session` from devtools, and set it as `STRAVA_SESSION_COOKIE`. The `CookieManager` then exchanges that static cookie for short-lived CloudFront cookies and refreshes **those** automatically every 12h — but the `_strava4_session` value itself is never renewed. When the Strava session expires, high-res tiles silently degrade to anonymous mode (zoom 11) until a human repeats the copy-paste ritual and restarts the container. On deployments managed by CI (e.g. tofu-maison), it also means updating a secret and re-applying. ## Proposal Let the proxy acquire and renew the session itself from credentials: - New env vars, e.g. `STRAVA_EMAIL` + `STRAVA_PASSWORD`. - Flow: `GET https://www.strava.com/login` → extract the CSRF token from the form → `POST` credentials → capture `_strava4_session` from the `Set-Cookie` response → hand it to the existing `CookieManager`. - On session-expiry failure (`RefreshFailed` persists / no CloudFront cookies returned), re-run the login flow with a backoff, reusing the existing cooldown pattern. - `STRAVA_SESSION_COOKIE` keeps working as-is: explicit cookie takes precedence when both are set; credentials mode only kicks in when no cookie is provided. ## Risks / open questions - The login flow may involve reCAPTCHA or 2FA on some accounts — if login fails, fall back to anonymous mode and log loudly, never crash-loop. - Strava can change the login form anytime; the existing mock-Strava test harness should get a login-flow scenario so breakage is caught in CI. - Credentials must stay strictly server-side; visitors still never receive any cookie. ## Acceptance - With `STRAVA_EMAIL`/`STRAVA_PASSWORD` set (and no cookie), `/api/config` reports `authenticated: true`, `maxZoom: 15`. - After a simulated session expiry in the mock-Strava tests, the proxy re-logins and recovers high-res tiles without a restart. - With neither cookie nor credentials set, behaviour is unchanged (anonymous, zoom 11).
Author
Owner

Live diagnosis from production (2026-10-02) — relevant to the login/password design:

Strava now requires a second cookie for high-res tiles. content-a.strava.com/identified/* rejects CloudFront-cookies-only requests with 401. Probes with a real logged-in session:

Cookies sent to the tile endpoint Result
CloudFront trio only (valid, identified-scope policy) 401
CloudFront trio + _strava_idcf 200 PNG

Details:

  • _strava_idcf is a short-lived (~24 h) JWT bound to the athlete ({"exp":…,"iat":…,"athleteId":…}).
  • The heatmap-page exchange that CookieManager::fetch_cookies already performs emits _strava_idcf as a Set-Cookie alongside the CloudFront trio — the proxy just never parsed/forwarded it (fix in progress: capture + forward it).
  • Session format note: logged-in _strava4_session is now a 32-char opaque token (server-side sessions), not the long Rails blob older tooling describes.

Implication for this issue: the credential login flow must maintain a small cookie jar — after POST /session and on every exchange, harvest _strava4_session and _strava_idcf, and send both (plus the CloudFront trio) on tile requests. A single session string won't be enough.

Live diagnosis from production (2026-10-02) — relevant to the login/password design: **Strava now requires a second cookie for high-res tiles.** `content-a.strava.com/identified/*` rejects CloudFront-cookies-only requests with **401**. Probes with a real logged-in session: | Cookies sent to the tile endpoint | Result | |---|---| | CloudFront trio only (valid, identified-scope policy) | 401 | | CloudFront trio + `_strava_idcf` | 200 PNG | Details: - `_strava_idcf` is a short-lived (~24 h) JWT bound to the athlete (`{"exp":…,"iat":…,"athleteId":…}`). - The heatmap-page exchange that `CookieManager::fetch_cookies` already performs **emits `_strava_idcf` as a Set-Cookie** alongside the CloudFront trio — the proxy just never parsed/forwarded it (fix in progress: capture + forward it). - Session format note: logged-in `_strava4_session` is now a 32-char opaque token (server-side sessions), not the long Rails blob older tooling describes. Implication for this issue: the credential login flow must maintain a small cookie jar — after POST /session and on every exchange, harvest `_strava4_session` **and** `_strava_idcf`, and send both (plus the CloudFront trio) on tile requests. A single session string won't be enough.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
mat/canicule#6
No description provided.