// Universal Auth Module: Immutable, Pattern-Based, Project-Agnostic // Usage: import and call Auth.use('pb-graph') or Auth.use('pb') or Auth.use('graph') // Provides: getToken(type), ensureToken(type), refreshToken(type), getUserInfo(), promptReauth() // Token types: 'pb' (PocketBase user/agent), 'graph' (Graph delegated), 'graphAgent' (Graph app-only) // All storage, retrieval, refresh, and UI are standardized and immutable const TOKEN_KEYS = { 'pb-user': 'pbUserToken', 'pb-agent': 'pbAgentToken', 'graph-user': 'graphUserToken', 'graph-agent': 'graphAgentToken' }; function parsePattern(pattern) { return pattern.split('+').map(s => s.trim()).filter(Boolean); } window.Auth = (() => { let pattern = 'pb-user'; // default let enabledTypes = ['pb-user']; let pb = null; let popup = null; let agentCreds = { email: '', password: '' }; let superuserCreds = { email: '', password: '' }; // --- Setup --- function use(type) { pattern = type; enabledTypes = parsePattern(type); if (enabledTypes.some(t => t.startsWith('pb'))) { pb = window.PocketBase ? new PocketBase('https://pocketbase.ccllc.pro') : null; } // Only one-time setup if (!document.getElementById('authPopup')) { createPopup(); } } // --- Token Storage --- function getToken(type) { return localStorage.getItem(TOKEN_KEYS[type]) || ''; } function setToken(type, value) { if (value) localStorage.setItem(TOKEN_KEYS[type], value); } function clearToken(type) { localStorage.removeItem(TOKEN_KEYS[type]); } // --- Info Extraction --- function getUserInfo() { if (pb && pb.authStore.model) { return { displayName: pb.authStore.model.display_name || pb.authStore.model.name || pb.authStore.model.email || pb.authStore.model.username || 'Unknown', email: pb.authStore.model.email || pb.authStore.model.username || null }; } return { displayName: '', email: '' }; } // --- Token Ensure/Refresh --- async function ensureToken(type) { let token = getToken(type); if (token) return token; if (type === 'pb-user' && pb) { if (pb.authStore.isValid) { setToken('pb-user', pb.authStore.token); return pb.authStore.token; } // Choose login method: email/password or OAuth if (pattern.includes('pb-user-oauth')) { return await promptReauth('pb-user-oauth'); } else { return await promptReauth('pb-user'); } } if (type === 'pb-superuser' && pb) { // Prompt for superuser credentials await promptReauth('pb-superuser'); // Use superuser credentials to login const authData = await pb.collection('_superuser').authWithPassword(superuserCreds.email, superuserCreds.password); setToken('pb-superuser', pb.authStore.token); return pb.authStore.token; } if (type === 'pb-agent' && pb) { // Prompt for agent credentials if not set if (!agentCreds.email || !agentCreds.password) { await promptReauth('pb-agent'); } // Use agent credentials to login const authData = await pb.collection('users').authWithPassword(agentCreds.email, agentCreds.password); setToken('pb-agent', pb.authStore.token); return pb.authStore.token; } if (type === 'graph-user' && pb) { // OAuth only return await promptReauth('graph-user'); } if (type === 'graph-agent') { // Prompt for agent credentials if not set if (!agentCreds.email || !agentCreds.password) { await promptReauth('graph-agent'); } // Use agent credentials to get token (simulate, real flow is backend) setToken('graph-agent', 'AGENT_TOKEN_' + agentCreds.email); return getToken('graph-agent'); } throw new Error('Unknown token type'); } async function refreshToken(type) { if (type === 'graph-user' && pb) { try { const authData = await pb.collection('users').authRefresh(); const meta = authData?.meta || pb?.authStore?.model?.meta || {}; const token = meta.graphAccessToken || meta.graph_token || meta.graphToken || meta.accessToken || meta.access_token || meta.token || meta.rawToken || meta?.authData?.access_token || ''; setToken('graph-user', token); return token || ''; } catch (err) { return ''; } } // Add refresh logic for other types as needed return ''; } // --- Reauth Popup --- function createPopup() { popup = document.createElement('div'); popup.id = 'authPopup'; popup.style = 'display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;background:rgba(0,0,0,0.4);z-index:9999;align-items:center;justify-content:center;'; popup.innerHTML = `

Sign In Required

`; document.body.appendChild(popup); } function showForm(type) { const forms = { 'pb-user': `

`, 'pb-user-oauth': ``, 'pb-superuser': `

`, 'pb-agent': `

`, 'graph-user': ``, 'graph-agent': `

` }; document.getElementById('authPopupForms').innerHTML = forms[type] || ''; } async function promptReauth(type) { showForm(type); popup.style.display = 'flex'; return new Promise((resolve, reject) => { document.getElementById('authPopupBtn').onclick = async () => { try { if (type === 'pb-user') { const email = document.getElementById('pbUserEmail').value; const password = document.getElementById('pbUserPassword').value; const authData = await pb.collection('users').authWithPassword(email, password); setToken('pb-user', pb.authStore.token); popup.style.display = 'none'; resolve(pb.authStore.token); } else if (type === 'pb-user-oauth') { const authData = await pb.collection('users').authWithOAuth2({ provider: 'microsoft' }); setToken('pb-user', authData?.meta?.graphAccessToken || ''); popup.style.display = 'none'; resolve(authData?.meta?.graphAccessToken || ''); } else if (type === 'pb-superuser') { superuserCreds.email = document.getElementById('pbSuperuserEmail').value; superuserCreds.password = document.getElementById('pbSuperuserPassword').value; popup.style.display = 'none'; resolve(); } else if (type === 'pb-agent') { agentCreds.email = document.getElementById('pbAgentEmail').value; agentCreds.password = document.getElementById('pbAgentPassword').value; popup.style.display = 'none'; resolve(); } else if (type === 'graph-user') { const authData = await pb.collection('users').authWithOAuth2({ provider: 'microsoft' }); setToken('graph-user', authData?.meta?.graphAccessToken || ''); popup.style.display = 'none'; resolve(authData?.meta?.graphAccessToken || ''); } else if (type === 'graph-agent') { agentCreds.email = document.getElementById('graphAgentEmail').value; agentCreds.password = document.getElementById('graphAgentPassword').value; popup.style.display = 'none'; resolve(); } } catch (err) { document.getElementById('authPopupError').textContent = err.message || 'Authentication failed.'; document.getElementById('authPopupError').style.display = ''; } }; }); } // --- API --- return { use, getToken, setToken, clearToken, getUserInfo, ensureToken, refreshToken, promptReauth }; })(); window.Auth = Auth; // Example usage: // Auth.use('pb-graph'); // const token = await Auth.ensureToken('graph'); // const user = Auth.getUserInfo(); // All login prompts are handled via the popup, which is hidden unless needed.