From 86e9656c727528ec2ed7fa5a3608300ec8b49a94 Mon Sep 17 00:00:00 2001 From: gator Date: Sat, 15 Aug 2026 00:27:24 +0000 Subject: [PATCH] Gate entire site behind PromptPCB login (Corben tenant auth); catalog + files served only to authed users --- assets/app.js | 7 ++-- assets/auth.js | 86 ++++++++++++++++++++++++++++++++++++++++++++++++ assets/part.js | 8 ++--- assets/style.css | 18 ++++++++++ assets/submit.js | 5 +-- index.html | 1 + mcp.html | 1 + part.html | 1 + submit.html | 1 + 9 files changed, 115 insertions(+), 13 deletions(-) create mode 100644 assets/auth.js diff --git a/assets/app.js b/assets/app.js index 86b2951..42cbfad 100644 --- a/assets/app.js +++ b/assets/app.js @@ -57,9 +57,8 @@ async function render() { if (data) { const img = new Image(); img.src = data; img.style.cssText = 'width:100%;height:100%;object-fit:contain'; cv.replaceWith(img); } } } -(async () => { - PARTS = await (await fetch('/assets/catalog.json')).json(); - $('#cred-n').textContent = localStorage.getItem('ppcb_credits') || '5'; +window.ppcbReady(async () => { + PARTS = await window.getCatalog(); // authed — only reachable once signed in renderChips(); render(); let t; $('#q').addEventListener('input', () => { clearTimeout(t); t = setTimeout(render, 160); }); -})(); +}); diff --git a/assets/auth.js b/assets/auth.js new file mode 100644 index 0000000..62719a1 --- /dev/null +++ b/assets/auth.js @@ -0,0 +1,86 @@ +// auth.js — gates the whole site behind a PromptPCB account (Corben tenant auth for this site). +// Nothing loads until you're signed in; the catalog + file URLs come only from the authed function. +(function () { + var API = 'https://api.corben.world'; + var SITE = '6ab5ec79-8b91-453d-aacc-5be76f9271d1'; // this site IS the tenant + var FN_CATALOG = '0f3cd09e-81ce-43f4-88e7-cde3d91f122e'; + var TK_CATALOG = '687d2466cea418815433b60946d6742a'; + var LS = 'ppcb_token'; + window.PPCB = { token: null, user: null }; + + window.ppcbReady = function (fn) { + if (window.PPCB.token) fn(); else addEventListener('ppcb:auth', function () { fn(); }, { once: true }); + }; + window.getCatalog = async function () { + var r = await fetch(API + '/functions/' + FN_CATALOG + '/invoke?token=' + TK_CATALOG, { + method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + window.PPCB.token }, body: '{}' }); + if (r.status === 401) { logout(); throw new Error('session expired'); } + var d = await r.json(); return d.parts || []; + }; + window.ppcbAuth = function () { return { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (window.PPCB.token || '') }; }; + + function authed(user) { + window.PPCB.user = user; document.documentElement.classList.add('ppcb-authed'); + var g = document.getElementById('ppcb-gate'); if (g) g.style.display = 'none'; + var c = document.querySelector('.credits'); + if (c) c.innerHTML = '◆ ' + (user.display_name || user.email.split('@')[0]) + ' · sign out'; + var lo = document.getElementById('ppcb-logout'); if (lo) lo.onclick = function (e) { e.preventDefault(); logout(); }; + dispatchEvent(new Event('ppcb:auth')); + } + function logout() { localStorage.removeItem(LS); location.reload(); } + window.ppcbLogout = logout; + + async function check() { + var t = localStorage.getItem(LS); + if (!t) return gate(); + try { + var r = await fetch(API + '/auth/tenant/me', { headers: { Authorization: 'Bearer ' + t } }); + if (!r.ok) return gate(); + var u = await r.json(); window.PPCB.token = t; authed(u); + } catch (e) { gate(); } + } + + function gate() { + document.documentElement.classList.remove('ppcb-authed'); + if (document.getElementById('ppcb-gate')) { document.getElementById('ppcb-gate').style.display = 'flex'; return; } + var el = document.createElement('div'); el.id = 'ppcb-gate'; + el.innerHTML = + '
' + + '
PromptPCB / parts
' + + '
Sign in to the part library
' + + '
One PromptPCB account. Every part—real specs, 3D, SPICE—once you’re in.
' + + '
' + + '' + + '' + + '' + + '' + + '
' + + '
'; + document.body.appendChild(el); + var mode = 'login'; + el.querySelectorAll('.gate-tabs button').forEach(function (b) { + b.onclick = function () { + mode = b.dataset.m; el.querySelectorAll('.gate-tabs button').forEach(function (x) { x.classList.toggle('on', x === b); }); + document.getElementById('g-name').style.display = mode === 'signup' ? 'block' : 'none'; + document.getElementById('g-go').textContent = mode === 'signup' ? 'Create account' : 'Sign in'; + document.getElementById('g-msg').textContent = ''; + }; + }); + document.getElementById('g-go').onclick = go; + el.addEventListener('keydown', function (e) { if (e.key === 'Enter') go(); }); + async function go() { + var email = document.getElementById('g-email').value.trim(), pass = document.getElementById('g-pass').value; + var msg = document.getElementById('g-msg'); msg.style.color = 'var(--dim)'; msg.innerHTML = ' …'; + var path = mode === 'signup' ? '/auth/tenant/register' : '/auth/tenant/login'; + var body = { tenant_id: SITE, email: email, password: pass }; + if (mode === 'signup') body.display_name = document.getElementById('g-name').value.trim(); + try { + var r = await fetch(API + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); + var d = await r.json(); + if (!r.ok) { msg.style.color = 'var(--bad,#ff6b6b)'; msg.textContent = d.error || 'failed'; return; } + localStorage.setItem(LS, d.token); window.PPCB.token = d.token; authed(d.user); + } catch (e) { msg.style.color = '#ff6b6b'; msg.textContent = 'network error'; } + } + } + if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', check); else check(); +})(); diff --git a/assets/part.js b/assets/part.js index 0418faf..0f383a3 100644 --- a/assets/part.js +++ b/assets/part.js @@ -49,11 +49,10 @@ function schematicSVG(p) { return s + ''; } -(async () => { - const parts = await (await fetch('/assets/catalog.json')).json(); +window.ppcbReady(async () => { + const parts = await window.getCatalog(); const p = parts.find((x) => x.id === id); const root = document.getElementById('root'); - document.getElementById('cred-n').textContent = localStorage.getItem('ppcb_credits') || '5'; if (!p) { root.innerHTML = '
Part not found. Back to library
'; return; } document.title = `${p.mpn || p.id} — PromptPCB Parts`; @@ -105,7 +104,6 @@ function schematicSVG(p) { show('d3'); document.getElementById('dl-all').onclick = async () => { - if (!spendCredit()) return; try { const wrl = await (await fetch(p.model_3d)).text(); dl(`${p.id}.wrl`, wrl); } catch {} dl(`${p.id}.cir`, `* ${p.mpn} SPICE model — PromptPCB Parts\n${p.spice || ''}\n`); dl(`${p.id}.json`, JSON.stringify(p, null, 2), 'application/json'); @@ -115,4 +113,4 @@ function schematicSVG(p) { if (!lib.includes(p.id)) { lib.push(p.id); localStorage.setItem('ppcb_lib', JSON.stringify(lib)); } document.getElementById('lib').textContent = '✓ In library'; }; -})(); +}); diff --git a/assets/style.css b/assets/style.css index d198916..adf48bf 100644 --- a/assets/style.css +++ b/assets/style.css @@ -152,3 +152,21 @@ body{animation:pagein .5s ease both} .vizpanel .hint{position:absolute;bottom:12px;left:12px;font-family:var(--mono);font-size:11px;color:#555} .viznote{font-family:var(--mono);font-size:12px;color:var(--dim);margin-top:12px;line-height:1.5} .viznote b{color:var(--acc)} + +/* login gate */ +html:not(.ppcb-authed) body{overflow:hidden} +#ppcb-gate{position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;align-items:center;justify-content:center;padding:24px} +.gate-card{width:100%;max-width:400px;display:flex;flex-direction:column} +.gate-brand{display:flex;align-items:center;gap:10px;font-weight:700;font-size:20px;margin-bottom:30px} +.gate-brand .mk{width:13px;height:13px;background:var(--acc);border-radius:2px} +.gate-brand small{font-family:var(--mono);font-size:12px;color:var(--dim);font-weight:400} +.gate-h{font-size:29px;font-weight:700;letter-spacing:-.02em;margin-bottom:8px;line-height:1.1} +.gate-sub{color:var(--dim);font-size:15px;line-height:1.5;margin-bottom:26px} +.gate-tabs{display:flex;border:1px solid var(--line2);border-radius:2px;width:fit-content;overflow:hidden;margin-bottom:18px} +.gate-tabs button{background:transparent;border:none;border-right:1px solid var(--line2);color:var(--dim);font-family:var(--mono);font-size:13px;padding:8px 18px;cursor:pointer} +.gate-tabs button:last-child{border-right:none} +.gate-tabs button.on{background:var(--acc);color:var(--acc-ink);font-weight:600} +#ppcb-gate input{background:var(--panel);border:1px solid var(--line2);border-radius:2px;color:var(--text);padding:14px 15px;font-size:15px;font-family:var(--display);outline:none;margin-bottom:12px} +#ppcb-gate input:focus{border-color:var(--acc)} +.gate-go{width:100%;justify-content:center;margin-top:4px} +.gate-msg{font-family:var(--mono);font-size:13px;margin-top:14px;min-height:18px} diff --git a/assets/submit.js b/assets/submit.js index 136b632..c858b17 100644 --- a/assets/submit.js +++ b/assets/submit.js @@ -6,11 +6,9 @@ const FN_SUBMIT = 'b02d634b-cd6d-414d-bf9c-80cf6cc2ccdc'; const TK_SUBMIT = '9c3ed2ece766bcc18ed7d33996bfeb86'; const $ = (id) => document.getElementById(id); -$('cred-n').textContent = localStorage.getItem('ppcb_credits') || '5'; - async function invoke(fn, token, payload) { const r = await fetch(`${API}/functions/${fn}/invoke?token=${token}`, { - method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); + method: 'POST', headers: window.ppcbAuth(), body: JSON.stringify(payload) }); // authed: sends the session token return r.json(); } @@ -57,7 +55,6 @@ $('submit').onclick = async () => { try { const d = await invoke(FN_SUBMIT, TK_SUBMIT, { email, part }); if (!d.ok) throw new Error(d.error || 'submit failed'); - localStorage.setItem('ppcb_credits', d.credits); $('cred-n').textContent = d.credits; document.querySelector('.form').innerHTML = `

Thank you — submitted for review ✓

Browse the catalog →`; diff --git a/index.html b/index.html index 8c10505..8c2513c 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,7 @@ +
diff --git a/mcp.html b/mcp.html index cb5b8e0..a586285 100644 --- a/mcp.html +++ b/mcp.html @@ -4,6 +4,7 @@ +
PromptPCB / parts diff --git a/part.html b/part.html index 869d3eb..1b64a0f 100644 --- a/part.html +++ b/part.html @@ -4,6 +4,7 @@ +
PromptPCB / parts diff --git a/submit.html b/submit.html index 380fec7..5f4e62e 100644 --- a/submit.html +++ b/submit.html @@ -4,6 +4,7 @@ +