[object Object]

← back to Commercialrealestate

Fix mls.html empty grid: relative fetch() threw on credentialed baseURI

9ac1e1127a0afac80fa56fced4845cb4f098e020 · 2026-08-18 12:01:48 -0700 · Steve

Opening http://admin:PASS@localhost:9911/mls.html left the embedded creds in
document.baseURI, so every relative fetch('/data/ranked.json') threw
'Request cannot be constructed from a URL that includes credentials' before any
request was sent. The over-broad .catch mislabeled it 'failed to load'.

- public/mls.html: wrap window.fetch to resolve against location.origin (never
  carries userinfo) + strip creds from absolute URLs; tidy the address bar.
- scripts/serve.js: set an httpOnly session cookie on Basic-auth pass so the now
  same-origin fetches authenticate via cookie (not the un-primed auth cache).

Verified real-browser (creds-in-URL AND dialog auth): 25,580 rows, 0 errors.

Files touched

Diff

commit 9ac1e1127a0afac80fa56fced4845cb4f098e020
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Aug 18 12:01:48 2026 -0700

    Fix mls.html empty grid: relative fetch() threw on credentialed baseURI
    
    Opening http://admin:PASS@localhost:9911/mls.html left the embedded creds in
    document.baseURI, so every relative fetch('/data/ranked.json') threw
    'Request cannot be constructed from a URL that includes credentials' before any
    request was sent. The over-broad .catch mislabeled it 'failed to load'.
    
    - public/mls.html: wrap window.fetch to resolve against location.origin (never
      carries userinfo) + strip creds from absolute URLs; tidy the address bar.
    - scripts/serve.js: set an httpOnly session cookie on Basic-auth pass so the now
      same-origin fetches authenticate via cookie (not the un-primed auth cache).
    
    Verified real-browser (creds-in-URL AND dialog auth): 25,580 rows, 0 errors.
---
 public/mls.html  |  9 +++++++++
 scripts/serve.js | 20 +++++++++++++++++++-
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/public/mls.html b/public/mls.html
index 52ce062..0e63f7b 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -1,6 +1,15 @@
 <!doctype html>
 <html lang="en">
 <head>
+<!-- Credential-safe fetch (2026-08-18). Opening this page as http://admin:PASS@host/mls.html leaves
+     the embedded credentials in document.baseURI (history.replaceState clears location.href but NOT
+     baseURI). Every relative fetch('/data/…') resolves against that credentialed base and throws
+     "Request cannot be constructed from a URL that includes credentials" — the page renders but the
+     grid never fills ("failed to load"). Fix: resolve fetches against location.origin (which never
+     carries userinfo) and strip creds from any absolute URL, so the request is always constructible.
+     Same-origin so the Basic-auth session cookie (set on this navigation) authenticates it. Must run
+     before any fetch(). Also tidy the visible address bar. -->
+<script>(function(){var of=window.fetch,O=location.origin;window.fetch=function(u,o){try{if(typeof u==='string'){if(u.charAt(0)==='/')u=O+u;else if(u.indexOf('@')>=0){var x=new URL(u);x.username='';x.password='';u=x.href;}}else if(u&&u.url&&typeof u.url==='string'&&u.url.indexOf('@')>=0){var y=new URL(u.url);y.username='';y.password='';u=new Request(y.href,u);}}catch(e){}return of.call(this,u,o);};try{if(location.href.indexOf('@')>=0){history.replaceState(null,'',location.pathname+location.search+location.hash);}}catch(e){}})();</script>
 <script>(function(){try{var t=localStorage.getItem('crcp-theme2');document.documentElement.setAttribute('data-theme',t==='dark'?'dark':'light');}catch(e){document.documentElement.setAttribute('data-theme','light');}})();</script>
 <!-- Self-heal: the Nav Agent grid controls can persist a "removed"/"collapsed" state that hides the
      WHOLE records grid with no obvious way back (this is how the listings went blank). The main data
diff --git a/scripts/serve.js b/scripts/serve.js
index d964b2f..38b2371 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -43,13 +43,31 @@ app.use(express.json({ limit: '256kb' }));
 // /healthz stays OPEN so the deploy smoke-test + uptime canaries get a 200 without credentials.
 const AUTH_USER = process.env.CRCP_USER || 'admin';
 const AUTH_PASS = process.env.CRCP_PASS || 'DW2024!';
+// Session-cookie companion to the Basic-auth gate (2026-08-18): when a page is opened with
+// credentials in the URL (http://admin:pass@host/mls.html), the browser sends the Authorization
+// header ONLY on the top-level navigation — it does NOT forward URL creds to the page's fetch()
+// subresource calls, and the browser's HTTP-auth cache is only primed by a real 401 challenge
+// round-trip (which preemptive URL creds skip). Result: the HTML loaded 200 but every
+// fetch('/data/ranked.json') went out unauthenticated -> 401 -> "failed to load" / empty grid.
+// Fix: on a successful Basic auth, set an httpOnly cookie the same-origin fetches carry
+// automatically, and accept that cookie as an alternative to the header. Token is a stable
+// non-reversible hash of the creds (never the plaintext password), so it survives restarts.
+const crypto = require('crypto');
+const AUTH_TOKEN = crypto.createHash('sha256').update(AUTH_USER + ':' + AUTH_PASS).digest('hex');
 app.get('/healthz', (req, res) => res.type('text').send('ok'));
 app.use((req, res, next) => {
+  // 1) session cookie set after a prior successful Basic auth (fixes URL-creds subresource 401s)
+  const cookies = req.headers.cookie || '';
+  if (cookies.split(/;\s*/).some(c => c === 'crcp_auth=' + AUTH_TOKEN)) return next();
+  // 2) the standard Authorization header (Basic-auth dialog, curl, or the navigation request)
   const hdr = req.headers.authorization || '';
   const [scheme, encoded] = hdr.split(' ');
   if (scheme === 'Basic' && encoded) {
     const [u, ...rest] = Buffer.from(encoded, 'base64').toString().split(':');
-    if (u === AUTH_USER && rest.join(':') === AUTH_PASS) return next();
+    if (u === AUTH_USER && rest.join(':') === AUTH_PASS) {
+      res.cookie('crcp_auth', AUTH_TOKEN, { httpOnly: true, sameSite: 'Lax', path: '/', maxAge: 30 * 24 * 3600 * 1000 });
+      return next();
+    }
   }
   res.set('WWW-Authenticate', 'Basic realm="CRCP (crcp.agentabrams.com)", charset="UTF-8"');
   return res.status(401).send('Authentication required');

← 74d6c6e auto-data-snapshot: 2026-08-18T11:49:58 (2 data files) — dat  ·  back to Commercialrealestate  ·  CRE: reroute fetch-sfr-redfin off Browserbase to local Chrom 9885970 →