Production-style authentication is implemented and verified locally. See Production Auth & Email for Google/SMTP setup, email templates, secure callback URLs, onboarding, and focused verification. Gmail SMTP, the hosted templates, and Google sign-in have been tested; Google remains in Testing until the public launch.
The design/UX/performance phase is complete. See the phase report for the redesigned pages, shared components, public RAWG caching, security checks, measurements and verification limits. Discovery now includes Popular, New releases and Critically acclaimed sections; library/profile state remains uncached and authenticated mutations retain RLS ownership checks.
A minimal gaming-library foundation using Next.js App Router, TypeScript, Tailwind CSS, Supabase Auth/Postgres, and server-side RAWG discovery.
The first product loop is implemented: search → game details → add/manage library → public profile. See the library-phase implementation report for architecture, security, migration verification, changed files, and live test results.
Use Node.js 22+ and the pnpm version pinned in package.json.
pnpm install --frozen-lockfile
pnpm devOpen http://localhost:3000. For a new checkout, copy .env.example to .env.local; do not overwrite an existing environment file.
| Variable | Purpose |
|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Browser-safe Supabase project URL |
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY |
Browser-safe publishable key; RLS enforces access |
RAWG_API_KEY |
Private, server-only RAWG key |
SUPABASE_SECRET_KEY |
Private server-only key used exclusively by the application to cache verified RAWG game references |
APP_URL |
Server-only canonical Auth origin; local default http://localhost:3000, required HTTPS origin in production |
Restart after environment changes. Environment files are ignored except .env.example. The secret key is required for adding games; browsing and existing-library updates use public credentials and user sessions. Never prefix private keys with NEXT_PUBLIC_.
| Boundary | Responsibility |
|---|---|
src/app/ |
Server Component pages, Server Actions, confirmation handlers |
src/components/ |
Small reusable forms, navigation, library list, existing RAWG preview |
src/lib/data/ |
Server-only profile/library queries using the current user's session |
src/lib/auth/session.ts |
Verified identity and protected-route guard |
src/lib/supabase/ |
Typed browser client, per-request server client, cookie refresh, safe server transport |
src/lib/validation/ |
Zod server validation; unrecognized ownership fields are discarded |
src/types/ |
Generated database types and shared form/game types |
src/lib/rawg/server.ts |
Existing server-only API helper returning selected metadata |
supabase/migrations/ |
Reproducible schema, constraints, triggers, grants, and RLS |
Forms use Client Components and useActionState; data fetching stays on the server. Authentication uses @supabase/ssr cookie clients. src/proxy.ts refreshes sessions and propagates cookies/cache headers. Protected pages/actions independently verify identity with auth.getUser(); they do not trust a supplied user ID or a session object alone.
Server Supabase requests use a fixed-origin Node HTTPS adapter to avoid framework fetch tracing of passwords/auth headers. The existing RAWG helper similarly keeps credential-bearing URLs out of fetch tracing. Both enforce timeouts, reject redirects, and sanitize upstream errors. Framework request logging is disabled. The publishable key and a user's own auth cookies are intentionally browser-accessible; private provider keys are not.
Applied additive migration: supabase/migrations/20260919230110_game_library_foundation.sql, on the existing game-library project (phupiulpcyxhxhxsrzpk). Inspection before application found no application tables, migrations, Auth users, or storage buckets.
| Table | Purpose and constraints | Access |
|---|---|---|
| profiles | Auth-user UUID primary/foreign key; unique lowercase username (3–40 letters/digits/underscores); display name ≤80, bio ≤500; optional HTTPS avatar; timestamps | Public SELECT; owner-only INSERT/UPDATE |
| games | Small reference cache; unique positive RAWG ID, slug/name, optional RAWG cover/release date, timestamps | Public SELECT; browser roles cannot write |
| user_games | Composite user/game primary key and foreign keys; status, favorite, optional integer rating 1–10, optional hours 0–100000 with one decimal, timestamps | Public SELECT; owner-only INSERT/UPDATE/DELETE |
All three tables have RLS enabled. Eight explicit policies cover public reads and owner writes. UPDATE policies check ownership both before and after updates. Column grants also prevent changing ownership, usernames, or timestamps. Foreign keys and supporting indexes protect/query relationships. Status is an enum: playing, played, want_to_play, dropped.
An Auth insert trigger atomically creates email-signup profiles from the new Auth ID and validated username. New social accounts and email signups without a username finish through owner-only onboarding. Existing usernames remain immutable. The trigger's SECURITY DEFINER function lives in an unexposed private schema with an empty search path; browser roles cannot execute it. User metadata supplies display input, never authorization.
Usernames are immutable in V1; display name and bio are editable. Profiles/libraries are public by design. Public profiles contain no email or password. The server verifies RAWG metadata before writing the small game cache with its private key. Library mutations use the current user's session and remain protected by RLS. There is no browser game-cache writer. Later additive migrations provide reviews, following, activity, avatars, and public game lists with the same explicit grants and RLS ownership model.
| Route | Behavior |
|---|---|
/ |
Existing live RAWG preview: five popular games, covers, release dates, IDs |
/search |
Server-side name search, bounded pagination, normalized cards |
/games/[game] |
Stable numeric RAWG ID plus readable slug; details and personal library editor |
/signup |
Email/password signup and unique username |
/login |
Email/password or Google sign-in; trusted return destination or username onboarding |
/verify-email |
Neutral verification resend with cooldown feedback |
/forgot-password |
Neutral recovery-email request |
/reset-password |
Verified-session password update and confirmation |
/reset-password/success |
Successful reset confirmation followed by sign-in |
/onboarding |
Authenticated first-time username selection |
/account |
Protected display-name/bio editor |
/library |
Protected collection with status/favorites filters, sorting, and management |
/players |
Public player discovery and search |
/feed |
Protected activity from followed players |
/lists |
Protected list management and public list viewing |
/profile/[username] |
Public profile, library statistics, and game collection; missing users return 404 |
/auth/callback |
PKCE exchange; canonical origin and restricted internal destinations |
/auth/confirm |
Supabase token-hash verification/recovery; clean redirects |
Signup creates a profile through the database trigger. With email confirmation enabled, confirm then sign in. Header sign-out clears the local session and revalidates navigation. Errors are generic and never print credentials.
Configure Supabase Site URL, callback allowlist, Google provider and SMTP using the exact setup checklist. Prepared token-hash email templates support verification/recovery across browsers. The existing PKCE path and root code fallback remain supported, with same-browser initiation required for PKCE. Hosted Auth provider/SMTP/template settings were not changed by this phase.
pnpm test
pnpm typecheck
pnpm lint
pnpm buildtests/validation.test.ts checks canonical usernames, password handling, input limits, and unwanted ownership fields.
tests/library.test.ts adds focused library validation, canonical route, upstream normalization, filter/sort, and statistics tests. pnpm test discovers and runs all regular and server-only TypeScript tests on Windows and Unix-like hosts.
scripts/verify-library.mjs is a separate explicit live test of existing authorized accounts. It requires the server-only secret key and TEST_EMAIL_A/TEST_EMAIL_B, uses temporary in-memory sign-in tokens without sending emails or changing passwords, exercises actual Server Actions/RLS, and cleans only its own game/library fixtures. See the phase report before running it.
supabase/tests/foundation_rls.sql creates transaction-local fixtures and rolls everything back. It tests profile creation, positive owner operations, cross-user profile/library write denial, anonymous write denial, cache write denial, uniqueness, immutable ownership/username, and rating/hours/bio constraints. Run only against the intended database with permission for rollback-only fixtures.
scripts/verify-auth.mjs is an explicit live test, not part of the automatic test command. It creates two real accounts and sends confirmation emails. Set TEST_EMAIL_A and TEST_EMAIL_B to fresh addresses you control in the process environment; optionally set the loopback TEST_APP_URL. Start the app, run node scripts/verify-auth.mjs, confirm BOTH emails, then enter verify. It keeps generated app passwords/tokens only in memory, exercises real forms/cookies and authenticated RLS, scans responses/assets/logs/source for private secrets, signs out, and exits. Enter stop to discard credentials. It never asks for inbox passwords. Accounts remain in Supabase; rerunning with the same addresses is not a cleanup/reset mechanism.
Implementation verification used two explicitly authorized accounts and manual confirmations. Signup, login/logout, independent-request session persistence, automatic profile creation, editing, protected/public access, actual authenticated cross-user profile restrictions, SQL library ownership checks, RAWG regression, and secret scans passed. Generated app passwords were discarded afterward. The two confirmed accounts/profiles remain; game/library SQL fixtures were rolled back. Automatic PKCE sign-in from an email link and a timed token-expiry refresh were not separately exercised end-to-end.
The security advisor reported two pre-existing execute-grant warnings on public.rls_auto_enable(), an event-trigger function. It was left unchanged; no new application-schema security warnings were reported. Details: anonymous execute warning, authenticated execute warning.
Added:
src/app/actions/auth.ts,src/app/actions/profile.tssrc/app/signup/page.tsx,src/app/login/page.tsx,src/app/account/page.tsx,src/app/library/page.tsx,src/app/profile/[username]/page.tsxsrc/app/auth/callback/route.ts,src/app/auth/confirm/route.ts,src/app/error.tsx,src/app/not-found.tsxsrc/components/forms/auth-form.tsx,src/components/forms/profile-form.tsx,src/components/layout/site-header.tsx,src/components/library/library-list.tsxsrc/lib/auth/session.ts,src/lib/data/profiles.ts,src/lib/data/library.ts,src/lib/validation/auth.tssrc/lib/supabase/config.ts,src/lib/supabase/server.ts,src/lib/supabase/proxy.ts,src/lib/supabase/transport.ts,src/proxy.tssrc/types/database.ts,src/types/forms.tssupabase/migrations/20260919230110_game_library_foundation.sql,supabase/tests/foundation_rls.sql,tests/validation.test.ts,scripts/verify-auth.mjs
Changed: src/lib/supabase/client.ts, src/app/layout.tsx, src/app/page.tsx, src/app/globals.css, .env.example, .gitignore, package.json, pnpm-lock.yaml, pnpm-workspace.yaml, and this README. .env.local and the existing RAWG helper/preview were not changed. Build-generated files are ignored. The repository currently has no Git remote; nothing has been pushed or deployed.