Files
NetForge/.planning/phases/01-foundation/01-02-PLAN.md
kevin-asprec 7e6d286fca docs(01): create phase plan
Phase 01: Foundation
- 5 plan(s) in 4 wave(s)
- 2 parallel (wave 2: auth + tenant provisioning), 3 sequential
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 18:13:27 +08:00

8.8 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
01-foundation 02 execute 2
01-01
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
true
truths artifacts key_links
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
path provides exports
src/lib/auth-options.ts NextAuth configuration with credentials provider
authOptions
path provides exports
src/app/api/auth/[...nextauth]/route.ts NextAuth API route handler
GET
POST
path provides min_lines
src/app/(auth)/login/page.tsx Login form UI 40
path provides contains
src/middleware.ts Route protection middleware middleware
path provides contains
src/types/next-auth.d.ts Extended session/JWT types with tenantId and roles tenantId
from to via pattern
src/lib/auth-options.ts prisma.user credential validation query prisma.user.findFirst
from to via pattern
src/lib/auth-options.ts src/types/next-auth.d.ts JWT callback populates tenantId and roles token.tenantId
from to via pattern
src/middleware.ts src/lib/auth-options.ts getToken checks authentication 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.

<execution_context> @C:\Users\KevinAsprec.claude/get-shit-done/workflows/execute-plan.md @C:\Users\KevinAsprec.claude/get-shit-done/templates/summary.md </execution_context>

@.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

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/01-foundation/01-02-SUMMARY.md`