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.
This commit is contained in:
Marc-André Bombeck committed 2026-09-11 10:55:33 +02:00
1 parent 5c194cc9b9
commit 4c2175a584
2 files changed
+30 -9

No files matched your search

+16 -3
View File
@@ -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"');
});
});
+14 -6
View File
@@ -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 (
<div className="w-full max-w-sm">
<div className="border-border bg-card rounded-xl border p-6 shadow-lg shadow-black/20 sm:p-8">