← back to All Designerwallcoverings
astek CF-Access: env-gated service-token headers on crawler + feed proxy (TK-11, memo 260727A)
26c4bba393d10015fdaa09efe2b10a9fbffc83a0 · 2026-07-27 15:05:58 -0700 · Steve
Inert/byte-identical (Basic-Auth-only) until CF_ACCESS_ASTEK_ID/SECRET are set;
scoped to astek.designerwallcoverings.com only. Deploy to Kamatera stays gated.
Files touched
M scripts/crawl-microsites.jsM server.js
Diff
commit 26c4bba393d10015fdaa09efe2b10a9fbffc83a0
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jul 27 15:05:58 2026 -0700
astek CF-Access: env-gated service-token headers on crawler + feed proxy (TK-11, memo 260727A)
Inert/byte-identical (Basic-Auth-only) until CF_ACCESS_ASTEK_ID/SECRET are set;
scoped to astek.designerwallcoverings.com only. Deploy to Kamatera stays gated.
---
scripts/crawl-microsites.js | 18 ++++++++++++++++++
server.js | 14 +++++++++++++-
2 files changed, 31 insertions(+), 1 deletion(-)
diff --git a/scripts/crawl-microsites.js b/scripts/crawl-microsites.js
index 45cd86d..19ffac0 100644
--- a/scripts/crawl-microsites.js
+++ b/scripts/crawl-microsites.js
@@ -222,9 +222,27 @@ function isInternalHost(url) {
return h === '127.0.0.1' || h === 'localhost' || /(^|\.)designerwallcoverings\.com$/i.test(h);
} catch { return false; }
}
+// CF Access service-token headers for astek (Zero-Trust pilot — memo 260727A / TK-11).
+// Once astek moves behind Cloudflare Access and its nginx auth_basic is dropped, the
+// shared Basic-Auth cred no longer authenticates the crawler — the CF Access service
+// token does. Scoped to the astek host ONLY (a CF Access service token is per-application).
+// Inert + byte-identical (Basic-Auth-only, exactly as today) whenever CF_ACCESS_ASTEK_ID /
+// CF_ACCESS_ASTEK_SECRET are unset. During the transition window (Access enforced but the
+// origin nginx auth_basic still present) BOTH headers ride — satisfying the CF edge AND
+// the origin gate — and once auth_basic is dropped the leftover Basic header is harmless.
+const CF_ACCESS_ASTEK_ID = process.env.CF_ACCESS_ASTEK_ID || '';
+const CF_ACCESS_ASTEK_SECRET = process.env.CF_ACCESS_ASTEK_SECRET || '';
+function isAstekHost(url) {
+ try { return new URL(url).hostname.toLowerCase() === 'astek.designerwallcoverings.com'; }
+ catch { return false; }
+}
async function getText(url, ms) {
const headers = { 'user-agent': 'all-dw-crawler/1.0 (+all.designerwallcoverings.com)' };
if (MICROSITE_AUTH && isInternalHost(url)) headers.Authorization = MICROSITE_AUTH;
+ if (CF_ACCESS_ASTEK_ID && CF_ACCESS_ASTEK_SECRET && isAstekHost(url)) {
+ headers['CF-Access-Client-Id'] = CF_ACCESS_ASTEK_ID;
+ headers['CF-Access-Client-Secret'] = CF_ACCESS_ASTEK_SECRET;
+ }
const r = await fetch(url, { redirect: 'follow', signal: AbortSignal.timeout(ms), headers });
return { status: r.status, ok: r.ok, finalUrl: r.url, body: await r.text() };
}
diff --git a/server.js b/server.js
index d1e310d..2f649c0 100644
--- a/server.js
+++ b/server.js
@@ -541,6 +541,13 @@ const MICROSITE_PER_SITE_CAP = 800; // full catalog per site is ≤735 — wel
// are readable. Same override the crawler uses; defaults to the standard admin cred.
const MICROSITE_FEED_AUTH = process.env.MICROSITE_BASIC_AUTH || 'admin:DW2024!';
const MICROSITE_FEED_HDR = 'Basic ' + Buffer.from(MICROSITE_FEED_AUTH).toString('base64');
+// CF Access service-token headers for astek (Zero-Trust pilot — memo 260727A / TK-11).
+// When astek moves behind Cloudflare Access, the on-demand feed proxy needs the service
+// token too. Scoped to the astek host ONLY (per-application token). Inert + byte-identical
+// (Basic-Auth-only, as today) whenever CF_ACCESS_ASTEK_ID / CF_ACCESS_ASTEK_SECRET are unset.
+const CF_ACCESS_ASTEK_ID = process.env.CF_ACCESS_ASTEK_ID || '';
+const CF_ACCESS_ASTEK_SECRET = process.env.CF_ACCESS_ASTEK_SECRET || '';
+const isAstekHost = (u) => { try { return new URL(u).hostname.toLowerCase() === 'astek.designerwallcoverings.com'; } catch { return false; } };
const MICROSITE_FEED_TIMEOUT_MS = 12000;
// Map a heterogeneous microsite feed row (each of the 4 feeds has a slightly different
@@ -628,9 +635,14 @@ function deriveMicrositeRow(p, feed) {
}
async function fetchMicrositeFeed(feed) {
+ const headers = { 'user-agent': 'all-dw/1.0', Authorization: MICROSITE_FEED_HDR };
+ if (CF_ACCESS_ASTEK_ID && CF_ACCESS_ASTEK_SECRET && isAstekHost(feed.url)) {
+ headers['CF-Access-Client-Id'] = CF_ACCESS_ASTEK_ID;
+ headers['CF-Access-Client-Secret'] = CF_ACCESS_ASTEK_SECRET;
+ }
const r = await fetch(feed.url, {
redirect: 'follow', signal: AbortSignal.timeout(MICROSITE_FEED_TIMEOUT_MS),
- headers: { 'user-agent': 'all-dw/1.0', Authorization: MICROSITE_FEED_HDR },
+ headers,
});
if (r.status !== 200) throw new Error(`HTTP ${r.status}`);
const j = JSON.parse(await r.text());
← ae33f6d auto-save: 2026-07-27T14:51:27 (1 files) — server.js
·
back to All Designerwallcoverings
·
chore: v1.7.0 — async job+poll live-check feature (session c 699a5fd →