Files
NetForge/.planning/phases/01-foundation/01-05-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

223 lines
10 KiB
Markdown

---
phase: 01-foundation
plan: 05
type: execute
wave: 4
depends_on: ["01-04"]
files_modified:
- src/app/(super-admin)/layout.tsx
- src/app/(super-admin)/admin/page.tsx
- src/app/(super-admin)/admin/tenants/page.tsx
- src/app/api/admin/tenants/route.ts
- src/app/api/admin/tenants/[id]/route.ts
- src/app/api/admin/tenants/[id]/suspend/route.ts
- src/lib/middleware/super-admin.ts
- prisma/seed.ts
- src/lib/__tests__/super-admin.test.ts
- src/lib/__tests__/tenant-isolation.test.ts
autonomous: true
must_haves:
truths:
- "Super-admin can log in and view all tenants without being scoped to any single tenant"
- "Super-admin can see tenant status, creation date, and subscriber count for each tenant"
- "Super-admin can suspend a tenant with a grace period"
- "Super-admin cannot access tenant-scoped data (no impersonation)"
- "Non-super-admin users cannot access the admin panel"
- "Test harness validates core isolation and auth behaviors"
artifacts:
- path: "src/app/(super-admin)/admin/tenants/page.tsx"
provides: "Tenant management list UI"
min_lines: 40
- path: "src/app/api/admin/tenants/route.ts"
provides: "List all tenants API"
exports: ["GET"]
- path: "src/app/api/admin/tenants/[id]/suspend/route.ts"
provides: "Tenant suspension API"
exports: ["POST"]
- path: "src/lib/middleware/super-admin.ts"
provides: "Super-admin route guard"
exports: ["withSuperAdmin"]
- path: "src/lib/__tests__/super-admin.test.ts"
provides: "Super-admin access and tenant management tests"
min_lines: 40
key_links:
- from: "src/app/api/admin/tenants/route.ts"
to: "src/lib/middleware/super-admin.ts"
via: "withSuperAdmin guard on route"
pattern: "withSuperAdmin"
- from: "src/app/(super-admin)/admin/tenants/page.tsx"
to: "src/app/api/admin/tenants/route.ts"
via: "fetch tenant list"
pattern: "fetch.*api/admin/tenants"
- from: "src/lib/middleware/super-admin.ts"
to: "src/lib/auth.ts"
via: "Checks isSuperAdmin from session"
pattern: "isSuperAdmin"
---
<objective>
Implement the super-admin panel: a separate auth context for platform-level management, tenant listing with status/metrics, tenant suspension with grace period, and the consolidated test harness proving all Phase 1 behaviors work together.
Purpose: The platform owner needs to manage ISP tenants (view, suspend, monitor) without being scoped to any single tenant. This closes the super-admin requirement and validates the entire foundation with comprehensive tests.
Output: Working super-admin panel with tenant management, plus full test suite covering auth, isolation, and RBAC.
</objective>
<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>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/01-foundation/01-CONTEXT.md
@.planning/phases/01-foundation/01-02-SUMMARY.md
@.planning/phases/01-foundation/01-03-SUMMARY.md
@.planning/phases/01-foundation/01-04-SUMMARY.md
@src/lib/auth-options.ts
@src/lib/casl/ability.ts
@src/lib/prisma-tenant.ts
@prisma/schema.prisma
</context>
<tasks>
<task type="auto">
<name>Task 1: Super-admin API routes and middleware guard</name>
<files>
src/lib/middleware/super-admin.ts
src/app/api/admin/tenants/route.ts
src/app/api/admin/tenants/[id]/route.ts
src/app/api/admin/tenants/[id]/suspend/route.ts
prisma/seed.ts
</files>
<action>
Create `src/lib/middleware/super-admin.ts`:
- Export `withSuperAdmin(handler)` — wraps an API route handler
- Gets current user session via getCurrentUser()
- If no session: return 401
- If session.user.isSuperAdmin is not true: return 403 { error: "Super-admin access required" }
- If authorized: call handler with user context
Create `src/app/api/admin/tenants/route.ts`:
- GET handler wrapped with withSuperAdmin
- Queries ALL tenants (no tenant scoping — super-admin sees everything)
- Returns array of: { id, name, slug, status, ownerEmail, createdAt, userCount, subscriberCount (0 for now — no Subscriber model yet) }
- userCount: count of users per tenant via Prisma _count
- Sort by createdAt descending
Create `src/app/api/admin/tenants/[id]/route.ts`:
- GET handler wrapped with withSuperAdmin
- Returns single tenant detail: all fields plus users list (id, email, firstName, lastName, roles, isActive)
- Return 404 if tenant not found
Create `src/app/api/admin/tenants/[id]/suspend/route.ts`:
- POST handler wrapped with withSuperAdmin
- Accepts JSON body: { action: "suspend" | "activate" }
- For suspend:
- Set status to PENDING_SUSPENSION
- Set suspendedAt to now()
- Set gracePeriodEndsAt to 7 days from now
- Return { message: "Tenant suspension initiated. Grace period ends on {date}." }
- For activate:
- Set status to ACTIVE
- Clear suspendedAt and gracePeriodEndsAt
- Return { message: "Tenant activated." }
- Return 404 if tenant not found
Update `prisma/seed.ts`:
- Add a second demo tenant: "Test ISP 2" with a different admin user (admin2@demo.com) for testing cross-tenant isolation
- Keep existing demo tenant and super-admin user
</action>
<verify>
Run `npm run db:seed` — seed runs cleanly with both tenants. Start the app. Use curl or an API client to:
1. GET /api/admin/tenants without auth — expect 401
2. Log in as super-admin, GET /api/admin/tenants — expect 200 with both tenants
3. POST /api/admin/tenants/{id}/suspend with { action: "suspend" } — expect tenant status changes to PENDING_SUSPENSION
</verify>
<done>Super-admin API routes list all tenants and can suspend/activate them. withSuperAdmin middleware blocks non-super-admin access with 403. Grace period suspension works.</done>
</task>
<task type="auto">
<name>Task 2: Super-admin UI panel and comprehensive test harness</name>
<files>
src/app/(super-admin)/layout.tsx
src/app/(super-admin)/admin/page.tsx
src/app/(super-admin)/admin/tenants/page.tsx
src/middleware.ts
src/lib/__tests__/super-admin.test.ts
src/lib/__tests__/tenant-isolation.test.ts
</files>
<action>
Create `src/app/(super-admin)/layout.tsx`:
- Server component that checks session
- If user is not super-admin, redirect to /login or show 403 page
- Simple layout with sidebar navigation: "Dashboard", "Tenants"
- Header shows "NetForge Admin" and the super-admin's name
- Sign out button
Create `src/app/(super-admin)/admin/page.tsx`:
- Simple admin dashboard showing: total tenants, active tenants, suspended tenants
- Fetches stats from /api/admin/tenants
Create `src/app/(super-admin)/admin/tenants/page.tsx`:
- Table listing all tenants: Name, Status (badge with color: green=Active, yellow=Pending Suspension, red=Suspended), Owner Email, Users, Created Date
- Each row has actions: View details, Suspend/Activate toggle button
- Suspend button triggers confirmation dialog then calls the suspend API
- Activate button calls the suspend API with action: "activate"
- Clicking tenant name navigates to a detail view (or opens a modal showing users)
- Use Tailwind for styling — clean data table with hover states
Update `src/middleware.ts`:
- Add /admin/* routes to be protected
- These routes should check for super-admin status specifically (or rely on the layout redirect + API guards)
- Public routes remain: /login, /signup, /api/auth/*
Create `src/lib/__tests__/super-admin.test.ts`:
- Test: super-admin user can access admin routes (withSuperAdmin allows)
- Test: regular admin user cannot access admin routes (withSuperAdmin returns 403)
- Test: tenant suspension sets correct status and grace period dates
- Test: tenant activation clears suspension fields
Enhance `src/lib/__tests__/tenant-isolation.test.ts` (if needed):
- Ensure the existing cross-tenant isolation test still passes with the two-tenant seed data
- Add test: super-admin query for tenants returns ALL tenants (not scoped)
- Add test: regular user query does NOT return other tenants' data
Run full test suite: `npx vitest run` — all tests must pass.
</action>
<verify>
Run `npx vitest run` — all tests pass (auth, RBAC, tenant isolation, super-admin). Start the app, log in as superadmin@netforge.com, navigate to /admin/tenants — see both demo tenants listed. Suspend a tenant — status badge updates. Activate it — status reverts. Log in as admin@demo.com — navigating to /admin returns forbidden.
</verify>
<done>Super-admin can log in and manage all tenants via /admin panel (TENANT-03). Non-super-admin users are blocked from admin routes. Full test harness covers auth, RBAC, tenant isolation, and super-admin access. Phase 1 success criteria are met.</done>
</task>
</tasks>
<verification>
1. Super-admin logs in and sees tenant list at /admin/tenants
2. Tenant table shows status, owner, user count, creation date
3. Suspend action sets PENDING_SUSPENSION with 7-day grace period
4. Activate action restores ACTIVE status
5. Regular admin user gets 403 when accessing /admin routes
6. `npx vitest run` passes ALL tests across the entire phase:
- Auth config tests
- RBAC permission tests for all 5 roles
- Tenant isolation tests (zero cross-tenant leakage)
- Super-admin access tests
</verification>
<success_criteria>
- Super-admin can log in and view all tenants (TENANT-03)
- Super-admin sees tenant status, user count, creation date per tenant
- Tenant suspension with grace period works (not immediate lockout)
- Non-super-admin blocked from admin panel
- Full test suite validates Phase 1 foundation (INFRA-02 partial — unit tests for auth/RBAC/isolation)
- All Phase 1 requirements met: TENANT-01, TENANT-02, TENANT-03, AUTH-01, AUTH-02, AUTH-03, AUTH-04, INFRA-01, INFRA-02
</success_criteria>
<output>
After completion, create `.planning/phases/01-foundation/01-05-SUMMARY.md`
</output>