gunauth
Version:
Minimal identity provider using GUN and SEA
991 lines (855 loc) • 44 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>App 1 - Secure GunAuth Demo</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 20px auto;
padding: 20px;
background: #f0f8ff;
}
.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;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
background-color: #007bff;
color: white;
padding: 12px 24px;
border: none;
border-radius: 4px;
cursor: pointer;
margin: 5px;
}
button:hover { background-color: #0056b3; }
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 1 - Secure GunAuth Demo</h1>
<p><strong>Port:</strong> 8001 | <strong>Domain:</strong> localhost:8001</p>
<div id="status" class="status unauthenticated">
🔒 Not authenticated
</div>
<!-- SSO Method -->
<div 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>
<button id="ssoCheckBtn">Check SSO Status</button>
<button id="debugBtn" style="background-color: #ffc107; color: black;">Debug localStorage</button>
</div>
<!-- Direct Registration/Login -->
<div id="directAuthSection">
<h3>🔐 Direct Authentication</h3>
<p><em>Register and login directly with secure client-side key storage</em></p>
<!-- Registration Form -->
<div id="registerSection">
<h3>Register New User</h3>
<div class="form-group">
<input type="text" id="regUsername" placeholder="Username (3-30 chars, letters/numbers/_/- only)" required>
</div>
<div class="form-group">
<input type="password" id="regPassword" placeholder="Password (min 8 chars, need: A-Z, a-z, 0-9)" required>
<div class="validation-help">
<small style="color: #666;">Password requirements: 8-128 characters, at least one uppercase, lowercase, and number</small>
</div>
</div>
<button id="registerBtn">Register</button>
</div>
<!-- Login Form -->
<div id="loginSection">
<h3>Login</h3>
<div class="form-group">
<input type="text" id="loginUsername" placeholder="Username" required>
</div>
<div class="form-group">
<input type="password" id="loginPassword" placeholder="Password" required>
</div>
<button id="loginBtn">Login</button>
</div>
<!-- Gun.user.recall() Integration -->
<div id="recallSection">
<h3>🔄 gun.user.recall() Integration</h3>
<p><em>Gun.js compatible session restoration with automatic detection</em></p>
<div class="form-group">
<button id="recallAutoBtn" style="background-color: #28a745;">Auto Recall Session</button>
<div style="font-size: 12px; color: #666; margin-top: 5px;">Attempts to restore session from storage, SSO, or TOTP automatically</div>
</div>
<div class="form-group">
<input type="text" id="recallUsername" placeholder="Username (for login-based recall)" required>
<input type="password" id="recallPassword" placeholder="Password (for login-based recall)" required>
<button id="recallLoginBtn" style="background-color: #6c757d;">Recall with Login</button>
<div style="font-size: 12px; color: #666; margin-top: 5px;">Force recall using login credentials</div>
</div>
<div class="form-group">
<input type="text" id="recallTotpUsername" placeholder="Username (for TOTP recall)" required>
<input type="password" id="recallTotpPassword" placeholder="Password (for TOTP recall)" required>
<input type="text" id="recallTotpCode" placeholder="Current TOTP Code" maxlength="6" required>
<button id="recallTotpBtn" style="background-color: #6f42c1;">Recall with TOTP</button>
<div style="font-size: 12px; color: #666; margin-top: 5px;">Recall using TOTP-encrypted session</div>
</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>
<!-- Session Management -->
<div id="sessionSection" style="display:none;">
<h3>Session Management</h3>
<button id="logoutBtn">Logout</button>
<button id="clearDataBtn" style="background-color: #dc3545;">Clear All Data</button>
</div>
<!-- Session Info Display -->
<div class="session-info" id="sessionInfo">
No session data
</div>
<!-- Cross-domain test -->
<div id="crossDomainTest">
<h3>🌐 Cross-Domain Session Sharing</h3>
<p><em>Share sessions across domains using encrypted user credentials or TOTP</em></p>
<!-- TOTP Setup Section -->
<div class="form-group" style="border: 1px solid #ddd; padding: 10px; border-radius: 4px; margin-bottom: 10px;">
<h4>🔐 TOTP (Automatically Configured)</h4>
<p style="font-size: 12px; color: #666; margin: 5px 0;">TOTP is automatically set up when you login. Use the current code for cross-domain authentication.</p>
<button id="setupTOTPBtn" style="background-color: #6f42c1; margin-bottom: 10px;">Show TOTP QR Code</button>
<div id="totpCurrentCode" style="display:none; background:#e8f5e8; padding:15px; border-radius:4px; margin:10px 0; text-align:center;">
<h4 style="margin:0 0 10px 0; color:#2d5016;">🕐 Current TOTP Code</h4>
<div style="font-size:24px; font-weight:bold; font-family:monospace; color:#2d5016;" id="currentTOTPDisplay">------</div>
<div style="font-size:12px; color:#666; margin-top:5px;">Updates every 30 seconds • Use this code on other domains</div>
</div>
<div id="totpSetupInfo" style="display:none; background:#f8f9fa; padding:10px; border-radius:4px; font-family:monospace; font-size:12px;"></div>
</div>
<!-- Standard Session Sharing -->
<div class="form-group">
<h4>📤 Standard Session Sharing</h4>
<input type="text" id="shareUsername" placeholder="Username for session sharing" required>
<input type="password" id="sharePassword" placeholder="Password for session sharing" required>
<button id="shareSessionBtn" style="background-color: #17a2b8;">Share Session</button>
</div>
<!-- TOTP-Enhanced Session Sharing -->
<div class="form-group" style="border: 1px solid #28a745; padding: 10px; border-radius: 4px;">
<h4>🔒 TOTP-Encrypted Session Sharing</h4>
<input type="text" id="totpUsername" placeholder="Username" required>
<input type="password" id="totpPassword" placeholder="Password" required>
<input type="text" id="totpCode" placeholder="6-digit TOTP code" maxlength="6" pattern="[0-9]{6}" required>
<button id="shareTOTPBtn" style="background-color: #28a745;">Share with TOTP</button>
</div>
<!-- Load Sessions -->
<div class="form-group">
<h4>📥 Load Shared Sessions</h4>
<button id="checkSharedBtn" style="background-color: #28a745;">Load Standard Session</button>
<button id="loadTOTPBtn" style="background-color: #dc3545;">Load TOTP Session</button>
<button id="clearSharedBtn" style="background-color: #ffc107; color: black;">Clear Sessions</button>
</div>
<p><small>Test cross-domain: <a href="http://localhost:8002" target="_blank">App 2 (Port 8002)</a></small></p>
<p id="sessionSharingInfo" style="display:none; background:#e9ecef; padding:10px; border-radius:4px;"></p>
</div>
</div>
<!-- Include Gun and GunAuth Client -->
<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>
// Initialize GunAuth Client
const auth = new GunAuthClient('http://localhost:8000');
// DOM elements
const statusDiv = document.getElementById('status');
const sessionInfo = document.getElementById('sessionInfo');
const registerSection = document.getElementById('registerSection');
const loginSection = document.getElementById('loginSection');
const sessionSection = document.getElementById('sessionSection');
const regUsernameInput = document.getElementById('regUsername');
const regPasswordInput = document.getElementById('regPassword');
const registerBtn = document.getElementById('registerBtn');
const loginUsernameInput = document.getElementById('loginUsername');
const loginPasswordInput = document.getElementById('loginPassword');
const loginBtn = document.getElementById('loginBtn');
const logoutBtn = document.getElementById('logoutBtn');
const clearDataBtn = document.getElementById('clearDataBtn');
const ssoLoginBtn = document.getElementById('ssoLoginBtn');
const ssoCheckBtn = document.getElementById('ssoCheckBtn');
const debugBtn = document.getElementById('debugBtn');
// Cross-domain session sharing elements
const shareUsernameInput = document.getElementById('shareUsername');
const sharePasswordInput = document.getElementById('sharePassword');
const shareSessionBtn = document.getElementById('shareSessionBtn');
const checkSharedBtn = document.getElementById('checkSharedBtn');
const clearSharedBtn = document.getElementById('clearSharedBtn');
const sessionSharingInfo = document.getElementById('sessionSharingInfo');
// TOTP elements
const setupTOTPBtn = document.getElementById('setupTOTPBtn');
const totpSetupInfo = document.getElementById('totpSetupInfo');
const totpUsernameInput = document.getElementById('totpUsername');
const totpPasswordInput = document.getElementById('totpPassword');
const totpCodeInput = document.getElementById('totpCode');
const shareTOTPBtn = document.getElementById('shareTOTPBtn');
const loadTOTPBtn = document.getElementById('loadTOTPBtn');
// TOTP display elements
const totpCurrentCode = document.getElementById('totpCurrentCode');
const currentTOTPDisplay = document.getElementById('currentTOTPDisplay');
let totpUpdateInterval = null;
// Gun.user.recall() elements
const recallAutoBtn = document.getElementById('recallAutoBtn');
const recallLoginBtn = document.getElementById('recallLoginBtn');
const recallTotpBtn = document.getElementById('recallTotpBtn');
const recallUsernameInput = document.getElementById('recallUsername');
const recallPasswordInput = document.getElementById('recallPassword');
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');
statusDiv.textContent = 'Processing SSO login...';
const result = await auth.handleSSOCallback();
if (result.success) {
updateUI();
console.log('✅ SSO login successful:', result);
// Automatically create TOTP session vault for cross-domain access
// Use the session from the callback result
const session = result.session;
if (session && session.username) {
console.log('🔄 Creating TOTP session vault for cross-domain access...');
try {
const vaultResult = await auth.shareTOTPSessionVault(session.username, session.token);
if (vaultResult.success) {
console.log('✅ TOTP session vault created automatically');
console.log('🔑 Current TOTP for immediate use:', vaultResult.currentTOTP);
} else {
console.warn('⚠️ Failed to create TOTP vault:', vaultResult.error);
}
} catch (error) {
console.warn('⚠️ TOTP vault creation error:', error.message);
}
}
} else {
console.error('❌ SSO login failed:', result.error);
statusDiv.textContent = '❌ SSO login failed: ' + result.error;
}
}
}
// Update UI based on authentication status
function updateUI() {
const isAuth = auth.isAuthenticated();
const session = auth.getSession();
if (isAuth && session) {
statusDiv.className = 'status authenticated';
statusDiv.textContent = `✅ Authenticated as ${session.username}`;
registerSection.style.display = 'none';
loginSection.style.display = 'none';
sessionSection.style.display = 'block';
sessionInfo.textContent = JSON.stringify({
username: session.username,
pub: session.pub,
exp: new Date(session.exp).toISOString(),
loginTime: new Date(session.loginTime).toISOString(),
timeUntilExpiry: Math.round((session.exp - Date.now()) / 1000) + ' seconds'
}, null, 2);
// Start TOTP display for authenticated users
startTOTPDisplay();
} else {
statusDiv.className = 'status unauthenticated';
statusDiv.textContent = '🔒 Not authenticated';
registerSection.style.display = 'block';
loginSection.style.display = 'block';
sessionSection.style.display = 'none';
sessionInfo.textContent = 'No session data';
// Stop TOTP display
stopTOTPDisplay();
}
}
// TOTP Display Functions
async function startTOTPDisplay() {
const session = auth.getSession();
console.log('🔄 Starting TOTP display, session:', session);
if (!session || !session.username) {
console.warn('⚠️ Cannot start TOTP display - no session or username');
return;
}
// Show the TOTP display area
totpCurrentCode.style.display = 'block';
console.log('✅ TOTP display area made visible');
// Update TOTP code immediately
console.log('🔄 Initial TOTP update...');
await updateTOTPDisplay();
// Clear any existing interval
if (totpUpdateInterval) {
clearInterval(totpUpdateInterval);
}
// Update every 30 seconds
console.log('🔄 Setting up 30-second TOTP update interval');
totpUpdateInterval = setInterval(() => {
console.log('🔄 30-second TOTP update triggered');
updateTOTPDisplay();
}, 30000);
}
function stopTOTPDisplay() {
totpCurrentCode.style.display = 'none';
if (totpUpdateInterval) {
clearInterval(totpUpdateInterval);
totpUpdateInterval = null;
}
}
async function updateTOTPDisplay() {
try {
const session = auth.getSession();
if (!session) return;
let currentCode = '------';
if (session.ssoLogin) {
// For SSO users, get TOTP from session token
const totpCode = await auth.getCurrentTOTPForSSO(session.username, session.token);
if (totpCode) {
currentCode = totpCode;
console.log('🔑 TOTP Display updated:', currentCode);
} else {
console.warn('⚠️ No TOTP code returned');
}
} else {
// For regular users, we'll need the password - we'll handle this case
// For now, show a message to scan QR code with authenticator app
currentCode = 'Use App';
}
currentTOTPDisplay.textContent = currentCode;
} catch (error) {
console.error('Error updating TOTP display:', error);
currentTOTPDisplay.textContent = '------';
}
}
// Register new user
registerBtn.addEventListener('click', async () => {
const username = regUsernameInput.value.trim();
const password = regPasswordInput.value;
if (!username || !password) {
alert('Please enter username and password');
return;
}
// Client-side validation to match server requirements
if (username.length < 3 || username.length > 30) {
alert('Username must be between 3 and 30 characters');
return;
}
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
alert('Username can only contain letters, numbers, hyphens, and underscores');
return;
}
if (password.length < 8 || password.length > 128) {
alert('Password must be between 8 and 128 characters');
return;
}
if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(password)) {
alert('Password must contain at least one lowercase letter, one uppercase letter, and one number');
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) {
alert('Registration error: ' + error.message);
} finally {
registerBtn.disabled = false;
registerBtn.textContent = 'Register';
}
});
// Login
loginBtn.addEventListener('click', async () => {
const username = loginUsernameInput.value.trim();
const password = loginPasswordInput.value;
if (!username || !password) {
alert('Please enter username and password');
return;
}
loginBtn.disabled = true;
loginBtn.textContent = 'Logging in...';
try {
const result = await auth.login(username, password);
if (result.success) {
loginUsernameInput.value = '';
loginPasswordInput.value = '';
updateUI();
alert('✅ Login successful!');
} else {
alert('Login failed: ' + result.error);
}
} catch (error) {
alert('Login error: ' + error.message);
} finally {
loginBtn.disabled = false;
loginBtn.textContent = 'Login';
}
});
// Logout
logoutBtn.addEventListener('click', async () => {
await auth.logout();
updateUI();
});
// Clear all data
clearDataBtn.addEventListener('click', () => {
if (confirm('This will clear all stored authentication data. Are you sure?')) {
auth.clearAllData();
updateUI();
}
});
// SSO Login
ssoLoginBtn.addEventListener('click', () => {
auth.ssoLogin({
redirectUri: window.location.origin + window.location.pathname,
clientId: 'app1-secure'
});
});
// SSO Status Check
ssoCheckBtn.addEventListener('click', () => {
const session = auth.getSession();
if (session && session.ssoLogin) {
alert('SSO session active: ' + JSON.stringify({
username: session.username || 'SSO User',
expires: new Date(session.exp).toISOString()
}, null, 2));
} else if (session) {
alert('Direct login session active (not SSO)');
} else {
alert('No active session');
}
});
// Debug localStorage
debugBtn.addEventListener('click', () => {
const items = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = localStorage.getItem(key);
}
const urlParams = new URLSearchParams(window.location.search);
const debugInfo = {
localStorage: items,
urlParams: {
code: urlParams.get('code'),
state: urlParams.get('state')
},
currentURL: window.location.href,
localSharing: auth.enableLocalSharing
};
console.log('🔍 Debug Info:', debugInfo);
alert('Debug info logged to console:\n' + JSON.stringify(debugInfo, null, 2));
});
// 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();
} 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';
}
});
// Login-based recall
recallLoginBtn.addEventListener('click', async () => {
const username = recallUsernameInput.value.trim();
const password = recallPasswordInput.value;
if (!username || !password) {
alert('Please enter both username and password for recall');
return;
}
recallLoginBtn.disabled = true;
recallLoginBtn.textContent = 'Recalling...';
recallResult.style.display = 'block';
recallResult.textContent = 'Attempting login-based recall...';
try {
const result = await auth.user.recall({ username, password });
if (result.success) {
recallResult.innerHTML = `
<strong>✅ Login 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();
// Clear password for security
recallPasswordInput.value = '';
} else {
recallResult.innerHTML = `
<strong>❌ Login Recall Failed</strong><br>
Error: ${result.error}<br>
Time: ${new Date().toISOString()}
`;
}
} catch (error) {
recallResult.innerHTML = `
<strong>❌ Login Recall Error</strong><br>
Error: ${error.message}<br>
Time: ${new Date().toISOString()}
`;
} finally {
recallLoginBtn.disabled = false;
recallLoginBtn.textContent = 'Recall with Login';
}
});
// 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();
// 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';
}
});
// Setup TOTP for current user
setupTOTPBtn.addEventListener('click', async () => {
const session = auth.getSession();
// For SSO users, use session data (already set up automatically)
if (session && session.ssoLogin && session.username) {
setupTOTPBtn.disabled = true;
setupTOTPBtn.textContent = 'Loading...';
try {
// For SSO users, just display the already configured TOTP
// We'll call a method that retrieves the existing TOTP secret
const result = await auth.displayTOTPForSSO(session.username, session.token);
if (result.success) {
totpSetupInfo.style.display = 'block';
totpSetupInfo.innerHTML = `
<strong>✅ TOTP Already Configured (SSO User)!</strong><br>
<strong>QR Code URL:</strong><br>
<a href="${result.qrURL}" target="_blank" style="word-break: break-all; font-size: 10px;">${result.qrURL}</a><br><br>
<strong>Manual Entry:</strong><br>
<code>${result.secret}</code><br><br>
<em>Scan the QR URL with Google Authenticator, Authy, or similar app</em>
`;
alert('✅ TOTP QR code displayed! This was automatically configured when you logged in.');
} else {
alert(`❌ TOTP display failed: ${result.error}`);
}
} catch (error) {
alert('Error displaying TOTP: ' + error.message);
} finally {
setupTOTPBtn.disabled = false;
setupTOTPBtn.textContent = 'Show TOTP QR Code';
}
return;
}
// For regular users, use form inputs
const username = loginUsernameInput.value.trim() || shareUsernameInput.value.trim();
const password = loginPasswordInput.value.trim() || sharePasswordInput.value.trim();
if (!username || !password) {
alert('Please enter username and password first (either in login form or session sharing form)');
return;
}
setupTOTPBtn.disabled = true;
setupTOTPBtn.textContent = 'Loading...';
try {
const result = await auth.setupTOTP(username, password);
if (result.success) {
totpSetupInfo.style.display = 'block';
totpSetupInfo.innerHTML = `
<strong>✅ TOTP Already Configured!</strong><br>
<strong>QR Code URL:</strong><br>
<a href="${result.qrURL}" target="_blank" style="word-break: break-all; font-size: 10px;">${result.qrURL}</a><br><br>
<strong>Manual Entry:</strong><br>
<code>${result.secret}</code><br><br>
<em>Scan the QR URL with Google Authenticator, Authy, or similar app</em>
`;
alert('✅ TOTP QR code displayed! This was automatically configured when you logged in.');
} else {
alert(`❌ TOTP display failed: ${result.error}`);
}
} catch (error) {
alert('Error displaying TOTP: ' + error.message);
} finally {
setupTOTPBtn.disabled = false;
setupTOTPBtn.textContent = 'Show TOTP QR Code';
}
});
// Share session with TOTP encryption
shareTOTPBtn.addEventListener('click', async () => {
const session = auth.getSession();
let username, password;
// For SSO users, use session data
if (session && session.ssoLogin && session.username) {
username = session.username;
password = session.token; // Use session token as password for SSO users
// Still need TOTP code
const totpCode = totpCodeInput.value.trim();
if (!totpCode) {
alert('Please enter TOTP code from your authenticator app');
return;
}
if (!/^\d{6}$/.test(totpCode)) {
alert('TOTP code must be exactly 6 digits');
return;
}
} else {
// For regular users, use form inputs
username = totpUsernameInput.value.trim();
password = totpPasswordInput.value.trim();
const totpCode = totpCodeInput.value.trim();
if (!username || !password || !totpCode) {
alert('Please enter username, password, and TOTP code');
return;
}
if (!/^\d{6}$/.test(totpCode)) {
alert('TOTP code must be exactly 6 digits');
return;
}
}
if (!auth.isAuthenticated()) {
alert('You must be logged in to share a session');
return;
}
shareTOTPBtn.disabled = true;
shareTOTPBtn.textContent = 'Sharing...';
try {
const totpCode = totpCodeInput.value.trim(); // Get TOTP code
const result = await auth.shareSessionWithTOTP(username, password, totpCode);
if (result.success) {
sessionSharingInfo.style.display = 'block';
const userType = session && session.ssoLogin ? ' (SSO User)' : '';
sessionSharingInfo.innerHTML = `✅ ${result.message}${userType}<br><small>🔐 Session encrypted with TOTP code. Auto-expires in ${result.totpWindow}.</small>`;
// Only clear form inputs for non-SSO users
if (!session?.ssoLogin) {
totpUsernameInput.value = '';
totpPasswordInput.value = '';
}
totpCodeInput.value = '';
alert('✅ Session shared with TOTP encryption!');
} else {
alert(`❌ ${result.error}`);
}
} catch (error) {
alert('Error sharing TOTP session: ' + error.message);
} finally {
shareTOTPBtn.disabled = false;
shareTOTPBtn.textContent = 'Share with TOTP';
}
});
// Load TOTP-encrypted session
loadTOTPBtn.addEventListener('click', async () => {
const username = totpUsernameInput.value.trim() || shareUsernameInput.value.trim();
const password = totpPasswordInput.value.trim() || sharePasswordInput.value.trim();
const totpCode = totpCodeInput.value.trim();
if (!username || !password || !totpCode) {
alert('Please enter username, password, and TOTP code');
return;
}
if (!/^\d{6}$/.test(totpCode)) {
alert('TOTP code must be exactly 6 digits');
return;
}
loadTOTPBtn.disabled = true;
loadTOTPBtn.textContent = 'Loading...';
try {
const result = await auth.loadTOTPSession(username, password, totpCode);
if (result.success) {
sessionSharingInfo.style.display = 'block';
sessionSharingInfo.innerHTML = `✅ ${result.message}<br><small>🔐 Decrypted with ${result.decryptedWith} TOTP code</small>`;
updateUI();
alert(`✅ TOTP session loaded for ${result.session.username}!`);
} else {
alert(`❌ ${result.error}`);
}
} catch (error) {
alert('Error loading TOTP session: ' + error.message);
} finally {
loadTOTPBtn.disabled = false;
loadTOTPBtn.textContent = 'Load TOTP Session';
}
});
// Share current session across domains
shareSessionBtn.addEventListener('click', async () => {
const username = shareUsernameInput.value.trim();
const password = sharePasswordInput.value.trim();
if (!username || !password) {
alert('Please enter both username and password for session sharing');
return;
}
if (!auth.isAuthenticated()) {
alert('You must be logged in to share a session');
return;
}
shareSessionBtn.disabled = true;
shareSessionBtn.textContent = 'Sharing...';
try {
const result = await auth.shareSessionLocally(username, password);
if (result.success) {
sessionSharingInfo.style.display = 'block';
sessionSharingInfo.innerHTML = `✅ ${result.message}<br><small>Session encrypted and stored for user: ${username}</small>`;
shareUsernameInput.value = '';
sharePasswordInput.value = '';
} else {
alert(`❌ ${result.error}`);
}
} catch (error) {
alert('Error sharing session: ' + error.message);
} finally {
shareSessionBtn.disabled = false;
shareSessionBtn.textContent = 'Share Session';
}
});
// Load shared cross-domain session
checkSharedBtn.addEventListener('click', async () => {
const username = shareUsernameInput.value.trim();
const password = sharePasswordInput.value.trim();
if (!username || !password) {
alert('Please enter both username and password to load shared session');
return;
}
checkSharedBtn.disabled = true;
checkSharedBtn.textContent = 'Loading...';
try {
const result = await auth.loadSharedSession(username, password);
if (result.success) {
alert(`✅ ${result.message}`);
shareUsernameInput.value = '';
sharePasswordInput.value = '';
sessionSharingInfo.style.display = 'block';
sessionSharingInfo.innerHTML = `✅ Session loaded for user: ${result.session.username}`;
updateUI();
} else {
alert(`❌ ${result.error}`);
}
} catch (error) {
alert('Error loading shared session: ' + error.message);
} finally {
checkSharedBtn.disabled = false;
checkSharedBtn.textContent = 'Load Shared Session';
}
});
// Clear shared session
clearSharedBtn.addEventListener('click', async () => {
const username = shareUsernameInput.value.trim();
const password = sharePasswordInput.value.trim();
if (!username || !password) {
alert('Please enter both username and password to clear shared session');
return;
}
if (confirm(`Clear shared session for user: ${username}?`)) {
try {
const result = await auth.clearSharedSession(username, password);
if (result.success) {
alert(`✅ ${result.message}`);
sessionSharingInfo.style.display = 'block';
sessionSharingInfo.innerHTML = `✅ Shared session cleared for user: ${username}`;
shareUsernameInput.value = '';
sharePasswordInput.value = '';
} else {
alert(`❌ ${result.error}`);
}
} catch (error) {
alert('Error clearing shared session: ' + error.message);
}
}
});
// Handle SSO callback on page load
handleSSO();
// Initialize UI
updateUI();
console.log('🚀 App 1 initialized with secure GunAuth client');
</script>
</body>
</html>