# 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
