7.3 KiB
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:
- Security Hardening — no dependencies, foundational
- Input Validation & TypeScript — no dependencies on (1), can overlap
- Service Layer Refactor — must complete before testing
- Testing — depends on services existing (tests target service layer)
- Production Build Preparation — last, especially migrations and graceful shutdown
1. Security Hardening
1.1 Secrets Management
- Verify
.envis in.gitignoreand was never committed to git history - Create
.env.examplewith 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)
- Production:
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
zodas 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: trueis already enabled intsconfig.server.json - Eliminate remaining
as Record<string, unknown>casts — replaced by Zod-inferred types - Remove any
@ts-ignoreoranyusage - 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()fromorders.tsintonumbering.service.ts - Move offer MAX-based numbering into same service
- Used by orders, projects, offers, and their next-number endpoints
- The
number_sequencestable stays in the database — only the code location changes fromsrc/utils/sequence.tstosrc/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_URLvia.env.testpointing 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 pushin production
5.3 Environment Template
- Create
.env.exampledocumenting 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_secretwith 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/anddist-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