mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(strava): add per-user BYO-OAuth workout source + Garmin indirect-path docs
Add Strava as a per-user BYO-OAuth workout source, mirroring the shipped WHOOP/Oura/Polar pattern. Strava caps every new app at single-player mode, so each self-hoster registers their own app and pastes the client id/secret into Settings (falling back to a shared env app). Strava is a workout source only — it exposes no daily metrics and no training-depth analytics are derived. - Schema (migration 0233): per-user encrypted client id/secret + access/refresh tokens, plaintext athlete id, incremental cursor, and backfill marker on `User`; `STRAVA` added to `MeasurementSource`. - Token model follows Oura's reactive-refresh-on-401 with a compare-and-set on the stored refresh ciphertext, because Strava rotates its refresh token on every refresh. - Client over safeFetch: authorization-code + refresh, activity list + budget- aware per-activity detail (for calories/HR), honouring the 200/15min cap; each activity maps to one `Workout` row. Activity name/description stay in metadata as data, never surfaced or interpolated. - Dedup reuses the existing source-agnostic two-layer picker: `STRAVA` slots into the workout ladder below the device-native wearables and above MANUAL/IMPORT, so an Apple-Watch + Strava twin collapses to one canonical row. - Poll-cohort hourly sync (:17) + self-converging boot backfill, wired into the integration-sync registrar. Webhook deferred. - OAuth routes (connect/callback/credentials/disconnect/status/test) with signed state, cross-account re-auth rejection, and a surgical `www.strava.com` connect-src gated to the Strava surfaces. - Settings card + consolidated status envelope entry; env + compose whitelist; i18n across all six locales. - Garmin: no direct connector is possible (business-partner-only). Add docs/integrations/garmin.md documenting the Apple Health / Google Health Connect path and Garmin's withheld proprietary scores, cross-linked from both platform docs, plus a non-connector info note in the Connections panel. Regenerate docs/api/openapi.yaml for the new `STRAVA` MeasurementSource value.
This commit is contained in:
1 parent
89634e20ee
commit
75eb0637e6
49 files changed
+2683
-15
No files matched your search
+10
-1
@@ -410,6 +410,15 @@ export function proxy(request: NextRequest) {
|
||||
pathname.startsWith("/settings/integrations/whoop") ||
|
||||
pathname.startsWith("/api/whoop/");
|
||||
const whoopConnectSrc = isWhoopRoute ? " https://api.prod.whoop.com" : "";
|
||||
// v1.28.x — `www.strava.com` gated to the Strava settings surface +
|
||||
// `/api/strava/*`, mirroring the WHOOP gating shape. The Strava data client
|
||||
// lives server-side and the OAuth handshake is a browser redirect (not a
|
||||
// fetch), so this is belt-and-suspenders parity — no other surface ever needs
|
||||
// to reach the Strava host from the browser.
|
||||
const isStravaRoute =
|
||||
pathname.startsWith("/settings/integrations/strava") ||
|
||||
pathname.startsWith("/api/strava/");
|
||||
const stravaConnectSrc = isStravaRoute ? " https://www.strava.com" : "";
|
||||
// v1.5.5 — Gravatar host removed from `img-src`. The /me payload
|
||||
// used to return `gravatarUrl: https://www.gravatar.com/avatar/<sha256(email)>`,
|
||||
// which leaked the email digest to Automattic on every authenticated
|
||||
@@ -433,7 +442,7 @@ export function proxy(request: NextRequest) {
|
||||
`default-src 'none'; frame-ancestors 'self';`
|
||||
: isDev
|
||||
? `default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; font-src 'self';`
|
||||
: `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'${aiConnectSrc}${withingsConnectSrc}${whoopConnectSrc}; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self'; report-uri ${cspReportEndpoint}; report-to csp-endpoint;`;
|
||||
: `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'${aiConnectSrc}${withingsConnectSrc}${whoopConnectSrc}${stravaConnectSrc}; font-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; worker-src 'self'; report-uri ${cspReportEndpoint}; report-to csp-endpoint;`;
|
||||
response.headers.set("Content-Security-Policy", csp);
|
||||
|
||||
// Production-only headers. HSTS carries `preload` so the domain stays
|
||||
|
||||
Reference in new issue
Block a user