UNPKG

gunauth

Version:

Minimal identity provider using GUN and SEA

408 lines (360 loc) 16.7 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>App 2 - Secure GunAuth Demo</title> <style> body { font-family: Arial, sans-serif; max-width: 800px; margin: 20px auto; padding: 20px; background-color: #fff8f0; } .container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .status { padding: 15px; border-radius: 4px; margin: 10px 0; } .authenticated { background-color: #d4edda; border: 1px solid #c3e6cb; color: #155724; } .unauthenticated { background-color: #f8d7da; border: 1px solid #f5c6cb; color: #721c24; } .form-group { margin-bottom: 15px; } .auth-section { margin-bottom: 30px; padding: 20px; border: 1px solid #ddd; border-radius: 8px; background-color: #f9f9f9; } input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } button { background-color: #28a745; color: white; padding: 12px 24px; border: none; border-radius: 4px; cursor: pointer; margin: 5px; } button:hover { background-color: #1e7e34; } button:disabled { background-color: #6c757d; cursor: not-allowed; } .session-info { background-color: #e9ecef; padding: 15px; border-radius: 4px; margin: 15px 0; font-family: monospace; white-space: pre-wrap; } .loading { opacity: 0.7; } </style> </head> <body> <div class="container"> <h1>🌟 App 2 - Secure GunAuth Demo</h1> <p><strong>Port:</strong> 8002 | <strong>Domain:</strong> localhost:8002</p> <div id="status" class="status unauthenticated"> 🔒 Not authenticated </div> <!-- Registration --> <div class="auth-section" id="registerSection"> <h3>📝 Register New User</h3> <p><em>Create account with secure client-side key storage</em></p> <div class="form-group"> <input type="text" id="regUsername" placeholder="Username" required> </div> <div class="form-group"> <input type="password" id="regPassword" placeholder="Password" required> </div> <button id="registerBtn" style="background-color: #007bff;">Register</button> </div> <!-- SSO Authentication --> <div class="auth-section" id="ssoSection"> <h3>🌐 SSO Authentication</h3> <p><em>OAuth2-like redirect flow - most secure for production</em></p> <button id="ssoLoginBtn" style="background-color: #17a2b8;">Login via SSO</button> </div> <!-- Gun.user.recall() Integration --> <div class="auth-section" id="recallSection"> <h3>🔄 gun.user.recall() Integration</h3> <p><em>Gun.js compatible session restoration - try auto recall first!</em></p> <div class="form-group"> <button id="recallAutoBtn" style="background-color: #28a745;">Auto Recall Session</button> </div> <div class="form-group"> <input type="text" id="recallTotpUsername" placeholder="Username (for TOTP recall)"> <input type="password" id="recallTotpPassword" placeholder="Password (for TOTP recall)"> <input type="text" id="recallTotpCode" placeholder="TOTP Code" maxlength="6"> <button id="recallTotpBtn" style="background-color: #6f42c1;">Recall with TOTP</button> </div> <div id="recallResult" style="display:none; background:#f8f9fa; padding:10px; border-radius:4px; margin:10px 0; font-family:monospace; font-size:12px;"></div> </div> <!-- TOTP Cross-Domain Login --> <div class="auth-section" id="totpLoginSection"> <h3>🔐 TOTP Cross-Domain Login</h3> <p><em>Login with username and TOTP code from primary domain</em></p> <div class="form-group"> <input type="text" id="totpUsername" placeholder="Username" required> </div> <div class="form-group"> <input type="text" id="totpCode" placeholder="6-digit TOTP code" maxlength="6" pattern="[0-9]{6}" required> </div> <button id="totpLoginBtn" style="background-color: #6f42c1;">Login with TOTP</button> <div id="totpStatus" style="margin-top: 10px; font-style: italic;"></div> </div> <!-- Session Information --> <div id="sessionInfo" class="session-info" style="display: none;"></div> <p><small>Cross-domain test: <a href="http://localhost:8001/app1.html" target="_blank">App 1 (Port 8001)</a></small></p> </div> <!-- Scripts --> <script src="https://cdn.jsdelivr.net/npm/gun/gun.js"></script> <script src="https://cdn.jsdelivr.net/npm/gun/sea.js"></script> <script src="totp-client.js"></script> <script src="gunauth-client.js"></script> <script> console.log('🌟 App 2 initialized with secure GunAuth client'); // Initialize GunAuth client with HTTP-only Gun connection const auth = new GunAuthClient(); // DOM elements const statusEl = document.getElementById('status'); const sessionInfo = document.getElementById('sessionInfo'); // Registration elements const registerBtn = document.getElementById('registerBtn'); const regUsernameInput = document.getElementById('regUsername'); const regPasswordInput = document.getElementById('regPassword'); // SSO elements const ssoLoginBtn = document.getElementById('ssoLoginBtn'); // TOTP elements const totpLoginBtn = document.getElementById('totpLoginBtn'); // Gun.user.recall() elements const recallAutoBtn = document.getElementById('recallAutoBtn'); const recallTotpBtn = document.getElementById('recallTotpBtn'); const recallTotpUsernameInput = document.getElementById('recallTotpUsername'); const recallTotpPasswordInput = document.getElementById('recallTotpPassword'); const recallTotpCodeInput = document.getElementById('recallTotpCode'); const recallResult = document.getElementById('recallResult'); // Handle SSO callback if present async function handleSSO() { if (auth.isSSOCallback()) { console.log('🔄 Processing SSO callback'); const result = await auth.handleSSOCallback(); if (result.success) { updateUI(true, result.user); } } } // Update UI based on authentication status function updateUI(authenticated, user = null) { if (authenticated && user) { statusEl.className = 'status authenticated'; statusEl.textContent = `✅ Authenticated as: ${user.username}`; sessionInfo.style.display = 'block'; sessionInfo.textContent = `Session: ${JSON.stringify(user, null, 2)}`; } else { statusEl.className = 'status unauthenticated'; statusEl.textContent = '🔒 Not authenticated'; sessionInfo.style.display = 'none'; } } // Registration handler registerBtn.addEventListener('click', async () => { const username = regUsernameInput.value.trim(); const password = regPasswordInput.value.trim(); if (!username || !password) { alert('Please fill in both fields'); return; } registerBtn.disabled = true; registerBtn.textContent = 'Registering...'; try { const result = await auth.register(username, password); if (result.success) { alert('✅ Registration successful! You can now login.'); regUsernameInput.value = ''; regPasswordInput.value = ''; } else { alert(`❌ Registration failed: ${result.error}`); } } catch (error) { console.error('Registration error:', error); alert(`❌ Registration error: ${error.message}`); } finally { registerBtn.disabled = false; registerBtn.textContent = 'Register'; } }); // SSO Login handler ssoLoginBtn.addEventListener('click', async () => { ssoLoginBtn.disabled = true; ssoLoginBtn.textContent = 'Redirecting...'; try { const result = await auth.ssoLogin('http://localhost:8002/app2.html'); // This will redirect, so we shouldn't reach here } catch (error) { console.error('SSO error:', error); alert(`❌ SSO error: ${error.message}`); ssoLoginBtn.disabled = false; ssoLoginBtn.textContent = 'Login via SSO'; } }); // TOTP Cross-Domain Login totpLoginBtn.addEventListener('click', async () => { const username = document.getElementById('totpUsername').value.trim(); const totpCode = document.getElementById('totpCode').value.trim(); const statusEl = document.getElementById('totpStatus'); if (!username || !totpCode) { statusEl.textContent = '❌ Please enter both username and TOTP code'; statusEl.style.color = 'red'; return; } if (totpCode.length !== 6 || !/^\d{6}$/.test(totpCode)) { statusEl.textContent = '❌ TOTP code must be exactly 6 digits'; statusEl.style.color = 'red'; return; } statusEl.textContent = '🔄 Loading TOTP session...'; statusEl.style.color = 'blue'; totpLoginBtn.disabled = true; try { const result = await auth.loginWithTOTP(username, totpCode); if (result.success) { statusEl.textContent = '✅ TOTP login successful!'; statusEl.style.color = 'green'; updateUI(true, { username: username, loginMethod: 'TOTP Cross-Domain', timestamp: new Date().toISOString() }); // Clear form document.getElementById('totpUsername').value = ''; document.getElementById('totpCode').value = ''; } else { statusEl.textContent = `❌ ${result.error}`; statusEl.style.color = 'red'; } } catch (error) { console.error('TOTP login error:', error); statusEl.textContent = `❌ Login error: ${error.message}`; statusEl.style.color = 'red'; } finally { totpLoginBtn.disabled = false; } }); // Gun.user.recall() Event Listeners // Auto recall - tries all available methods recallAutoBtn.addEventListener('click', async () => { recallAutoBtn.disabled = true; recallAutoBtn.textContent = 'Recalling...'; recallResult.style.display = 'block'; recallResult.textContent = 'Attempting automatic session recall...'; try { const result = await auth.user.recall(); if (result.success) { recallResult.innerHTML = ` <strong>✅ Recall Successful!</strong><br> Source: ${result.source}<br> Username: ${result.session?.username || 'N/A'}<br> Token: ${result.session?.token?.substring(0, 20)}...<br> Time: ${new Date().toISOString()} `; updateUI(true, { username: result.session?.username || 'Recalled User', loginMethod: 'gun.user.recall()', source: result.source, timestamp: new Date().toISOString() }); } else { recallResult.innerHTML = ` <strong>❌ Recall Failed</strong><br> Error: ${result.error}<br> Available options: ${JSON.stringify(result.availableOptions, null, 2)}<br> Time: ${new Date().toISOString()} `; } } catch (error) { recallResult.innerHTML = ` <strong>❌ Recall Error</strong><br> Error: ${error.message}<br> Time: ${new Date().toISOString()} `; } finally { recallAutoBtn.disabled = false; recallAutoBtn.textContent = 'Auto Recall Session'; } }); // TOTP-based recall recallTotpBtn.addEventListener('click', async () => { const username = recallTotpUsernameInput.value.trim(); const password = recallTotpPasswordInput.value; const totpCode = recallTotpCodeInput.value.trim(); if (!username || !password || !totpCode) { alert('Please enter username, password, and current TOTP code for recall'); return; } if (!/^\d{6}$/.test(totpCode)) { alert('TOTP code must be 6 digits'); return; } recallTotpBtn.disabled = true; recallTotpBtn.textContent = 'Recalling...'; recallResult.style.display = 'block'; recallResult.textContent = 'Attempting TOTP-based recall...'; try { const result = await auth.user.recall({ username, password, totpCode }); if (result.success) { recallResult.innerHTML = ` <strong>✅ TOTP Recall Successful!</strong><br> Source: ${result.source}<br> Username: ${result.session?.username || 'N/A'}<br> Token: ${result.session?.token?.substring(0, 20)}...<br> Time: ${new Date().toISOString()} `; updateUI(true, { username: result.session?.username || username, loginMethod: 'TOTP Recall', source: result.source, timestamp: new Date().toISOString() }); // Clear sensitive data for security recallTotpPasswordInput.value = ''; recallTotpCodeInput.value = ''; } else { recallResult.innerHTML = ` <strong>❌ TOTP Recall Failed</strong><br> Error: ${result.error}<br> Time: ${new Date().toISOString()} `; } } catch (error) { recallResult.innerHTML = ` <strong>❌ TOTP Recall Error</strong><br> Error: ${error.message}<br> Time: ${new Date().toISOString()} `; } finally { recallTotpBtn.disabled = false; recallTotpBtn.textContent = 'Recall with TOTP'; } }); // Initialize on page load document.addEventListener('DOMContentLoaded', () => { handleSSO(); }); </script> </body> </html>