← 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
M public/marketplace/index.htmlM server.js
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 →