Route guards: the account page needs a Session, sign-in and sign-up send a signed-in Player home #409

Closed
opened 2026-10-15 12:31:53 +00:00 by root · 13 comments
root commented 2026-10-15 12:31:53 +00:00 (Migrated from gitlab.core-project.space)

Problem Statement

  • A Player with no Session who opens the account page sees a signed-out panel on that page. After signing in from it, they land on the home page, not on the account page they wanted.
  • A signed-in Player can still open the sign-in and sign-up pages, which offer them nothing.

Source: the front audit, docs/research/front-audit.md, sections 1 (point 10) and 2.1. The owner decided both redirects (section 5).

Solution

  • Opening the account page without a Session goes to the sign-in page, which remembers where the Player was going. After signing in, the Player lands there.
  • Opening sign-in or sign-up while signed in goes to the home page.
  • The checks run before the page renders, so no page flashes before the redirect.

User Stories

  1. As a Player with no Session, I want opening the account page to take me to sign-in, so that I can sign in instead of reading a dead end.
  2. As a Player sent to sign-in from the account page, I want to land back on the account page, on the tab I asked for, once signed in, so that I finish what I started.
  3. As a Player sent to sign-in who signs up instead, I want to land back where I was going too, so that both doors lead to the same place.
  4. As a Player who signs in from the navigation, with nowhere remembered, I want to land on the home page as today, so that nothing changes for the usual path.
  5. As a Player who signs in and is offered to add my Guest Games, I want the offer first, then my destination, so that the claim still comes before I move on (ADR 0035).
  6. As a signed-in Player, I want opening sign-in or sign-up to take me to the home page, so that I am never asked to sign in again.
  7. As a Player, I don't want the account page or the sign-in page to flash before a redirect, so that the switch is clean.
  8. As a Player whose Session ends while on the account page (signed out elsewhere, deleted, refresh refused), I want the page to follow, so that I am not left on an Account I can no longer read.
  9. As a Player, I want a remembered destination to be a page of this site only, so that a crafted link cannot send me elsewhere after signing in.
  10. As a Player using a screen reader, I want the page I land on to announce itself as any navigation does, so that the redirect is not silent.

Seams

  • S1 — the app's router, mounted by the router harness, with the auth fakes (front view test). Covers 1-10: the redirects, the remembered destination and tab, the claim offer before the destination, a Session lost on the account page, an outside destination refused.
  • S2 — apps/e2e (e2e). Covers 1-2 and 6 in a real browser: a new test in accounts.spec opens the account page with no Session, signs in, and lands on the account page; then opens sign-in while signed in and lands home.

Implementation Decisions

  • beforeLoad on the account route, the sign-in route and the sign-up route. Each reads the Account through Query (ensureQueryData of the existing Account query options), so the router and the components share one answer (ADR 0019). No auth object is added to the router context: the Account query already is the source of truth.
  • The redirect to sign-in carries the destination as a validated search parameter (a same-site path only), taken from location.href in beforeLoad, per the Router docs. Sign-in and sign-up navigate there after success, after the claim offer where there is one.
  • Session changes. Wherever a Session changes today (sign-in, sign-up, sign-out, reset, deletion, a refused refresh), the router is invalidated after Query is reset, so guards run again. Watch for the stale-context issue the official example works around with a sleep(1); a test must show the guard sees the new Session.
  • The signed-out panel on the account page goes, with its copy, once nothing renders it.
  • Copy (ADR 0025): no new copy expected; any goes into the catalogue in English and French.
  • ADRs: 0019, 0025, 0030 (Sessions), 0035 (claims). No new ADR.

Testing Decisions

  • Prior art: router.test.tsx (mountRouter), session-refresh.test.tsx (a lost Session), sign-in.test.tsx (the claim offer); accounts.spec.ts in apps/e2e.
  • Edge and unhappy paths: the Account read fails (the router's error component, not a redirect loop); a destination outside the site; a destination with a tab; a refresh refused while on the account page; a sign-in refused (stay on sign-in, destination kept).

Out of Scope

  • Guarding other routes: My games and the Game page serve Guests too.
  • A login wall, or any change to what a Guest can do.
  • Server-side authorization, which the API already enforces.

Further Notes

## Problem Statement - A Player with no Session who opens the account page sees a signed-out panel on that page. After signing in from it, they land on the home page, not on the account page they wanted. - A signed-in Player can still open the sign-in and sign-up pages, which offer them nothing. Source: the front audit, `docs/research/front-audit.md`, sections 1 (point 10) and 2.1. The owner decided both redirects (section 5). ## Solution - Opening the account page without a Session goes to the sign-in page, which remembers where the Player was going. After signing in, the Player lands there. - Opening sign-in or sign-up while signed in goes to the home page. - The checks run before the page renders, so no page flashes before the redirect. ## User Stories 1. As a Player with no Session, I want opening the account page to take me to sign-in, so that I can sign in instead of reading a dead end. 2. As a Player sent to sign-in from the account page, I want to land back on the account page, on the tab I asked for, once signed in, so that I finish what I started. 3. As a Player sent to sign-in who signs up instead, I want to land back where I was going too, so that both doors lead to the same place. 4. As a Player who signs in from the navigation, with nowhere remembered, I want to land on the home page as today, so that nothing changes for the usual path. 5. As a Player who signs in and is offered to add my Guest Games, I want the offer first, then my destination, so that the claim still comes before I move on (ADR 0035). 6. As a signed-in Player, I want opening sign-in or sign-up to take me to the home page, so that I am never asked to sign in again. 7. As a Player, I don't want the account page or the sign-in page to flash before a redirect, so that the switch is clean. 8. As a Player whose Session ends while on the account page (signed out elsewhere, deleted, refresh refused), I want the page to follow, so that I am not left on an Account I can no longer read. 9. As a Player, I want a remembered destination to be a page of this site only, so that a crafted link cannot send me elsewhere after signing in. 10. As a Player using a screen reader, I want the page I land on to announce itself as any navigation does, so that the redirect is not silent. ## Seams - **S1 — the app's router, mounted by the router harness, with the auth fakes** (front view test). Covers 1-10: the redirects, the remembered destination and tab, the claim offer before the destination, a Session lost on the account page, an outside destination refused. - **S2 — `apps/e2e`** (e2e). Covers 1-2 and 6 in a real browser: a new test in `accounts.spec` opens the account page with no Session, signs in, and lands on the account page; then opens sign-in while signed in and lands home. ## Implementation Decisions - **`beforeLoad` on the account route, the sign-in route and the sign-up route.** Each reads the Account through Query (`ensureQueryData` of the existing Account query options), so the router and the components share one answer (ADR 0019). No auth object is added to the router context: the Account query already is the source of truth. - **The redirect** to sign-in carries the destination as a validated search parameter (a same-site path only), taken from `location.href` in `beforeLoad`, per the Router docs. Sign-in and sign-up navigate there after success, after the claim offer where there is one. - **Session changes.** Wherever a Session changes today (sign-in, sign-up, sign-out, reset, deletion, a refused refresh), the router is invalidated after Query is reset, so guards run again. Watch for the stale-context issue the official example works around with a `sleep(1)`; a test must show the guard sees the new Session. - **The signed-out panel** on the account page goes, with its copy, once nothing renders it. - **Copy (ADR 0025):** no new copy expected; any goes into the catalogue in English and French. - **ADRs:** 0019, 0025, 0030 (Sessions), 0035 (claims). No new ADR. ## Testing Decisions - Prior art: `router.test.tsx` (`mountRouter`), `session-refresh.test.tsx` (a lost Session), `sign-in.test.tsx` (the claim offer); `accounts.spec.ts` in `apps/e2e`. - Edge and unhappy paths: the Account read fails (the router's error component, not a redirect loop); a destination outside the site; a destination with a tab; a refresh refused while on the account page; a sign-in refused (stay on sign-in, destination kept). ## Out of Scope - Guarding other routes: My games and the Game page serve Guests too. - A login wall, or any change to what a Guest can do. - Server-side authorization, which the API already enforces. ## Further Notes - Audit: `docs/research/front-audit.md` on branch `claude/nestjs-best-practices-audit-716d79`. - Fits one merge request: work it with `implement` on this spec. - Easier after the rules spec (the sign-in page is one file then), but not blocked by it. - Docs: <https://tanstack.com/router/latest/docs/framework/react/guide/authenticated-routes>; official example: <https://github.com/TanStack/router/blob/main/examples/react/authenticated-routes/src/routes/login.tsx>.
root commented 2026-10-15 12:57:59 +00:00 (Migrated from gitlab.core-project.space)

mentioned in merge request !533

mentioned in merge request !533
root commented 2026-10-15 22:11:50 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 4cf15eb2efbfab85fc0fe292c67cdcca68732e27

mentioned in commit 4cf15eb2efbfab85fc0fe292c67cdcca68732e27
root commented 2026-10-15 22:11:50 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit b818f65dfb901ad349e99c96be2f59b879dd3349

mentioned in commit b818f65dfb901ad349e99c96be2f59b879dd3349
root commented 2026-10-15 22:11:50 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit fb942db36cd91a4bb9731e54f3d6a3b451643d9c

mentioned in commit fb942db36cd91a4bb9731e54f3d6a3b451643d9c
root commented 2026-10-15 22:12:00 +00:00 (Migrated from gitlab.core-project.space)

mentioned in merge request !542

mentioned in merge request !542
root commented 2026-10-15 22:12:24 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 04f2d27157a54e9cd08288eb86e357edcbc6e465

mentioned in commit 04f2d27157a54e9cd08288eb86e357edcbc6e465
root commented 2026-10-15 22:12:24 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit d76c8f382c3c60b5ed1f2aef26f1842a8215bbfe

mentioned in commit d76c8f382c3c60b5ed1f2aef26f1842a8215bbfe
root commented 2026-10-15 22:12:24 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 348a3e7fa45606819f341c851bedebb6825e0037

mentioned in commit 348a3e7fa45606819f341c851bedebb6825e0037
root commented 2026-10-15 22:17:08 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 4386078b5a

mentioned in commit 4386078b5a688daf98501f2343f67f67677e4dac
root commented 2026-10-15 22:17:08 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit 54bd79bc0a

mentioned in commit 54bd79bc0af1ed128cdd23de245e8ef6ca5f1ef6
root commented 2026-10-15 22:17:08 +00:00 (Migrated from gitlab.core-project.space)

mentioned in commit f42c5dcac8

mentioned in commit f42c5dcac88bc6397e284dce54adbe70c2dab2fc
root (Migrated from gitlab.core-project.space) closed this issue 2026-10-15 22:35:37 +00:00
root commented 2026-10-16 08:13:50 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #440

mentioned in issue #440
root commented 2026-10-16 08:20:07 +00:00 (Migrated from gitlab.core-project.space)

mentioned in issue #443

mentioned in issue #443
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amaurycoudr/chess-project#409
No description provided.