diff --git a/CHANGELOG.md b/CHANGELOG.md index bfafa17..5381f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to Workout Lens are documented here. +## [Unreleased] + +### Developer / Infrastructure +- **Fix sporty sync writes blocked by missing User-Agent** — Supabase rejects POST and DELETE requests from the `sb_secret` service role key when no `User-Agent` header is present (treats the request as a browser). Azure Functions' built-in `fetch` sends no User-Agent, so the cleanup DELETE and upsert POST in `sportySync.js` were silently failing after each sync — the cleanup wiped future rows, then the upsert failed to re-insert them, leaving `gym_calendar` empty from June 6 onwards. Added `User-Agent: WorkoutLens/1.0 sporty-sync (Azure Functions)` to both requests. A post-deploy manual backfill is needed to restore June data. + ## [1.5.16] — 2026-05-19 ### Accessibility diff --git a/CLAUDE.md b/CLAUDE.md index fbdf86c..4ebe317 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -181,3 +181,6 @@ Preview branches apply all migrations on a fresh empty DB. Delta migrations fail ### #237 — Excess anon grants + duplicate RLS policies Default `GRANT ALL` gives anon TRUNCATE (bypasses RLS). **Only grant what PostgREST needs.** Always `DROP POLICY IF EXISTS` old policies when replacing them. + +### #268 — Supabase blocks sb_secret writes without User-Agent +Supabase treats POST/DELETE with an `sb_secret` service role key and no `User-Agent` as a browser request and returns 403 "Forbidden use of secret API key in browser". Azure Functions' built-in `fetch` sends no User-Agent by default. **Always add `'User-Agent': 'WorkoutLens/1.0 sporty-sync (Azure Functions)'` to every write request (POST, DELETE, PATCH) that uses the service role key.** GET requests are unaffected. diff --git a/app/api/sportySync.js b/app/api/sportySync.js index d038c42..5e36f41 100644 --- a/app/api/sportySync.js +++ b/app/api/sportySync.js @@ -113,6 +113,7 @@ async function syncGymCalendar(context, { shiftDays = 0, daysBack = 0 } = {}) { 'apikey': serviceKey, 'Authorization': `Bearer ${serviceKey}`, 'Prefer': 'return=minimal', + 'User-Agent': 'WorkoutLens/1.0 sporty-sync (Azure Functions)', }, } ); @@ -132,6 +133,7 @@ async function syncGymCalendar(context, { shiftDays = 0, daysBack = 0 } = {}) { 'apikey': serviceKey, 'Authorization': `Bearer ${serviceKey}`, 'Prefer': 'resolution=merge-duplicates,return=minimal', + 'User-Agent': 'WorkoutLens/1.0 sporty-sync (Azure Functions)', }, body: JSON.stringify(rows), }