--- phase: 01-foundation plan: 02 type: execute wave: 2 depends_on: ["01-01"] files_modified: - package.json - src/lib/auth.ts - src/lib/auth-options.ts - src/app/api/auth/[...nextauth]/route.ts - src/app/(auth)/login/page.tsx - src/app/(auth)/layout.tsx - src/middleware.ts - src/types/next-auth.d.ts - src/lib/__tests__/auth.test.ts autonomous: true must_haves: truths: - "A user can log in with email and password and receive a valid session" - "A logged-in user stays logged in after browser refresh" - "Logging out ends the session and redirects to login" - "The JWT contains tenantId and roles for downstream use" - "Unauthenticated users are redirected to the login page" artifacts: - path: "src/lib/auth-options.ts" provides: "NextAuth configuration with credentials provider" exports: ["authOptions"] - path: "src/app/api/auth/[...nextauth]/route.ts" provides: "NextAuth API route handler" exports: ["GET", "POST"] - path: "src/app/(auth)/login/page.tsx" provides: "Login form UI" min_lines: 40 - path: "src/middleware.ts" provides: "Route protection middleware" contains: "middleware" - path: "src/types/next-auth.d.ts" provides: "Extended session/JWT types with tenantId and roles" contains: "tenantId" key_links: - from: "src/lib/auth-options.ts" to: "prisma.user" via: "credential validation query" pattern: "prisma\\.user\\.findFirst" - from: "src/lib/auth-options.ts" to: "src/types/next-auth.d.ts" via: "JWT callback populates tenantId and roles" pattern: "token\\.tenantId" - from: "src/middleware.ts" to: "src/lib/auth-options.ts" via: "getToken checks authentication" pattern: "getToken|withAuth" --- Implement email/password authentication using NextAuth.js (Auth.js) with the credentials provider, JWT sessions carrying tenantId and roles, login/logout UI, and route protection middleware. Purpose: Every protected feature in the app depends on knowing WHO the user is and WHICH tenant they belong to. Auth is the gateway. Output: Working login/logout flow with JWT-based sessions that persist across refresh. @C:\Users\KevinAsprec\.claude/get-shit-done/workflows/execute-plan.md @C:\Users\KevinAsprec\.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation/01-CONTEXT.md @.planning/phases/01-foundation/01-01-SUMMARY.md @prisma/schema.prisma Task 1: NextAuth.js configuration with credentials provider and JWT package.json src/lib/auth-options.ts src/lib/auth.ts src/app/api/auth/[...nextauth]/route.ts src/types/next-auth.d.ts src/middleware.ts Install dependencies: `npm install next-auth@4 bcryptjs` and `npm install -D @types/bcryptjs`. Use NextAuth.js v4 (not v5 — v5/Auth.js is still beta-ish for credentials provider with custom JWT). This is a deliberate choice for stability. Create `src/types/next-auth.d.ts` to extend NextAuth types: - Session.user includes: id, email, tenantId (string | null), roles (Role[]), isSuperAdmin (boolean), firstName, lastName - JWT includes same fields Create `src/lib/auth-options.ts` exporting `authOptions: NextAuthOptions`: - CredentialsProvider with email and password fields - In authorize(): look up user by email using prisma.user.findFirst where email matches AND (tenantId matches an ACTIVE tenant OR user.isSuperAdmin is true). Use bcryptjs.compare for password verification. - If user found and password valid, return user object with id, email, tenantId, roles, isSuperAdmin, firstName, lastName - If not found or password invalid, return null (NextAuth shows error) - JWT callback: persist tenantId, roles, isSuperAdmin, firstName, lastName into token - Session callback: expose tenantId, roles, isSuperAdmin, firstName, lastName on session.user - Session strategy: "jwt" - JWT maxAge: 24 hours (session duration) - Pages: signIn: "/login" Create `src/lib/auth.ts` exporting a helper `getServerSession()` wrapper and a `getCurrentUser()` helper that returns the typed session user or null. Create `src/app/api/auth/[...nextauth]/route.ts` — standard NextAuth route handler exporting GET and POST. Create `src/middleware.ts`: - Use NextAuth's `withAuth` or `getToken` to check for valid JWT - Protect all routes EXCEPT: /login, /signup, /api/auth/*, /_next/*, /favicon.ico, static assets - If no valid token, redirect to /login - If valid token, allow request to continue Build check: `npx next build` completes without type errors. The auth route is accessible at /api/auth/providers (returns JSON with credentials provider listed). NextAuth configured with credentials provider. JWT contains tenantId and roles. Middleware redirects unauthenticated users to /login. TypeScript types are extended for session. Task 2: Login page UI, logout flow, and seed script for testing src/app/(auth)/login/page.tsx src/app/(auth)/layout.tsx prisma/seed.ts package.json src/lib/__tests__/auth.test.ts Create `src/app/(auth)/layout.tsx` — a centered layout for auth pages (no sidebar/nav, just centered card on neutral background). Use Tailwind. Create `src/app/(auth)/login/page.tsx`: - Client component ("use client") - Form with email and password fields, submit button - Uses `signIn("credentials", { email, password, redirect: false })` from next-auth/react - On success: redirect to /dashboard (or / for now) - On error: show error message below form (red text, "Invalid email or password") - Loading state on submit button - Clean, minimal design with Tailwind — no heavy UI library needed - Page title: "Sign in to NetForge" - Include a "Sign up" link pointing to /signup (page will be created in plan 01-03) Add logout functionality: - Create a simple header component at `src/components/layout/header.tsx` that shows the user's name and a "Sign out" button - Sign out button calls `signOut({ callbackUrl: "/login" })` from next-auth/react - Include this header in the main app layout (not the auth layout) - Create `src/app/(dashboard)/layout.tsx` as the authenticated layout that includes the header Create `prisma/seed.ts`: - Creates a test tenant: "Demo ISP", status ACTIVE - Creates an admin user: admin@demo.com / password "admin123" (bcrypt hashed), role ADMIN, linked to demo tenant - Creates a super-admin user: superadmin@netforge.com / password "super123" (bcrypt hashed), isSuperAdmin true, no tenantId - Uses upsert to be idempotent - Add "prisma.seed" config to package.json pointing to "ts-node prisma/seed.ts" (or use tsx: `npx tsx prisma/seed.ts`) - Add script: "db:seed": "npx tsx prisma/seed.ts" Create `src/lib/__tests__/auth.test.ts`: - Test that authOptions has credentials provider configured - Test that JWT callback adds tenantId and roles to token (mock the callback) - Test that session callback exposes tenantId and roles on session - These are unit tests of the auth config, NOT integration tests requiring a running server Run the seed script to populate test data. Run `npm run db:seed` — creates test users without errors. Run `npx vitest run` — auth tests pass. Start the app, navigate to /login, log in with admin@demo.com / admin123 — redirects to dashboard. Click sign out — redirects to /login. Refresh after login — session persists. Login page renders with email/password form. Seed data creates demo tenant + admin + super-admin. Login with valid credentials works and session persists across refresh. Logout redirects to login. Auth unit tests pass. 1. /login page renders a clean login form 2. Login with admin@demo.com / admin123 succeeds and redirects to authenticated area 3. Session persists after browser refresh (no re-login required) 4. Sign out button ends session and redirects to /login 5. Visiting a protected route while logged out redirects to /login 6. `npx vitest run` passes all auth tests - Email/password authentication works end-to-end (AUTH-01) - JWT contains tenantId and roles for downstream RBAC and tenant scoping - Session persists across browser refresh (AUTH-04) - Unauthenticated users cannot access protected routes - Seed data provides test accounts for development After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`