← back to Dw Pitch Followup
pitch: enable Send-now over the public tunnel via ALLOW_TUNNEL_SEND flag (Steve-approved) + Send button now shows 'send cancelled' so a dismissed confirm never looks dead
56a35bd6abf48679f36f254540f672221b4df0f6 · 2026-07-30 14:38:52 -0700 · steve
Files touched
M public/index.htmlM server.js
Diff
commit 56a35bd6abf48679f36f254540f672221b4df0f6
Author: steve <steve@designerwallcoverings.com>
Date: Thu Jul 30 14:38:52 2026 -0700
pitch: enable Send-now over the public tunnel via ALLOW_TUNNEL_SEND flag (Steve-approved) + Send button now shows 'send cancelled' so a dismissed confirm never looks dead
---
public/index.html | 2 +-
server.js | 10 +++++++++-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index 5df8d61..46cd17c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -629,7 +629,7 @@ async function sendActive(btn){
const stat=document.getElementById('pSendStat');
if(!to){stat.style.color='var(--red)';stat.textContent=list==='list3'?'no vendor email on file':'no client email on file';return}
if(!subj||!body){stat.style.color='var(--amber)';stat.textContent='generate a letter first';return}
- if(!confirm(`Send this letter NOW?\n\nFROM: info@designerwallcoverings.com\nTO: ${to}\nSUBJ: ${subj}\n\nThis emails the recipient immediately.`))return;
+ if(!confirm(`Send this letter NOW?\n\nFROM: info@designerwallcoverings.com\nTO: ${to}\nSUBJ: ${subj}\n\nThis emails the recipient immediately.`)){stat.style.color='var(--amber)';stat.textContent='send cancelled';return}
const old=btn.textContent; btn.disabled=true; btn.textContent='sending…'; stat.style.color='var(--mut)'; stat.textContent='';
try{
const payloadBase={to,subject:subj,body,samples:Array.isArray(r.samples_sent)?r.samples_sent:[],list,account:r.account,company:(list==='list3'?r.vendor:r.company)};
diff --git a/server.js b/server.js
index 280cf25..d6a3c9e 100644
--- a/server.js
+++ b/server.js
@@ -97,6 +97,13 @@ function envLocal(key) {
const BASIC_AUTH = process.env.BASIC_AUTH || envLocal('BASIC_AUTH') || 'admin:DW2024!';
const AUTH_HEADER = 'Basic ' + Buffer.from(BASIC_AUTH).toString('base64');
const LOOPBACK = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
+// Allow /api/send over the PUBLIC cloudflared tunnel (Steve-approved 2026-07-30). Default OFF:
+// the tunnel-origin guard normally restricts send-as-info@ to a local-loopback operator because
+// Basic Auth alone is a weak gate for a send button on the open internet. Set ALLOW_TUNNEL_SEND=1
+// in .env to permit AUTHENTICATED remote sends — still behind Basic Auth + the brute-force lockout,
+// and every other send guard (suppression list, 30-day frequency, George external-send token,
+// recipient validation) is unchanged. Flip the .env flag back to '0' to re-lock instantly.
+const ALLOW_TUNNEL_SEND = (process.env.ALLOW_TUNNEL_SEND || envLocal('ALLOW_TUNNEL_SEND')) === '1';
// Brute-force lockout (contrarian FIX FIRST #2): the public tunnel exposes Basic Auth,
// which without a lockout is guessable at line speed. Track failed auths per client IP;
@@ -635,7 +642,8 @@ app.post('/api/send', async (req, res) => {
// arrive from cloudflared (non-loopback socket + cf-connecting-ip); loopback callers
// never carry those proxy headers. Header check kept as belt-and-suspenders.
const sendRemote = req.socket && req.socket.remoteAddress;
- if (!LOOPBACK.has(sendRemote) || req.get('cf-connecting-ip') || req.get('x-forwarded-for')) {
+ const isTunnelOrigin = !LOOPBACK.has(sendRemote) || req.get('cf-connecting-ip') || req.get('x-forwarded-for');
+ if (isTunnelOrigin && !ALLOW_TUNNEL_SEND) {
return res.status(403).json({ error: 'Sending is disabled over the public tunnel. Open this tool locally to send — drafting and preview still work remotely.', tunnelBlocked: true });
}
const { to, subject, body, samples, list, account: acctNo, company } = req.body || {};
← 6330e75 pitch: signpost 9768 (own listen port) vs 9850 (GEORGE clien
·
back to Dw Pitch Followup
·
pitch: package.json description now reflects reality — notes b50e31f →