# Auth

**Status:** in production
**Lives at:** `/login`, `/change-password`, `/auth/signout` · `middleware.ts` · `lib/supabase/server.ts`, `lib/supabase/middleware.ts`

## Summary
Email + password login backed by Supabase Auth. Seeded users are forced to
set a new password on their first login.

## Why
2026-05-20 — "I want users to create a password upon first login. In the
meantime they get a temporary password to log in the first time."

## Behavior
- The app is auth-gated. Unauthenticated visitors to anything except
  `/login` get redirected to `/login`.
- Login takes email + password. On success, the user is redirected to `/`.
- If the user's profile has `must_change_password = true`, *every*
  navigation gets redirected to `/change-password` until they set a new
  password.
- `/change-password` requires the new password to be ≥ 8 characters and
  match a confirmation field. On success, the auth password is updated
  *and* `profiles.must_change_password` is flipped to `false`.
- Sign-out is a POST to `/auth/signout`. The server-side action calls
  `supabase.auth.signOut()` and redirects back to `/login`.
- Sessions refresh via Next.js middleware (`updateSession`). The middleware
  also sets/refreshes Supabase cookies on every navigation.

## Constraints & edge cases
- Auth cookies are HTTP-only, set by `@supabase/ssr` via the
  Next.js cookie API.
- The middleware is the *only* place that does the
  `must_change_password` redirect. It's important that `/change-password`
  itself is excluded from that check, otherwise it loops.
- `/login` is the only entry in `PUBLIC_PATHS`. Anything else requires a
  session. If an authenticated user hits `/login`, the middleware bounces
  them to `/`.
- Forms use Next.js Server Actions (`'use server'`) and redirect with
  `?error=…` for inline error messages. Server Actions throw on
  `redirect()`, so error handling is `redirect-on-fail, fall-through-on-success`.

## Permissions
- Anyone with a valid Supabase session can sign in.
- New users are created with `must_change_password = true` by the seed
  script. The flag flips to false the first time they set a real password.

## Open considerations
- No password reset flow yet (e.g. "I forgot my password"). For now Rian
  resets via the Supabase dashboard.
- No "remember me" toggle. Supabase default session length applies.
- No MFA / 2FA.
- No invite-by-email flow. Adding a new user means running the seed
  script (or manually creating in Supabase + inserting a profile row).
- We don't enforce password complexity beyond ≥ 8 chars. Could plug in
  zxcvbn or similar later.

## Tests
- 🟡 AUTH-001 First-login redirects to /change-password (integration)
- 🟡 AUTH-002 /change-password updates `must_change_password = false` (integration)

(No pure-logic tests for this feature — it's entirely Supabase + cookies.)

## Changelog
- **2026-05-20** — Initial: login, force-password-change, sign-out, seed
  of Rian + Adi.
- **2026-05-21** — Middleware-cookie loop fix (RLS regression in 0003
  prevented org lookup post-login; resolved in `20260521120001_fix_recursive_rls`).
