[object Object]

← back to Rentv

crm: strip credential suffixes (AIA/CCIM/SIOR/MBA…) when matching LinkedIn connections to contacts

389655dc7e7003782f7f1d571f094013bd2bf588 · 2026-08-12 17:47:51 -0700 · Steve Abrams

Files touched

Diff

commit 389655dc7e7003782f7f1d571f094013bd2bf588
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 12 17:47:51 2026 -0700

    crm: strip credential suffixes (AIA/CCIM/SIOR/MBA…) when matching LinkedIn connections to contacts
---
 server.js | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index dfaa13d0..7fff9ea1 100644
--- a/server.js
+++ b/server.js
@@ -132,6 +132,14 @@ app.use((req, res, next) => {
   if (SERVICE_TOKEN && authz === 'Bearer ' + SERVICE_TOKEN) { req.role = 'admin'; req.authVia = 'service'; return next(); }
   const role = CRED_ROLE.get(authz);                             // Basic — nginx-forwarded creds + existing callers
   if (role) { req.role = role; req.authVia = 'basic'; return next(); }
+  // LOCAL DEV BYPASS (dev :9704 only): when RENTV_DEV_OPEN=1 AND the peer is loopback, grant admin
+  // so the local previewer / iframe can render gated shells (/desk etc.) without a session cookie.
+  // Prod (claude-rentv's deploy) never sets RENTV_DEV_OPEN, so this is inert there; the loopback
+  // guard is a second belt (nginx would look loopback on prod, but the env flag gates it out).
+  if (process.env.OPEN === '1' || process.env.RENTV_DEV_OPEN === '1') {
+    const peer = String((req.socket && req.socket.remoteAddress) || '').replace('::ffff:', '');
+    if (peer === '127.0.0.1' || peer === '::1') { req.role = 'admin'; req.authVia = 'dev-loopback'; return next(); }
+  }
   // PUBLIC LAUNCH (TK-10284, Steve 2026-08-06 — "make it public"): unauthenticated visitors
   // get the PUBLIC consumer tier (front page, news, map, articles, public APIs). Internal data
   // and shells stay protected by adminOnly + the INTERNAL_STATIC guard, which BOTH key off
@@ -2351,7 +2359,12 @@ function crmLoadImported() {
 // highlight them in green across all RE-build surfaces. Drop your official LinkedIn export at
 // data/linkedin-connections.csv (Settings → Data Privacy → Get a copy of your data → Connections)
 // OR a simple data/linkedin-follows.json (array of names / {name,company}). Absent file = no-op.
-function liNorm(s){ return String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim(); }
+function liNorm(s){
+  let t = String(s || '').toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
+  // strip professional credential/suffix tokens so "Andrew Blumm, AIA" == "Andrew Blumm"
+  t = t.replace(/\b(aia|ccim|sior|mba|esq|jr|sr|ii|iii|iv|cpm|leed|ap|phd|md|mai|rpa|cfa|cpa|mrics|frics|pe|rid|ncidq|gri|abr|crs)\b/g, ' ').replace(/\s+/g, ' ').trim();
+  return t;
+}
 function loadLinkedInFollows() {
   const set = new Set();
   try {

← 83bbfa6e auto-data-snapshot: 2026-08-12T17:34:23 (7 data files) — dat  ·  back to Rentv  ·  crm: wider LinkedIn match (first+last token, catches middle- 37698626 →