Fix security issues: path traversal, typed errors, file validation

- Fix path traversal in file serve endpoint (validate company prefix, block ..)
- Add typed error classes: ValidationError, NotFoundError, ForbiddenError,
  ConflictError, StorageError
- Global error handler catches AppError subclasses with correct status codes
- 4xx logged as warn, 5xx as error with request ID
- File upload validates entityType whitelist, UUID format, category pattern
- Remove fragile string-matching error handling from routes
- Services throw typed errors instead of plain Error
- Health endpoint documented as intentionally public
This commit is contained in:
Ryan Moon
2026-03-28 16:03:45 -05:00
parent 5aadd68128
commit e65175ef19
9 changed files with 150 additions and 86 deletions

View File

@@ -1,6 +1,7 @@
import { eq, and, count } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { products, inventoryUnits, priceHistory } from '../db/schema/inventory.js'
import { ValidationError } from '../lib/errors.js'
import type {
ProductCreateInput,
ProductUpdateInput,
@@ -119,11 +120,11 @@ export const InventoryUnitService = {
async create(db: PostgresJsDatabase, companyId: string, input: InventoryUnitCreateInput) {
if (input.condition) {
const valid = await ItemConditionService.validateSlug(db, companyId, input.condition)
if (!valid) throw new Error(`Invalid condition: "${input.condition}"`)
if (!valid) throw new ValidationError(`Invalid condition: "${input.condition}"`)
}
if (input.status) {
const valid = await UnitStatusService.validateSlug(db, companyId, input.status)
if (!valid) throw new Error(`Invalid status: "${input.status}"`)
if (!valid) throw new ValidationError(`Invalid status: "${input.status}"`)
}
const [unit] = await db
@@ -190,11 +191,11 @@ export const InventoryUnitService = {
) {
if (input.condition) {
const valid = await ItemConditionService.validateSlug(db, companyId, input.condition)
if (!valid) throw new Error(`Invalid condition: "${input.condition}"`)
if (!valid) throw new ValidationError(`Invalid condition: "${input.condition}"`)
}
if (input.status) {
const valid = await UnitStatusService.validateSlug(db, companyId, input.status)
if (!valid) throw new Error(`Invalid status: "${input.status}"`)
if (!valid) throw new ValidationError(`Invalid status: "${input.status}"`)
}
const updates: Record<string, unknown> = { ...input }