فهرست منبع

[Scope] Phase 5 task 5.9 shipped: user signout + cookie-name fix

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User 2 ماه پیش
والد
کامیت
18b38f964b
1فایلهای تغییر یافته به همراه31 افزوده شده و 26 حذف شده
  1. 31 26
      PROJECT_SCOPE.md

+ 31 - 26
PROJECT_SCOPE.md

@@ -28,30 +28,30 @@ MovieDice solves group decision paralysis by combining collaborative curation (e
 
 ### In Scope (MVP)
 
-| Feature                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Priority    |
-| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
+| Feature                            | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Priority    |
+| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
 | Landing page                       | Centered logo, splash text, slot-machine reel animation on Roll the Dice (3 reels spinning through ~20 automatically fetched posters from TMDB popular/top-rated, replaced on each periodic refresh), Genre Roll against TMDB (no login required), Login button, scrolling About section, 3-step how-it-works demo with alternating left-right-left alignment, TMDB attribution footer, privacy policy link. Roll result card emerges in the carousel center and stays settled until next user action. Result card has an "i" info button that opens a MoreInfoModal (plot, Add to list → /login, Watch Trailer). | Must Have   |
-| Anonymous auth via Supabase        | User picks a display name and optional avatar color; account created via `supabase.auth.signInAnonymously()` which issues a JWT for RLS-compatible sessions; persisted on device via `@supabase/ssr` cookie-based session handling                                                                                                                                                                                                                                                                                                    | Must Have   |
-| Recovery code                      | A 24-character alphanumeric code (128-bit entropy) shown once after account creation that lets users reclaim their identity on a new device; hashed with Argon2id (memory=19456 KiB, iterations=2, parallelism=1, output=32 bytes) before storage; single-use (invalidated after successful claim); claim endpoint rate-limited (5 failed attempts per IP per 15-minute window)                                                                                                                                                       | Must Have   |
-| Group creation with invite code    | Creator gets a short human-readable code in WORD-WORD format (e.g., WOLF-MOON; word list: 2,000+ words, 3-8 characters each, offensive/confusing terms filtered, uppercase display, case-insensitive comparison, collision check on generation) to share; creator becomes List Admin                                                                                                                                                                                                                                                  | Must Have   |
-| Group join via invite code         | Enter code to join a group and access its shared list; regular member role assigned; join endpoint rate-limited (5-10 failed attempts per IP per 15-minute window); group join is a server-side operation via service role key (not client-side INSERT)                                                                                                                                                                                                                                                                               | Must Have   |
-| List Admin permissions             | Creator can rename the list, initiate list deletion or ownership transfer (on self-removal), remove members, and regenerate the invite code                                                                                                                                                                                                                                                                                                                                                                                           | Must Have   |
-| Regular user permissions           | Members can add/remove movies, mark movies as watched, and leave the list                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Must Have   |
-| Movie search (TMDB integration)    | Search bar queries TMDB via server-side API proxy (`/api/tmdb/*`) with debounce (~300ms); all calls set `include_adult=false`; results show below a separator from in-list results                                                                                                                                                                                                                                                                                                                                                    | Must Have   |
-| Add/remove movie                   | Tap a TMDB result to add it; poster, genres, title, year, and trailer URL auto-populate (trailer URL stored in DB and refreshed periodically); added-by attribution stored                                                                                                                                                                                                                                                                                                                                                            | Must Have   |
-| Poster-forward grid view           | 2-column evenly-scaling grid; each card shows movie poster (full bleed, using TMDB native sized URLs) with title below and meaningful `alt` text; added-by avatar overlaid top-right; binoculars emoji overlaid top-left when watched; infinite scroll loading 12 movies initially                                                                                                                                                                                                                                                    | Must Have   |
-| Expanded movie card (inline panel) | Tapping a poster expands a full-page-width panel downward, inserted below that row in the grid — not a modal or popup. Panel order (top to bottom): full-size poster → title → "Added by [username]" → genre tags → Watched It + Trailer (side by side) → Delete (centered below). Delete uses two-tap shake-and-confirm. Watched It toggles watched state. Trailer opens in new tab. Panel collapses on tap outside. | Must Have   |
-| Genre filter                       | Tapping a genre tag in the expanded panel filters the grid to that genre; announce filter state change via `aria-live="polite"` region                                                                                                                                                                                                                                                                                                                                                                                                | Must Have   |
-| Roll the Dice                      | Large button pinned above the list; triggers an animated randomizer that lands on one unwatched movie from the group list; announce result via `aria-live="polite"` region                                                                                                                                                                                                                                                                                                                                                            | Must Have   |
-| Re-roll                            | Tapping Roll again re-rolls from the same eligible pool                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Must Have   |
-| Genre + Emotion Roll               | Secondary button accepting comma-separated genres and/or emotion keywords; maps emotions to genre IDs, filters pool, then rolls                                                                                                                                                                                                                                                                                                                                                                                                       | Must Have   |
-| Watched state (per group, toggle)  | Marking a movie watched moves it to a collapsed "Watched" section; marking again moves it back. Binoculars overlay and button color update in real time across all members. Announce state change via `aria-live="polite"` region.                                                                                                                                                                                                                                                                                                    | Must Have   |
-| Real-time list sync                | Add, remove, and watched-status changes appear live on all connected group members' screens (Supabase real-time); subscribe only to the currently-viewed list, unsubscribe on navigation away                                                                                                                                                                                                                                                                                                                                         | Must Have   |
-| Logged-in home page                | Upon login or return visit (stored user ID detected), user lands on a home page that mirrors the landing page layout but shows their lists as cards and replaces Login with Create List; Roll the Dice and Genre Roll roll across all user lists combined and display the result as a standalone teaser card on the home page (no navigation into a specific list)                                                                                                                                                                    | Must Have   |
-| Multi-group support                | A user can belong to more than one group; all their lists appear as cards on the home page                                                                                                                                                                                                                                                                                                                                                                                                                                            | Should Have |
-| Invite code rotation               | List Admin can regenerate the invite code to revoke access for anyone with the old code                                                                                                                                                                                                                                                                                                                                                                                                                                               | Should Have |
-| Trailer URL periodic refresh       | Background job via Node.js cron container on a bi-weekly cadence refreshes stored trailer URLs only for movies where trailer_url is currently null. Note: this behavior should be reassessed post-launch to also refresh stale URLs after a certain age.                                                                                                                                                                                                                                                                              | Should Have |
-| Master Admin panel                 | Site-owner-only admin page with TOTP 2FA; can search and delete any list or user (deletion must remove both `public.users` row and `auth.users` record via `supabase.auth.admin.deleteUser()`); credentials set via environment variables; session managed via iron-session v8 (HttpOnly, Secure, SameSite=Strict cookie, 8-hour expiry)                                                                                                                                                                                              | Must Have   |
+| Anonymous auth via Supabase        | User picks a display name and optional avatar color; account created via `supabase.auth.signInAnonymously()` which issues a JWT for RLS-compatible sessions; persisted on device via `@supabase/ssr` cookie-based session handling                                                                                                                                                                                                                                                                                                                                                                                | Must Have   |
+| Recovery code                      | A 24-character alphanumeric code (128-bit entropy) shown once after account creation that lets users reclaim their identity on a new device; hashed with Argon2id (memory=19456 KiB, iterations=2, parallelism=1, output=32 bytes) before storage; single-use (invalidated after successful claim); claim endpoint rate-limited (5 failed attempts per IP per 15-minute window)                                                                                                                                                                                                                                   | Must Have   |
+| Group creation with invite code    | Creator gets a short human-readable code in WORD-WORD format (e.g., WOLF-MOON; word list: 2,000+ words, 3-8 characters each, offensive/confusing terms filtered, uppercase display, case-insensitive comparison, collision check on generation) to share; creator becomes List Admin                                                                                                                                                                                                                                                                                                                              | Must Have   |
+| Group join via invite code         | Enter code to join a group and access its shared list; regular member role assigned; join endpoint rate-limited (5-10 failed attempts per IP per 15-minute window); group join is a server-side operation via service role key (not client-side INSERT)                                                                                                                                                                                                                                                                                                                                                           | Must Have   |
+| List Admin permissions             | Creator can rename the list, initiate list deletion or ownership transfer (on self-removal), remove members, and regenerate the invite code                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Must Have   |
+| Regular user permissions           | Members can add/remove movies, mark movies as watched, and leave the list                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | Must Have   |
+| Movie search (TMDB integration)    | Search bar queries TMDB via server-side API proxy (`/api/tmdb/*`) with debounce (~300ms); all calls set `include_adult=false`; results show below a separator from in-list results                                                                                                                                                                                                                                                                                                                                                                                                                                | Must Have   |
+| Add/remove movie                   | Tap a TMDB result to add it; poster, genres, title, year, and trailer URL auto-populate (trailer URL stored in DB and refreshed periodically); added-by attribution stored                                                                                                                                                                                                                                                                                                                                                                                                                                        | Must Have   |
+| Poster-forward grid view           | 2-column evenly-scaling grid; each card shows movie poster (full bleed, using TMDB native sized URLs) with title below and meaningful `alt` text; added-by avatar overlaid top-right; binoculars emoji overlaid top-left when watched; infinite scroll loading 12 movies initially                                                                                                                                                                                                                                                                                                                                | Must Have   |
+| Expanded movie card (inline panel) | Tapping a poster expands a full-page-width panel downward, inserted below that row in the grid — not a modal or popup. Panel order (top to bottom): full-size poster → title → "Added by [username]" → genre tags → Watched It + Trailer (side by side) → Delete (centered below). Delete uses two-tap shake-and-confirm. Watched It toggles watched state. Trailer opens in new tab. Panel collapses on tap outside.                                                                                                                                                                                             | Must Have   |
+| Genre filter                       | Tapping a genre tag in the expanded panel filters the grid to that genre; announce filter state change via `aria-live="polite"` region                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | Must Have   |
+| Roll the Dice                      | Large button pinned above the list; triggers an animated randomizer that lands on one unwatched movie from the group list; announce result via `aria-live="polite"` region                                                                                                                                                                                                                                                                                                                                                                                                                                        | Must Have   |
+| Re-roll                            | Tapping Roll again re-rolls from the same eligible pool                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Must Have   |
+| Genre + Emotion Roll               | Secondary button accepting comma-separated genres and/or emotion keywords; maps emotions to genre IDs, filters pool, then rolls                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | Must Have   |
+| Watched state (per group, toggle)  | Marking a movie watched moves it to a collapsed "Watched" section; marking again moves it back. Binoculars overlay and button color update in real time across all members. Announce state change via `aria-live="polite"` region.                                                                                                                                                                                                                                                                                                                                                                                | Must Have   |
+| Real-time list sync                | Add, remove, and watched-status changes appear live on all connected group members' screens (Supabase real-time); subscribe only to the currently-viewed list, unsubscribe on navigation away                                                                                                                                                                                                                                                                                                                                                                                                                     | Must Have   |
+| Logged-in home page                | Upon login or return visit (stored user ID detected), user lands on a home page that mirrors the landing page layout but shows their lists as cards and replaces Login with Create List; Roll the Dice and Genre Roll roll across all user lists combined and display the result as a standalone teaser card on the home page (no navigation into a specific list)                                                                                                                                                                                                                                                | Must Have   |
+| Multi-group support                | A user can belong to more than one group; all their lists appear as cards on the home page                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Should Have |
+| Invite code rotation               | List Admin can regenerate the invite code to revoke access for anyone with the old code                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Should Have |
+| Trailer URL periodic refresh       | Background job via Node.js cron container on a bi-weekly cadence refreshes stored trailer URLs only for movies where trailer_url is currently null. Note: this behavior should be reassessed post-launch to also refresh stale URLs after a certain age.                                                                                                                                                                                                                                                                                                                                                          | Should Have |
+| Master Admin panel                 | Site-owner-only admin page with TOTP 2FA; can search and delete any list or user (deletion must remove both `public.users` row and `auth.users` record via `supabase.auth.admin.deleteUser()`); credentials set via environment variables; session managed via iron-session v8 (HttpOnly, Secure, SameSite=Strict cookie, 8-hour expiry)                                                                                                                                                                                                                                                                          | Must Have   |
 
 ### Out of Scope (Future)
 
@@ -247,8 +247,11 @@ MovieDice solves group decision paralysis by combining collaborative curation (e
    - Regenerate invite code (revokes old code)
    - Display current invite code with copy-to-clipboard
 3. Regular members see a settings menu with only: "Leave this list" option
+```
 
 Admin Self-Removal / Ownership Transfer flow:
+
+```
    a. List Admin taps "Leave this list" (or equivalent self-removal action)
    b. If other members exist:
       - "Transfer Ownership" popup appears before the admin can leave
@@ -312,7 +315,7 @@ List Deletion flow (separate from self-removal):
 | Auth                 | Supabase Anonymous Sign-In via `@supabase/ssr`          | `signInAnonymously()` — no email, instant account, JWT for RLS; cookie-based session via `createBrowserClient`/`createServerClient` |
 | Movie Data           | TMDB API                                                | Posters, genres, metadata, trailer URLs; all calls via server-side proxy with `include_adult=false`                                 |
 | State Management     | TanStack Query (React Query)                            | Server state sync, caching with explicit `staleTime`, loading states                                                                |
-| Admin Sessions       | iron-session v8                                        | Encrypted HttpOnly cookie for Master Admin TOTP sessions (use v8 README directly — v7 patterns are incompatible)                    |
+| Admin Sessions       | iron-session v8                                         | Encrypted HttpOnly cookie for Master Admin TOTP sessions (use v8 README directly — v7 patterns are incompatible)                    |
 | 2FA (Master Admin)   | TOTP via otplib (or equivalent)                         | Authenticator-app compatible; TOTP secret never exposed client-side                                                                 |
 | PWA                  | @serwist/next                                           | App Router compatible, Workbox 7; requires authoring `app/sw.ts` and `tsconfig.worker.json`                                         |
 | Image Optimization   | sharp (local assets only)                               | TMDB posters use native sized URLs from TMDB CDN directly (not next/image)                                                          |
@@ -530,7 +533,9 @@ Configure HTTP security headers at the Caddy reverse proxy level (not in next.co
 - [ ] 5.6 — Error handling: invalid invite code, TMDB API failure, network errors
 - [ ] 5.7 — Configure HTTP security headers in Caddyfile: CSP (with self-hosted URLs, not \*.supabase.co), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, HSTS (start with max-age=86400, increase before launch), Permissions-Policy; use Report-Only mode during testing
 - [ ] 5.8 — Final MVP smoke test and Docker production deployment
-- [ ] 5.9 — Implement user-facing sign-out: call `supabase.auth.signOut()` server-side (or via a dedicated API route), clear the session cookie, and redirect to the landing page. DECISIONS NEEDED: (a) where the sign-out control lives (header menu, settings page, or both); (b) whether to show a confirmation dialog warning that signing out of an anonymous account without a saved recovery code will orphan the account; (c) redirect target after sign-out (landing page assumed). Do not confuse with `/api/admin/logout`, which handles Master Admin TOTP sessions only.
+- [x] 5.9 — Implement user-facing sign-out. SHIPPED (2026-05-02, commits 44ff76c→582a7fd→add8745): `POST /api/auth/signout` calls `supabase.auth.signOut()` server-side and returns `Set-Cookie` clears; `GET/POST /logout` is a redirect-to-`/` alias for manual/debug links. `<SignOutButton />` (`src/components/auth/sign-out-button.tsx`) placed in the `(app)` layout header — no avatar/menu. On click: calls API, clears TanStack Query cache, hard-navigates to `/`. No confirmation dialog (decided: anonymous-account warning is deliberately omitted; click = signed out). Distinct from `/api/admin/logout` (iron-session). No `Clear-Site-Data` header (no service worker yet). persistQueryClient cleanup deferred to Phase 8.
+
+  DEV-ENV QUIRK — cookie-name pinning: `SUPABASE_INTERNAL_URL` and `NEXT_PUBLIC_SUPABASE_URL` may differ in dev (different hostnames). The Supabase SDK derives the session cookie name from the URL, so each side silently derived a different name, making `signOut`/`getUser` no-ops. Fixed by pinning `cookieOptions.name` to a value derived from `NEXT_PUBLIC_SUPABASE_URL` in `src/lib/supabase/cookie-name.ts`. Any new `createServerClient` call must include this `cookieOptions.name` — see also CLAUDE.md.
 
 ---
 
@@ -580,7 +585,7 @@ Configure HTTP security headers at the Caddy reverse proxy level (not in next.co
 - [ ] 9.17 — Data retention test: verify inactive account cleanup job correctly identifies and deletes accounts inactive for 12+ months; verify orphan group handling (admin transfer or cascade delete); verify auth.users record removal
 - [ ] 9.18 — Docker security test: verify Kong/Postgres ports not accessible from host; verify Studio not publicly accessible; verify all default Supabase secrets have been replaced; verify Caddy TLS certificates persist across container restart
 - [ ] 9.19 — Backup test: verify pg_dump backup runs daily; test restore procedure
-- [ ] 9.20 — Sign-out test: verify sign-out clears the Supabase session cookie and redirects to landing page; verify signed-out user cannot access authenticated routes; verify sign-out control is distinct from `/api/admin/logout`; confirm any anonymous-account warning (if implemented per 5.9 decision) displays correctly before sign-out completes
+- [ ] 9.20 — Sign-out test: verify sign-out clears the Supabase session cookie and redirects to landing page; verify signed-out user cannot access authenticated routes; verify sign-out control is distinct from `/api/admin/logout`; confirm no anonymous-account warning dialog appears (per 5.9 decision). NOTE: manual click-through verification was performed against the dev server in the 5.9 implementation round; no automated Playwright e2e test was added. This task remains open until automated coverage is in place.
 
 ### Phase 10: Launch (June 22 - July 5, 2026) — Full Feature Complete