[object Object]

← back to Wallco Ai

security: admin-gate the entire /api/generator/* surface + /generator page

64c2db6a1b5afcba4f945861d089f2c81a956f7d · 2026-06-01 16:55:23 -0700 · Steve Abrams

The generator admin page and several of its API routes were anonymous-reachable on prod (recipes GET/POST/DELETE, palette, run-now, refresh-orders, and cron — which could launchctl start/stop the generator daemon, and refresh-orders which hit Shopify + wrote the DB). Added one app.use('/api/generator', requireAdmin) before the route block so no route can be left open, plus requireAdmin on the /generator page route. requireAdmin passes localhost (server-side ticks) + admin cookie/token (styleguides.html Generate button), 404s everyone else. Already-gated handlers keep their isAdmin checks as defense in depth.

Files touched

Diff

commit 64c2db6a1b5afcba4f945861d089f2c81a956f7d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 1 16:55:23 2026 -0700

    security: admin-gate the entire /api/generator/* surface + /generator page
    
    The generator admin page and several of its API routes were anonymous-reachable on prod (recipes GET/POST/DELETE, palette, run-now, refresh-orders, and cron — which could launchctl start/stop the generator daemon, and refresh-orders which hit Shopify + wrote the DB). Added one app.use('/api/generator', requireAdmin) before the route block so no route can be left open, plus requireAdmin on the /generator page route. requireAdmin passes localhost (server-side ticks) + admin cookie/token (styleguides.html Generate button), 404s everyone else. Already-gated handlers keep their isAdmin checks as defense in depth.
---
 server.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index 5295823..ebad3d5 100644
--- a/server.js
+++ b/server.js
@@ -21581,6 +21581,14 @@ app.get('/api/voice/preview', async (req, res) => {
 // ─────────────────────────────────────────────────────────────────────────
 // WALLCO GENERATOR (30-min cron + recipe picker)
 // ─────────────────────────────────────────────────────────────────────────
+// SECURITY (2026-06-01): gate the WHOLE /api/generator/* surface in one place so
+// no route is accidentally left open (recipes GET/POST/DELETE, palette, run-now,
+// refresh-orders, cron were all anonymous-reachable — cron could launchctl
+// start/stop the generator daemon; refresh-orders hit Shopify + wrote the DB).
+// requireAdmin passes localhost (server-side ticks) + admin-cookie/token, 404s
+// everyone else. Individual handlers keep their own isAdmin checks as defense in
+// depth. Registered before the route defs so it covers every one below.
+app.use('/api/generator', requireAdmin);
 app.get('/api/generator/recipes', (_req, res) => {
   try {
     const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.id), '[]'::json) FROM (
@@ -21942,7 +21950,7 @@ app.post('/api/generator/cron', (req, res) => {
 });
 
 // ── /generator admin page
-app.get('/generator', (_req, res) => {
+app.get('/generator', requireAdmin, (_req, res) => {
   res.type('html').send(`${htmlHead({
     title: 'Generator — wallco.ai admin',
     description: '30-min recipe-driven pattern generator',

← a1e2cc7 deploy: run PDP theme smoke test as step [5b/6] after health  ·  back to Wallco Ai  ·  Replicate fallback: sanctioned feather seam-heal + fix post- f9f8f89 →