[object Object]

← back to Wallco Ai

security: remove public /spoonflower nav tab + admin-gate the page and all /api/spoonflower/* endpoints

26dbc6200d89216feef68b810d4bf65b54b65564 · 2026-05-13 21:11:23 -0700 · SteveStudio2

Public /spoonflower was leaking the DW Spoonflower library (titles, IDs, design URLs, job logs). Now:
- Spoonflower removed from the top-nav (was visible to every public visitor)
- /spoonflower page wrapped in requireAdmin (returns 404 to non-admins)
- /api/spoonflower/{upload,launch-chrome,chrome-status,pull-all,status/:job} all gated
- 'Spoonflower-style storefronts' marketing copy on /marketplace renamed to 'Designer storefronts'

Admin still has full access via cookie/token/localhost.

Files touched

Diff

commit 26dbc6200d89216feef68b810d4bf65b54b65564
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 21:11:23 2026 -0700

    security: remove public /spoonflower nav tab + admin-gate the page and all /api/spoonflower/* endpoints
    
    Public /spoonflower was leaking the DW Spoonflower library (titles, IDs, design URLs, job logs). Now:
    - Spoonflower removed from the top-nav (was visible to every public visitor)
    - /spoonflower page wrapped in requireAdmin (returns 404 to non-admins)
    - /api/spoonflower/{upload,launch-chrome,chrome-status,pull-all,status/:job} all gated
    - 'Spoonflower-style storefronts' marketing copy on /marketplace renamed to 'Designer storefronts'
    
    Admin still has full access via cookie/token/localhost.
---
 public/marketplace/index.html |  2 +-
 server.js                     | 17 +++++++++--------
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/public/marketplace/index.html b/public/marketplace/index.html
index 27ba201..fb9c141 100644
--- a/public/marketplace/index.html
+++ b/public/marketplace/index.html
@@ -54,7 +54,7 @@
       <div class="mp-grid lg">
         <div class="mp-card"><div class="body"><strong>Patternbank-style licensing</strong><div class="sub">Designers earn from non-exclusive and exclusive licensing deals. Every inquiry tracked, every contract attributable.</div></div></div>
         <div class="mp-card"><div class="body"><strong>Material Bank-style sampling</strong><div class="sub">Trade buyers order samples, save to project boards, get client approval — Wallco fulfills.</div></div></div>
-        <div class="mp-card"><div class="body"><strong>Spoonflower-style storefronts</strong><div class="sub">Every approved designer gets a personal storefront, collections, and follow-button discovery.</div></div></div>
+        <div class="mp-card"><div class="body"><strong>Designer storefronts</strong><div class="sub">Every approved designer gets a personal storefront, collections, and follow-button discovery.</div></div></div>
         <div class="mp-card"><div class="body"><strong>Wallco × AI</strong><div class="sub">Auto-tag, auto-colorway, auto-mockup. One pattern becomes a sellable family of SKUs.</div></div></div>
       </div>
     </section>
diff --git a/server.js b/server.js
index 198c3cb..91fafbe 100644
--- a/server.js
+++ b/server.js
@@ -2328,7 +2328,6 @@ function htmlHeader(active) {
     ['/studio', 'Studio'],
     ['/play',   'Play'],
     ['/generator', 'Generator'],
-    ['/spoonflower', 'Spoonflower'],
     ['/marketplace', 'Marketplace'],
     ['/designers', 'Designers'],
     ['/patterns', 'Patterns'],
@@ -10740,7 +10739,7 @@ app.post('/api/rooms/:id/marketing', (req, res) => {
 });
 
 // ── POST /api/spoonflower/upload — upload ONE design as new product
-app.post('/api/spoonflower/upload', (req, res) => {
+app.post('/api/spoonflower/upload', requireAdmin, (req, res) => {
   const { design_id, dry = true, tags = '' } = req.body || {};
   const d = findDesign(design_id);
   if (!d) return res.status(404).json({ ok: false, error: 'design not found' });
@@ -10950,7 +10949,7 @@ app.post('/api/design/:id/upload-shopify', requireAdmin, express.json({ limit: '
 });
 
 // ── POST /api/spoonflower/launch-chrome — open dedicated debug Chrome for cookie reuse
-app.post('/api/spoonflower/launch-chrome', (req, res) => {
+app.post('/api/spoonflower/launch-chrome', requireAdmin, (req, res) => {
   try {
     const out = execSync(`zsh ${path.join(__dirname, 'scripts', 'launch_spoon_chrome.sh')} 2>&1`,
                          { encoding: 'utf8', timeout: 10000 });
@@ -10961,7 +10960,7 @@ app.post('/api/spoonflower/launch-chrome', (req, res) => {
 });
 
 // ── GET /api/spoonflower/chrome-status — is debug Chrome reachable?
-app.get('/api/spoonflower/chrome-status', async (_req, res) => {
+app.get('/api/spoonflower/chrome-status', requireAdmin, async (_req, res) => {
   try {
     const r = await fetch('http://127.0.0.1:9222/json/version', { signal: AbortSignal.timeout(1500) });
     if (r.ok) {
@@ -10975,7 +10974,7 @@ app.get('/api/spoonflower/chrome-status', async (_req, res) => {
 });
 
 // ── POST /api/spoonflower/pull-all — bulk pull DW account designs
-app.post('/api/spoonflower/pull-all', (req, res) => {
+app.post('/api/spoonflower/pull-all', requireAdmin, (req, res) => {
   const { limit = 0, account = 'dw' } = req.body || {};
 
   let jobId;
@@ -11015,7 +11014,7 @@ app.post('/api/spoonflower/pull-all', (req, res) => {
 });
 
 // ── GET /api/spoonflower/status/:job — return job + log tail
-app.get('/api/spoonflower/status/:job', (req, res) => {
+app.get('/api/spoonflower/status/:job', requireAdmin, (req, res) => {
   const jobId = parseInt(req.params.job, 10);
   let row;
   try {
@@ -11052,8 +11051,10 @@ app.get('/api/spoonflower/status/:job', (req, res) => {
   }, null, 2));
 });
 
-// ── GET /spoonflower — admin page: pulled DW library + per-design publish buttons
-app.get('/spoonflower', (req, res) => {
+// ── GET /spoonflower — admin page: pulled DW library + per-design publish buttons.
+// HARD-GATED 2026-05-13 — page was leaking the DW Spoonflower library publicly.
+// requireAdmin returns 404 to non-admins; admin can still reach via direct URL.
+app.get('/spoonflower', requireAdmin, (req, res) => {
   let pulled = [];
   try {
     const raw = psqlQuery(`SELECT COALESCE(json_agg(t ORDER BY t.pulled_at DESC), '[]'::json) FROM (

← d14bd81 feat: 18 geometric-block-leaves designs + 2 fill-in runners  ·  back to Wallco Ai  ·  marketplace: accept qualified AI agents (no bots) — add /mar b22d13a →