197 lines
7.3 KiB
Markdown
197 lines
7.3 KiB
Markdown
# Production Readiness Audit — boha-app-ts
|
|
|
|
**Date:** 2026-03-23
|
|
**Status:** Approved
|
|
**Scope:** Code hardening, security, validation, architecture refactor, testing, build preparation
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
boha-app-ts is a TypeScript/Node.js migration of the PHP boha-app. Stack: Fastify backend, React frontend, Prisma ORM (MySQL), JWT + TOTP auth. The app will be deployed on an Ubuntu server alongside the existing PHP app, reusing the same MySQL database, nginx, and SSL setup.
|
|
|
|
This spec covers making the codebase production-ready. Deployment configuration (nginx, PM2, Ubuntu) is out of scope — this focuses purely on code quality, security, and build preparation.
|
|
|
|
## Implementation Order
|
|
|
|
Sections must be executed in this order due to dependencies:
|
|
|
|
1. **Security Hardening** — no dependencies, foundational
|
|
2. **Input Validation & TypeScript** — no dependencies on (1), can overlap
|
|
3. **Service Layer Refactor** — must complete before testing
|
|
4. **Testing** — depends on services existing (tests target service layer)
|
|
5. **Production Build Preparation** — last, especially migrations and graceful shutdown
|
|
|
|
---
|
|
|
|
## 1. Security Hardening
|
|
|
|
### 1.1 Secrets Management
|
|
- Verify `.env` is in `.gitignore` and was never committed to git history
|
|
- Create `.env.example` with placeholder values (no real secrets)
|
|
- Document that production requires new JWT_SECRET and TOTP_ENCRYPTION_KEY
|
|
- TOTP re-encryption script — see section 5.4 for implementation details
|
|
|
|
### 1.2 Rate Limiting
|
|
- Global: 100 requests/min per IP (unchanged — sufficient for internal admin app)
|
|
- Login endpoint (`POST /api/admin/login`): 20 requests/min per IP
|
|
- All other endpoints: inherit global limit
|
|
- Account lockout (5 failed attempts = 15 min lock) remains the primary brute-force defense
|
|
- Rate limiting protects infrastructure, lockout protects accounts
|
|
|
|
### 1.3 Security Headers
|
|
Add to existing security middleware:
|
|
- `Content-Security-Policy` — production only (dev mode needs relaxed policy for Vite HMR/eval)
|
|
- Production: `default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'`
|
|
- Dev: no CSP (Vite injects scripts dynamically)
|
|
- `Permissions-Policy: camera=(), microphone=(), geolocation=()`
|
|
- Existing headers unchanged: X-Content-Type-Options, X-Frame-Options, Referrer-Policy, HSTS (prod)
|
|
|
|
### 1.4 Request Body Limits
|
|
- Global JSON body limit: 1MB via Fastify `bodyLimit`
|
|
- Auth endpoints (login, refresh, TOTP): 10KB
|
|
- Multipart (file uploads): 10MB (unchanged)
|
|
|
|
### 1.5 Timing-Safe Auth
|
|
- When user not found during login, still run `bcrypt.compare()` against a dummy hash
|
|
- Prevents timing-based username enumeration
|
|
|
|
---
|
|
|
|
## 2. Input Validation & TypeScript Strictness
|
|
|
|
### 2.1 Zod Validation
|
|
- Install `zod` as dependency
|
|
- Create validation schemas for all request bodies in `src/schemas/`
|
|
- Schemas organized by domain: `auth.schema.ts`, `users.schema.ts`, `offers.schema.ts`, etc.
|
|
- Replace all `request.body as Record<string, unknown>` with Zod parsing
|
|
- Validation errors return 400 with field-level messages in Czech
|
|
|
|
### 2.2 TypeScript Strictness
|
|
- Verify `strict: true` is already enabled in `tsconfig.server.json`
|
|
- Eliminate remaining `as Record<string, unknown>` casts — replaced by Zod-inferred types
|
|
- Remove any `@ts-ignore` or `any` usage
|
|
- This step is effectively part of the Zod migration (2.1)
|
|
|
|
### 2.3 Error Response Consistency
|
|
- All user-facing error messages in Czech
|
|
- Every route uses `error()` / `success()` helpers
|
|
- Proper HTTP status codes on all responses
|
|
|
|
---
|
|
|
|
## 3. Service Layer & Code Architecture
|
|
|
|
### 3.1 Service Extraction
|
|
Move business logic from route handlers into `src/services/`:
|
|
|
|
| Service | Responsibility |
|
|
|---------|---------------|
|
|
| `offers.service.ts` | CRUD, numbering, duplication |
|
|
| `orders.service.ts` | CRUD, numbering, project creation |
|
|
| `invoices.service.ts` | CRUD, stats, PDF data preparation |
|
|
| `projects.service.ts` | CRUD, notes, numbering |
|
|
| `users.service.ts` | CRUD, role assignment, password reset |
|
|
| `attendance.service.ts` | Clock in/out, shift management |
|
|
| `numbering.service.ts` | Shared number generation (orders + projects + offers) |
|
|
|
|
Existing services (`auth.ts`, `audit.ts`) remain unchanged — already well structured.
|
|
|
|
### 3.2 Route Handler Pattern
|
|
After refactor, routes follow this pattern:
|
|
```
|
|
parse & validate input (Zod) → call service → return response
|
|
```
|
|
No business logic in route files.
|
|
|
|
### 3.3 Number Generation Consolidation
|
|
- Move `generateSharedNumber()` from `orders.ts` into `numbering.service.ts`
|
|
- Move offer MAX-based numbering into same service
|
|
- Used by orders, projects, offers, and their next-number endpoints
|
|
- The `number_sequences` table stays in the database — only the code location changes from `src/utils/sequence.ts` to `src/services/numbering.service.ts`
|
|
|
|
---
|
|
|
|
## 4. Testing
|
|
|
|
### 4.1 Stack
|
|
- **Vitest** — test runner (compatible with existing Vite setup)
|
|
- **Supertest** — HTTP integration testing
|
|
- Real test database (no mocks)
|
|
|
|
### 4.2 Test Database Setup
|
|
- Separate `DATABASE_URL` via `.env.test` pointing to a dedicated test database
|
|
- Tests use transaction rollback for cleanup (each test runs in a transaction that rolls back)
|
|
- Seed script for baseline test data (admin user, roles, permissions)
|
|
|
|
### 4.3 Test Coverage
|
|
|
|
| Area | Tests |
|
|
|------|-------|
|
|
| Auth flow | Login, TOTP verify, backup codes, token refresh, rotation, logout, lockout |
|
|
| Permissions | Admin bypass, role-based access, forbidden responses |
|
|
| Number generation | Offer, order, project shared sequence correctness |
|
|
| CRUD | Create/read/update/delete for offers, orders, invoices |
|
|
| Edge cases | Expired tokens, invalid TOTP, duplicate usernames, password validation |
|
|
|
|
### 4.4 Not Testing
|
|
- Frontend React components
|
|
- Prisma query internals
|
|
- Simple list/get endpoints with no business logic
|
|
|
|
### 4.5 Structure
|
|
```
|
|
src/__tests__/
|
|
auth.test.ts
|
|
permissions.test.ts
|
|
offers.test.ts
|
|
orders.test.ts
|
|
invoices.test.ts
|
|
numbering.test.ts
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Production Build Preparation
|
|
|
|
### 5.1 Graceful Shutdown
|
|
- Handle SIGTERM/SIGINT in server.ts
|
|
- Close Fastify server (drain in-flight requests)
|
|
- Disconnect Prisma client
|
|
- Log shutdown events
|
|
|
|
### 5.2 Prisma Migration Strategy
|
|
- Create baseline migration from existing schema using `prisma migrate diff`
|
|
- All future schema changes go through versioned migration files
|
|
- Never use `db push` in production
|
|
|
|
### 5.3 Environment Template
|
|
- Create `.env.example` documenting all required variables with placeholder values
|
|
- Mark which values must be regenerated for production (secrets)
|
|
- Mark which values are deployment-specific (HOST, PORT, CORS_ORIGINS, NAS_PATH)
|
|
|
|
### 5.4 TOTP Re-encryption Script
|
|
- Standalone script: `scripts/rotate-totp-key.ts`
|
|
- Reads old key and new key from arguments
|
|
- Decrypts all `users.totp_secret` with old key, re-encrypts with new key
|
|
- Transaction-safe (all or nothing)
|
|
- Dry-run mode for verification
|
|
|
|
### 5.5 Static File Serving
|
|
- Production serves `dist-client/` via `@fastify/static` (already a dependency)
|
|
- Dev mode uses Vite middleware (already implemented)
|
|
|
|
### 5.6 Cleanup
|
|
- Verify no unused dependencies in package.json
|
|
- Ensure `dist/` and `dist-client/` are in `.gitignore`
|
|
|
|
---
|
|
|
|
## Out of Scope
|
|
|
|
- Nginx configuration
|
|
- PM2 / process management setup
|
|
- Ubuntu server provisioning
|
|
- Frontend component refactoring
|
|
- Database data migration from PHP app
|