parts-store/assets/auth.js

87 lines
5.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// auth.js — gates the whole site behind your PromptPCB account (SHARED with app.promptpcb.ai via its
// 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 () {
var CORBEN = 'https://api.corben.world'; // Corben functions (catalog/autofill/submit)
var AUTH = 'https://app.promptpcb.ai'; // shared PromptPCB accounts live here
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(CORBEN + '/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 = '<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(); };
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(AUTH + '/api/auth/sso/verify', { headers: { Authorization: 'Bearer ' + t } });
if (!r.ok) return gate();
var u = await r.json(); if (!u.valid) return gate();
window.PPCB.token = t; authed({ email: u.email, name: u.name });
} 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 =
'<div class="gate-card">' +
'<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-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>' +
'<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-pass" type="password" placeholder="Password (8+ characters)" autocomplete="current-password">' +
'<button class="btn gate-go" id="g-go">Sign in</button>' +
'<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>';
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 = '<span class="spin"></span> …';
var path = mode === 'signup' ? '/api/auth/signup' : '/api/auth/login';
var body = { email: email, password: pass };
if (mode === 'signup') body.name = document.getElementById('g-name').value.trim();
try {
var r = await fetch(AUTH + path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
var d = await r.json();
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({ email: d.user.email, name: d.user.name });
} catch (e) { msg.style.color = '#ff6b6b'; msg.textContent = 'network error'; }
}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', check); else check();
})();