SSO: gate uses shared app.promptpcb.ai accounts

This commit is contained in:
gator 2026-08-15 00:56:15 +00:00
parent d20596989c
commit 4c390d45c7

View file

@ -1,18 +1,17 @@
// auth.js — gates the whole site behind a PromptPCB account (Corben tenant auth for this site). // auth.js — gates the whole site behind your PromptPCB account (SHARED with app.promptpcb.ai via its
// Nothing loads until you're signed in; the catalog + file URLs come only from the authed function. // SSO bridge). Nothing loads until you're signed in; the catalog + file URLs come only from the authed
// Corben function, which itself verifies your PromptPCB session.
(function () { (function () {
var API = 'https://api.corben.world'; var CORBEN = 'https://api.corben.world'; // Corben functions (catalog/autofill/submit)
var SITE = '6ab5ec79-8b91-453d-aacc-5be76f9271d1'; // this site IS the tenant var AUTH = 'https://app.promptpcb.ai'; // shared PromptPCB accounts live here
var FN_CATALOG = '0f3cd09e-81ce-43f4-88e7-cde3d91f122e'; var FN_CATALOG = '0f3cd09e-81ce-43f4-88e7-cde3d91f122e';
var TK_CATALOG = '687d2466cea418815433b60946d6742a'; var TK_CATALOG = '687d2466cea418815433b60946d6742a';
var LS = 'ppcb_token'; var LS = 'ppcb_token';
window.PPCB = { token: null, user: null }; window.PPCB = { token: null, user: null };
window.ppcbReady = function (fn) { window.ppcbReady = function (fn) { if (window.PPCB.token) fn(); else addEventListener('ppcb:auth', function () { fn(); }, { once: true }); };
if (window.PPCB.token) fn(); else addEventListener('ppcb:auth', function () { fn(); }, { once: true });
};
window.getCatalog = async function () { window.getCatalog = async function () {
var r = await fetch(API + '/functions/' + FN_CATALOG + '/invoke?token=' + TK_CATALOG, { var r = await fetch(CORBEN + '/functions/' + FN_CATALOG + '/invoke?token=' + TK_CATALOG, {
method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + window.PPCB.token }, body: '{}' }); method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + window.PPCB.token }, body: '{}' });
if (r.status === 401) { logout(); throw new Error('session expired'); } if (r.status === 401) { logout(); throw new Error('session expired'); }
var d = await r.json(); return d.parts || []; var d = await r.json(); return d.parts || [];
@ -23,7 +22,7 @@
window.PPCB.user = user; document.documentElement.classList.add('ppcb-authed'); window.PPCB.user = user; document.documentElement.classList.add('ppcb-authed');
var g = document.getElementById('ppcb-gate'); if (g) g.style.display = 'none'; var g = document.getElementById('ppcb-gate'); if (g) g.style.display = 'none';
var c = document.querySelector('.credits'); var c = document.querySelector('.credits');
if (c) c.innerHTML = '<span title="' + user.email + '" style="cursor:default">◆ ' + (user.display_name || user.email.split('@')[0]) + '</span> · <a href="#" id="ppcb-logout" style="color:var(--dim)">sign out</a>'; if (c) c.innerHTML = '<span title="' + user.email + '" style="cursor:default">◆ ' + (user.name || user.email.split('@')[0]) + '</span> · <a href="#" id="ppcb-logout" style="color:var(--dim)">sign out</a>';
var lo = document.getElementById('ppcb-logout'); if (lo) lo.onclick = function (e) { e.preventDefault(); logout(); }; var lo = document.getElementById('ppcb-logout'); if (lo) lo.onclick = function (e) { e.preventDefault(); logout(); };
dispatchEvent(new Event('ppcb:auth')); dispatchEvent(new Event('ppcb:auth'));
} }
@ -34,9 +33,10 @@
var t = localStorage.getItem(LS); var t = localStorage.getItem(LS);
if (!t) return gate(); if (!t) return gate();
try { try {
var r = await fetch(API + '/auth/tenant/me', { headers: { Authorization: 'Bearer ' + t } }); var r = await fetch(AUTH + '/api/auth/sso/verify', { headers: { Authorization: 'Bearer ' + t } });
if (!r.ok) return gate(); if (!r.ok) return gate();
var u = await r.json(); window.PPCB.token = t; authed(u); var u = await r.json(); if (!u.valid) return gate();
window.PPCB.token = t; authed({ email: u.email, name: u.name });
} catch (e) { gate(); } } catch (e) { gate(); }
} }
@ -48,13 +48,14 @@
'<div class="gate-card">' + '<div class="gate-card">' +
'<div class="gate-brand"><span class="mk"></span> PromptPCB <small>/ parts</small></div>' + '<div class="gate-brand"><span class="mk"></span> PromptPCB <small>/ parts</small></div>' +
'<div class="gate-h">Sign in to the part library</div>' + '<div class="gate-h">Sign in to the part library</div>' +
'<div class="gate-sub">One PromptPCB account. Every part—real specs, 3D, SPICE—once you’re in.</div>' + '<div class="gate-sub">Your PromptPCB account — the same one you use at app.promptpcb.ai. Real specs, 3D, SPICE for every part, once you’re in.</div>' +
'<div class="gate-tabs"><button data-m="login" class="on">Sign in</button><button data-m="signup">Create account</button></div>' + '<div class="gate-tabs"><button data-m="login" class="on">Sign in</button><button data-m="signup">Create account</button></div>' +
'<input id="g-name" placeholder="Display name" style="display:none">' + '<input id="g-name" placeholder="Your name" style="display:none">' +
'<input id="g-email" type="email" placeholder="you@example.com" autocomplete="email">' + '<input id="g-email" type="email" placeholder="you@example.com" autocomplete="email">' +
'<input id="g-pass" type="password" placeholder="Password (8+ characters)" autocomplete="current-password">' + '<input id="g-pass" type="password" placeholder="Password (8+ characters)" autocomplete="current-password">' +
'<button class="btn gate-go" id="g-go">Sign in</button>' + '<button class="btn gate-go" id="g-go">Sign in</button>' +
'<div class="gate-msg" id="g-msg"></div>' + '<div class="gate-msg" id="g-msg"></div>' +
'<div style="margin-top:18px;font-family:var(--mono);font-size:12px;color:var(--dim2)">One account across all of PromptPCB · <a href="https://app.promptpcb.ai" style="color:var(--dim)">app.promptpcb.ai ↗</a></div>' +
'</div>'; '</div>';
document.body.appendChild(el); document.body.appendChild(el);
var mode = 'login'; var mode = 'login';
@ -71,14 +72,14 @@
async function go() { async function go() {
var email = document.getElementById('g-email').value.trim(), pass = document.getElementById('g-pass').value; 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 = '<span class="spin"></span> …'; var msg = document.getElementById('g-msg'); msg.style.color = 'var(--dim)'; msg.innerHTML = '<span class="spin"></span> …';
var path = mode === 'signup' ? '/auth/tenant/register' : '/auth/tenant/login'; var path = mode === 'signup' ? '/api/auth/signup' : '/api/auth/login';
var body = { tenant_id: SITE, email: email, password: pass }; var body = { email: email, password: pass };
if (mode === 'signup') body.display_name = document.getElementById('g-name').value.trim(); if (mode === 'signup') body.name = document.getElementById('g-name').value.trim();
try { try {
var r = await fetch(API + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); var r = await fetch(AUTH + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
var d = await r.json(); var d = await r.json();
if (!r.ok) { msg.style.color = 'var(--bad,#ff6b6b)'; msg.textContent = d.error || 'failed'; return; } if (!r.ok || !d.token) { msg.style.color = '#ff6b6b'; msg.textContent = d.error || 'failed'; return; }
localStorage.setItem(LS, d.token); window.PPCB.token = d.token; authed(d.user); localStorage.setItem(LS, d.token); window.PPCB.token = d.token; authed({ email: d.user.email, name: d.user.name });
} catch (e) { msg.style.color = '#ff6b6b'; msg.textContent = 'network error'; } } catch (e) { msg.style.color = '#ff6b6b'; msg.textContent = 'network error'; }
} }
} }