From 982c35287db0f5190e33211846b042c7a0cba657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 11 Sep 2026 09:26:10 +0200 Subject: [PATCH] fix(demo): the setup flow's baseline writes are allowlisted A demo visitor who typed a height on the confirm screen got a 403 and a generic toast, and the "someone I look after" arm could only take "Finish without the profile": the profile PUT, the self-context PUT and the managed-profile POST were missing from the demo allowlist, so only the empty-form path completed. All three are added, pinned per method as the rest of the list is. None of them creates health data. --- .../proxy-demo-mode-allowlist.test.ts | 30 +++++++++++++++++++ src/proxy.ts | 13 ++++++++ 2 files changed, 43 insertions(+) diff --git a/src/__tests__/proxy-demo-mode-allowlist.test.ts b/src/__tests__/proxy-demo-mode-allowlist.test.ts index d51c61082..0f7a6af0d 100644 --- a/src/__tests__/proxy-demo-mode-allowlist.test.ts +++ b/src/__tests__/proxy-demo-mode-allowlist.test.ts @@ -106,6 +106,36 @@ describe("proxy.ts DEMO_MODE mutation allowlist", () => { expect(missing, "onboarding writes the demo cannot make").toEqual([]); }); + it("permits the baseline writes the confirm screen makes (M-m)", () => { + // v1.39 (Wave C, C6) — a demo visitor who typed a height got a 403 and a + // generic toast, and "someone I look after" could only take "Finish + // without the profile": the three writes the confirm screen makes were + // not on the list. None of them is health data — the account's own + // profile fields, the encrypted self-context the anamnesis card writes, + // and the managed record the flow's guardian arm creates. + for (const [path, method] of [ + ["/api/auth/profile", "PUT"], + ["/api/coach/about-me", "PUT"], + ["/api/managed-profiles", "POST"], + ] as const) { + expect( + proxy(makeRequest(path, method)).status, + `${method} ${path} is blocked in the demo`, + ).not.toBe(403); + } + }); + + it("does not drag in a sibling verb on the baseline paths", () => { + expect(proxy(makeRequest("/api/auth/profile", "DELETE")).status).toBe(403); + expect(proxy(makeRequest("/api/managed-profiles", "DELETE")).status).toBe( + 403, + ); + // The per-profile routes are a different path and stay closed. + expect( + proxy(makeRequest("/api/managed-profiles/abc", "DELETE")).status, + ).toBe(403); + }); + it("still blocks a health-data mutation (POST /api/measurements)", () => { const res = proxy(makeRequest("/api/measurements", "POST")); expect(res.status).toBe(403); diff --git a/src/proxy.ts b/src/proxy.ts index 5184878e7..b73d8c3ed 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -174,6 +174,19 @@ const DEMO_MUTATION_ALLOWLIST: ReadonlyArray<{ path: string; method: string }> = // Neither creates health data. { path: "/api/onboarding/disclaimer", method: "POST" }, { path: "/api/onboarding/tour", method: "POST" }, + // v1.39 (Wave C, C6) — the three writes the confirm screen makes. Without + // them a demo visitor who typed a height got a 403 and a generic toast, + // and the "someone I look after" arm could only take "Finish without the + // profile" — only the empty-form/Skip path completed, so the demo did not + // in fact walk the same flow (design spec §Principles 5). None of the + // three creates health data: the caller's own profile fields, the + // encrypted self-context behind the optional anamnesis card, and the + // managed record the guardian arm of the flow exists to create. The + // per-profile routes (`/api/managed-profiles/{id}`, its guardians) are + // different paths and stay closed. + { path: "/api/auth/profile", method: "PUT" }, + { path: "/api/coach/about-me", method: "PUT" }, + { path: "/api/managed-profiles", method: "POST" }, ]; // Legacy route redirects (German → English)