Files
kevin-asprec ab1e94480e docs(05-02): complete Subscriber Portal Auth and API plan
Tasks completed: 2/2
- Portal authentication via subscriber account number
- PortalService, API routes, and integration tests (5 tests)

SUMMARY: .planning/phases/05-visibility-and-client-portal/05-02-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 17:28:03 +08:00

110 lines
4.9 KiB
Markdown

---
phase: 05-visibility-and-client-portal
plan: 02
subsystem: portal
tags: [portal, auth, subscriber, nextauth, credentials, invoices, payments, account]
# Dependency graph
requires:
- phase: 01-foundation
provides: NextAuth v4 with JWT strategy, withTenantContext, middleware auth exclusions
- phase: 02-subscriber-and-billing-core
provides: Subscriber, Invoice, Payment, ServicePlan models
provides:
- Portal credentials provider (accountNumber + password login)
- PortalService with subscriber-scoped data retrieval
- Portal API routes (account, invoices, payments)
- withPortalAuth middleware for portal endpoint authorization
affects: [05-03 (portal UI consumes these APIs), 05-05 (e2e tests)]
# Tech tracking
tech-stack:
added: []
patterns:
- "Dual NextAuth credentials provider (staff + portal) on same instance"
- "subscriberId in JWT/session for portal user identification"
- "withPortalAuth HOF for portal-only endpoint authorization"
key-files:
created:
- src/lib/services/portal-service.ts
- src/lib/middleware/portal-auth.ts
- src/app/api/portal/account/route.ts
- src/app/api/portal/invoices/route.ts
- src/app/api/portal/payments/route.ts
- src/lib/__tests__/portal-service.test.ts
modified:
- prisma/schema.prisma
- src/lib/auth-options.ts
- src/types/next-auth.d.ts
- src/middleware.ts
# Decisions
decisions:
- id: "portal-dual-provider"
description: "Two CredentialsProviders on same NextAuth instance (id: credentials, id: portal-credentials) -- additive, no change to staff auth"
- id: "portal-password-nullable"
description: "Subscriber.passwordHash is nullable -- only subscribers with a set password can log in to portal"
- id: "portal-subscriberId-jwt"
description: "subscriberId persisted in JWT token and session -- distinguishes portal users from staff users without DB lookup"
# Metrics
metrics:
duration: "4 min"
completed: "2026-03-05"
tasks: 2
tests: 5
---
# Phase 05 Plan 02: Subscriber Portal Authentication and API Summary
**Portal auth via account number + password with subscriber-scoped read-only API endpoints, 5 tests passing**
## What Was Done
### Task 1: Portal Authentication
- Added `passwordHash` (nullable String) to Subscriber model in Prisma schema
- Added second CredentialsProvider (`portal-credentials`) to NextAuth config accepting accountNumber + password + tenantId
- Provider looks up subscriber by @@unique([tenantId, accountNumber]) with non-null passwordHash
- JWT callback persists `subscriberId` into token; session callback exposes it on session.user
- Extended next-auth type definitions (Session, User, JWT) with optional `subscriberId`
- Updated middleware matcher to exclude `/portal/login` and `/api/portal/auth` from auth requirement
### Task 2: PortalService and API Routes
- Created `PortalService` with three subscriber-scoped methods:
- `getPortalAccount(db, subscriberId)` -- returns profile, plan details, credit balance, billing day
- `getPortalInvoices(db, subscriberId, options)` -- paginated invoices with line items, ordered by periodStart DESC
- `getPortalPayments(db, subscriberId, options)` -- paginated payments, ordered by createdAt DESC
- Created `withPortalAuth` middleware HOF that validates subscriberId in session (401 if no session, 403 if not portal user)
- Created three portal API routes:
- `GET /api/portal/account` -- subscriber profile + plan
- `GET /api/portal/invoices` -- paginated invoices (query: page, limit)
- `GET /api/portal/payments` -- paginated payments (query: page, limit)
### Integration Tests (5/5 passing)
1. `getPortalAccount returns subscriber with plan details` -- verifies all fields including plan name, speed, price, billingType
2. `getPortalInvoices returns paginated invoices` -- creates 3 invoices, verifies page 1 limit 2 returns 2 with total 3
3. `getPortalPayments returns paginated payment history` -- creates 2 payments, verifies list and subscriber scoping
4. `getPortalAccount scoped to subscriberId only` -- creates 2 subscribers, verifies each sees only own data
5. `getPortalInvoices includes invoice line items` -- verifies lines array populated with description and lineTotal
## Deviations from Plan
None -- plan executed exactly as written.
## Commits
| Hash | Message |
|------|---------|
| 539564d | feat(05-02): portal authentication via subscriber account number |
| 7222dbe | feat(05-02): portal service, API routes, and integration tests |
## Requirements Satisfied
- **PORT-01**: Subscriber can log in with account number and password
- **PORT-02**: Subscriber can view current bill and outstanding balance (via invoices endpoint + account creditBalance)
- **PORT-04**: Subscriber can view full payment history (via payments endpoint)
- Account overview includes plan details, balance, and billing day
- Invoice and payment history are paginated
- Portal API endpoints scoped to logged-in subscriber only