← back to Domain Landings
app: admin inquiries viewer (Basic Auth, created date+time) + env-configurable George email auth
82ea4eb2cd0c5ce8988182164656eac786375029 · 2026-08-17 14:03:47 -0700 · Steve Abrams
Files touched
Diff
commit 82ea4eb2cd0c5ce8988182164656eac786375029
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 17 14:03:47 2026 -0700
app: admin inquiries viewer (Basic Auth, created date+time) + env-configurable George email auth
---
server.js | 42 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 41 insertions(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 7bbcdfa..6562943 100644
--- a/server.js
+++ b/server.js
@@ -9,6 +9,10 @@ const path = require('path');
const PORT = Number(process.env.PORT || readPort() || 9788);
const OFFER_TO = process.env.OFFER_TO || 'steve@designerwallcoverings.com';
const GEORGE = process.env.GEORGE_URL || 'http://127.0.0.1:9850/api/send';
+const GEORGE_TOKEN = process.env.GEORGE_TOKEN || ''; // set to enable email
+const GEORGE_AUTH_SCHEME = process.env.GEORGE_AUTH_SCHEME || 'Bearer'; // Bearer | Basic | raw
+const GEORGE_AUTH_HEADER = process.env.GEORGE_AUTH_HEADER || 'Authorization';
+const BASIC_AUTH = process.env.BASIC_AUTH || 'admin:DW2024!'; // admin viewer gate
const CFG = JSON.parse(fs.readFileSync(path.join(__dirname, 'data/domains.json'), 'utf8'));
const INQ = path.join(__dirname, 'data/inquiries.jsonl');
@@ -111,8 +115,12 @@ async function forwardGeorge(payload) {
try {
const data = JSON.stringify(payload);
const u = new URL(GEORGE);
+ const headers = { 'content-type': 'application/json', 'content-length': Buffer.byteLength(data) };
+ if (GEORGE_TOKEN) {
+ headers[GEORGE_AUTH_HEADER] = GEORGE_AUTH_SCHEME === 'raw' ? GEORGE_TOKEN : `${GEORGE_AUTH_SCHEME} ${GEORGE_TOKEN}`;
+ }
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: 'POST',
- headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(data) }, timeout: 8000 },
+ headers, timeout: 8000 },
r => { let b = ''; r.on('data', c => b += c); r.on('end', () => resolve({ ok: r.statusCode < 400, code: r.statusCode, body: b.slice(0, 200) })); });
req.on('error', e => resolve({ ok: false, err: String(e.message) }));
req.on('timeout', () => { req.destroy(); resolve({ ok: false, err: 'timeout' }); });
@@ -121,8 +129,40 @@ async function forwardGeorge(payload) {
});
}
+function adminInquiries(req, res) {
+ const hdr = req.headers.authorization || '';
+ const want = 'Basic ' + Buffer.from(BASIC_AUTH).toString('base64');
+ if (hdr !== want) {
+ res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="offers"' }).end('auth required');
+ return;
+ }
+ let rows = [];
+ try {
+ rows = fs.readFileSync(INQ, 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l)).reverse();
+ } catch {}
+ const fmt = ts => { try { return new Date(ts).toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch { return ts; } };
+ const cards = rows.map(r => `<div class="card">
+ <div class="dom">${esc(r.domain)}</div>
+ <div class="when" title="${esc(r.ts)}">🕓 ${esc(fmt(r.ts))}</div>
+ <div class="who"><b>${esc(r.name)}</b> · <a href="mailto:${esc(r.email)}">${esc(r.email)}</a></div>
+ <div class="msg">${esc(r.message) || '<i>(no message)</i>'}</div></div>`).join('');
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
+ res.end(`<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1">
+<title>Domain offers (${rows.length})</title>
+<style>body{font:15px/1.5 -apple-system,system-ui,sans-serif;background:#0f1216;color:#e6edf3;margin:0;padding:24px}
+h1{font-size:20px;margin:0 0 18px}.card{background:#161b22;border:1px solid #2a3038;border-radius:12px;padding:16px 18px;margin:0 0 12px;max-width:720px}
+.dom{font-weight:700;color:#58a6ff;font-size:16px}.when{color:#8b949e;font-size:12px;margin:2px 0 8px}
+.who{margin-bottom:6px}.who a{color:#58a6ff}.msg{color:#c9d1d9;white-space:pre-wrap}
+.empty{color:#8b949e}</style>
+<h1>💰 Domain offers — ${rows.length}</h1>
+${cards || '<p class="empty">No offers yet.</p>'}`);
+}
+
const server = http.createServer((req, res) => {
const host = hostOf(req);
+ if (req.method === 'GET' && req.url.split('?')[0] === '/admin/inquiries') {
+ return adminInquiries(req, res);
+ }
if (req.method === 'POST' && req.url.split('?')[0] === '/api/inquire') {
let body = '';
req.on('data', c => { body += c; if (body.length > 1e5) req.destroy(); });
← ee9f1e5 batch rollout tooling: dns_point + batch_nginx (batch 1: 18
·
back to Domain Landings
·
app: Basic-auth base64 for George + .env loader (secrets sta 8a2e5c4 →