Harden Stripe payment integration: 15 security fixes from audit

Addresses 11 original findings (1 critical, 3 high, 4 medium, 3 low)
plus 4 additional findings from security review:

- Mask secrets in PUT /settings response (was leaking decrypted keys)
- Add paymentCheckoutRateLimit (10/hr/IP) to all 5 checkout endpoints
- Implement durable audit logging to payment_audit_log table
- Pin Stripe API version to 2026-01-28.clover (SDK v20.3.1)
- Add charge.dispute.created/closed webhook handlers with DISPUTED status
- Restore tickets on dispute won, handle charge_refunded closure
- Guard against sentinel passthrough corrupting stored Stripe keys
- Wrap refund DB updates in try/catch with webhook reconciliation fallback
- Add $transaction for product maxPurchases race condition
- Remove dead Payment model lookup from handleChargeRefunded
- Cap donation amount at $100k in both schemas
- Add requirePaymentsEnabled middleware on all checkout routes
- Remove Stripe internal IDs from CSV exports
- Add Cache-Control: no-store on admin settings responses

Bunker Admin
This commit is contained in:
2026-03-31 08:34:23 -06:00
parent 3de1d3fca5
commit 0c2ffe754e
47 changed files with 2457 additions and 187 deletions

View File

@@ -0,0 +1,18 @@
-- AlterEnum: Add DISPUTED status for chargeback tracking
ALTER TYPE "OrderStatus" ADD VALUE 'DISPUTED';
-- DropForeignKey: Make paymentId optional on audit log
ALTER TABLE "payment_audit_log" DROP CONSTRAINT "payment_audit_log_payment_id_fkey";
-- AlterTable: Add orderId column, make paymentId nullable
ALTER TABLE "payment_audit_log" ADD COLUMN "order_id" TEXT,
ALTER COLUMN "payment_id" DROP NOT NULL;
-- CreateIndex
CREATE INDEX "idx_payment_audit_log_order" ON "payment_audit_log"("order_id");
-- AddForeignKey (nullable)
ALTER TABLE "payment_audit_log" ADD CONSTRAINT "payment_audit_log_payment_id_fkey" FOREIGN KEY ("payment_id") REFERENCES "payments"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "payment_audit_log" ADD CONSTRAINT "payment_audit_log_order_id_fkey" FOREIGN KEY ("order_id") REFERENCES "orders"("id") ON DELETE SET NULL ON UPDATE CASCADE;