diff --git a/app/api/local.settings.json.example b/app/api/local.settings.json.example index ac14e7a..a64a550 100644 --- a/app/api/local.settings.json.example +++ b/app/api/local.settings.json.example @@ -8,6 +8,6 @@ "VITE_SUPABASE_ANON_KEY": "eyJ...", "SUPABASE_ANON_KEY": "eyJ...", "SUPABASE_SERVICE_ROLE_KEY": "eyJ...", - "SPORTY_SYNC_API_KEY": "any-secret-string-you-choose" + "SPORTY_SYNC_API_KEY": "any-secret-string-you-choose (used by /api/sporty-health monitoring endpoint only)" } } diff --git a/app/api/sportySync.js b/app/api/sportySync.js index 03b647f..502e376 100644 --- a/app/api/sportySync.js +++ b/app/api/sportySync.js @@ -1,4 +1,5 @@ import { app } from '@azure/functions'; +import { verifySupabaseJwt } from './claudeUtils.js'; import { normalizeName } from './sportyUtils.js'; // BU 8 is hardcoded for the single-gym MVP (Sporty Thon Senter Ski). @@ -176,14 +177,20 @@ app.http('sportySyncHealth', { // ── HTTP trigger: manual kick + optional backfill ───────────────────── // POST /api/sporty-sync → sync today // POST /api/sporty-sync {"shiftDays":-7} → duplicate current data 7 days back -// Requires header: x-api-key: +// Requires header: X-Supabase-Token: +// (Azure SWA hijacks the Authorization header — never use it for app JWTs) app.http('sportySyncHttp', { methods: ['POST'], route: 'sporty-sync', authLevel: 'anonymous', handler: async (request, context) => { - const expectedKey = process.env.SPORTY_SYNC_API_KEY; - if (!expectedKey || request.headers.get('x-api-key') !== expectedKey) { + const token = request.headers.get('x-supabase-token'); + const userId = await verifySupabaseJwt( + token, + process.env.SUPABASE_URL, + process.env.SUPABASE_ANON_KEY, + ); + if (!userId) { return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401, headers: { 'Content-Type': 'application/json' }, diff --git a/app/src/components/Home.jsx b/app/src/components/Home.jsx index 4ca2e0d..85c5b06 100644 --- a/app/src/components/Home.jsx +++ b/app/src/components/Home.jsx @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import { BodySVG } from "../lib/bodymap.jsx"; import { fetchLastSession, fetchThisWeekSessions } from "../lib/db"; import { extractMuscles, logDevError, getIntlLocale, toWeekIso, isoWeekMonday, toIsoDate } from "../lib/utils"; +import { supabase } from "../lib/supabase"; import PageShell, { SectionLabel, AccentChip } from "./PageShell"; import { useNav } from "../lib/NavContext"; @@ -45,9 +46,12 @@ export default function Home({ onShowHistoryWithDate }) { setSyncState('loading'); setSyncMsg(''); try { + const { data: { session } } = await supabase.auth.getSession(); const res = await fetch('/api/sporty-sync', { method: 'POST', - headers: { 'x-api-key': import.meta.env.VITE_SPORTY_SYNC_API_KEY ?? '' }, + headers: { + ...(session?.access_token ? { 'X-Supabase-Token': session.access_token } : {}), + }, }); const json = await res.json(); setSyncState(res.ok ? 'ok' : 'error');