fix(security+db): close the real audit findings (SEC-04/05/01, DB-01)
Deploy to VPS / deploy (push) Has been cancelled
Deploy to VPS / deploy (push) Has been cancelled
Acts on the verified findings from the 2026-06 audit (docs/AUDIT_2026-06_ VERIFIED.md). The audit's #1 "middleware never runs" was a false positive (verified in prod: /hq-command redirects to login). These are the genuine gaps: - SEC-04 (HIGH): /api/assets (GET/POST/PUT/DELETE/PATCH) and /api/branding/favicon (POST) had NO auth. The middleware matcher excludes /api, so they were world-reachable — anyone could list/upload/rename/ delete CMS files or regenerate the favicon. Added a new getAdminSession() helper (src/lib/session.ts) and a requireAdmin() guard on every handler. - DB-01 (HIGH): the ClientUser table (B2B client portal) was defined in the schema but NEVER created by any migration, and OperationsSignal.clientId + its FK were missing too. B2B register/login failed at runtime; the dashboard silently showed 0 clients. New additive migration 20260609120000_add_client_user creates the table, the unique email index, the clientId column (IF NOT EXISTS), and the FK (duplicate-object guarded). - SEC-05 (MED-HIGH): operations.ts generateRichEmailHtml() interpolated item.title/sku/quantity, clientName/Company/Email/Phone and the free-text message straight into HTML — stored XSS into the team's internal inbox. Now escaped via escapeHtml/escapeAttr/safeMailto; file links validated to internal paths only. - SEC-01 (MED): removed the hardcoded SESSION_SECRET fallback in src/proxy.ts; it now validates lazily and throws if the secret is missing (mirrors session.ts), so a runtime env failure can't fall back to a public key. Verified: next build compiles with SESSION_SECRET unset (Docker parity), TypeScript clean, prisma schema valid, golden tests 17/17. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
-- ADDITIVE MIGRATION — creates the ClientUser table (B2B client portal) and
|
||||
-- wires OperationsSignal.clientId to it. The Prisma schema has defined this
|
||||
-- model + relation for a while, but no migration ever created the table, so
|
||||
-- the B2B register/login flow (src/app/actions/clientAuth.ts) and the
|
||||
-- dashboard client counts were failing at runtime. This backfills it.
|
||||
--
|
||||
-- Idempotent: IF NOT EXISTS / duplicate-object guards make it safe to re-run
|
||||
-- and safe for `migrate deploy` against the existing production DB.
|
||||
-- ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "ClientUser" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"passwordHash" TEXT NOT NULL,
|
||||
"fullName" TEXT NOT NULL,
|
||||
"companyName" TEXT NOT NULL,
|
||||
"phone" TEXT,
|
||||
"isApproved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"lastLoginAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "ClientUser_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "ClientUser_email_key" ON "ClientUser" ("email");
|
||||
|
||||
-- OperationsSignal.clientId — the FK column the schema references. Add it if
|
||||
-- a prior schema state never created it (nullable, so existing rows are fine).
|
||||
ALTER TABLE "OperationsSignal" ADD COLUMN IF NOT EXISTS "clientId" TEXT;
|
||||
|
||||
-- Foreign key OperationsSignal.clientId -> ClientUser.id
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "OperationsSignal"
|
||||
ADD CONSTRAINT "OperationsSignal_clientId_fkey"
|
||||
FOREIGN KEY ("clientId") REFERENCES "ClientUser"("id")
|
||||
ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
Reference in New Issue
Block a user