From 4c2175a58445aca086821c4b158b793eb383d096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 11 Sep 2026 10:55:33 +0200 Subject: [PATCH] fix(auth): the register page reads a session off the account payload `isAuthenticated` holds the last-known state off a localStorage marker when `/api/auth/me` fails at the transport level or with a 5xx, so the shell does not sign people out on a blip. The register page gated its "already signed in" panel on that flag, and on that path there is no account payload: an invited household member opening the link during a 502 window was shown "You are signed in as ." and a sign-out posting to the failing endpoint, with no way to register at all. The gate is now the payload itself, which is the only positive evidence of a session and is equally absent while the probe is in flight. --- src/app/auth/register/__tests__/page.test.tsx | 19 +++++++++++++++--- src/app/auth/register/page.tsx | 20 +++++++++++++------ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/app/auth/register/__tests__/page.test.tsx b/src/app/auth/register/__tests__/page.test.tsx index 47272db5a..1ef5d0564 100644 --- a/src/app/auth/register/__tests__/page.test.tsx +++ b/src/app/auth/register/__tests__/page.test.tsx @@ -11,9 +11,8 @@ * the page rendered. The sign-out click itself is driven in * `e2e/invite-registration.spec.ts`. * - * Mutation check, run: drop the `isAuthenticated` branch → the two signed-in - * cases go red naming the form inputs they found, the anonymous case stays - * green. + * Mutation check, run: drop the signed-in branch → the two signed-in cases go + * red naming the form inputs they found, the anonymous case stays green. */ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; @@ -112,4 +111,18 @@ describe("register page — an invitation cannot be accepted from a live session const markup = render(); expect(markup).not.toContain('data-testid="register-already-signed-in"'); }); + + it("renders the form when the auth probe failed and only a stale marker says otherwise", () => { + // `useAuth` deliberately holds the LAST-KNOWN state when `/api/auth/me` + // fails at the transport level or with a 5xx, so the shell does not sign + // people out on a blip. There is no account payload on that path, so the + // panel would name nobody ("You are signed in as .") and offer a sign-out + // that posts to the endpoint the same failure is affecting. An invited + // household member on a flaky connection could not register at all. + authRef.value = { user: null, isAuthenticated: true, isLoading: false }; + inviteRef.value = TOKEN; + const markup = render(); + expect(markup).not.toContain('data-testid="register-already-signed-in"'); + expect(markup).toContain('id="username"'); + }); }); diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index b47fe23ea..8b1bc9c34 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -27,11 +27,19 @@ export default function RegisterPage() { // v1.38.19 — an invitation creates a NEW account, so it cannot be accepted // from inside a live session; `POST /api/auth/register` refuses one with // 409 `already_authenticated`. Rendering the form anyway would let the - // visitor discover that by filling it in. `isLoading` holds the panel back - // until the session question is actually answered — failing closed the - // other way round would flash "you are already signed in" at somebody who - // is not. - const { user, isAuthenticated, isLoading: authLoading } = useAuth(); + // visitor discover that by filling it in. + // + // The gate is the resolved account payload, not `isAuthenticated`. That + // flag deliberately holds the LAST-KNOWN state off a localStorage marker + // whenever `/api/auth/me` fails at the transport level or with a 5xx, so + // the shell does not sign people out on a blip — and on that path there is + // no `user`. Gating on it would show an invited household member behind a + // 502 a panel that names nobody and offers a sign-out posting to the very + // endpoint that is failing, with no way to register. A payload in hand is + // the only positive evidence of a session, and it is also absent while the + // probe is still in flight, so the panel cannot flash at somebody who is + // not signed in. + const { user } = useAuth(); const [signingOut, setSigningOut] = useState(false); const [email, setEmail] = useState(""); const [username, setUsername] = useState(""); @@ -104,7 +112,7 @@ export default function RegisterPage() { } } - if (isAuthenticated && !authLoading) { + if (user) { return (