diff --git a/rowers/tests/testdata/testdata.tcx.gz b/rowers/tests/testdata/testdata.tcx.gz index 65720607..bd1d17df 100644 Binary files a/rowers/tests/testdata/testdata.tcx.gz and b/rowers/tests/testdata/testdata.tcx.gz differ diff --git a/rowing-courses-spec.md b/rowing-courses-spec.md index bf7d5b1e..53348bf1 100644 --- a/rowing-courses-spec.md +++ b/rowing-courses-spec.md @@ -168,7 +168,9 @@ Even in Stage 1, users need a way to log in to like courses and have those likes 6. Worker sets the encrypted blob as an HTTP-only, Secure, SameSite=Lax cookie named `rn_session`. 7. On all subsequent authenticated requests, the Worker decrypts the cookie to recover the athlete ID and access token. No D1 lookup needed. -**D1 is not required in Stage 1.** The cookie carries all state needed for browser-based authentication. D1 is introduced in Stage 2 for the `is_organizer` flag and challenge-related state. +**Token refresh in Stage 1:** On each authenticated request the Worker decrypts the cookie and checks `expiresAt`. If the access token has expired, the Worker calls `POST https://intervals.icu/oauth/token` with the `refreshToken` from the cookie, receives a new `{accessToken, refreshToken, expiresAt}`, and rewrites the `rn_session` cookie before continuing. The refresh token is never written to D1 or KV — the cookie is the only storage. This is sufficient for Stage 1. + +**D1 is not required in Stage 1.** The cookie is entirely self-contained. D1 is introduced in Stage 2 for the `is_organizer` flag and challenge-related state. At that point the refresh token can optionally migrate to D1 (stored encrypted), which would enable server-side session revocation — currently not possible since there is no server-side session record to invalidate. **CrewNerd API key:** @@ -183,12 +185,13 @@ async function apiKeyForAthlete(athleteId: string, secret: string): Promise