534 lines
22 KiB
HTML
534 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Session Isolation Test - NOSTR LOGIN LITE</title>
|
|
<style>
|
|
body {
|
|
font-family: 'Courier New', monospace;
|
|
margin: 20px;
|
|
background: #f5f5f5;
|
|
}
|
|
|
|
.container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.status-panel {
|
|
background: #f8f9fa;
|
|
border: 2px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.info-box {
|
|
background: #e7f3ff;
|
|
border: 2px solid #0066cc;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.isolated-notice {
|
|
background: #f8d7da;
|
|
border: 2px solid #dc3545;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
button {
|
|
background: white;
|
|
color: black;
|
|
border: 2px solid black;
|
|
border-radius: 8px;
|
|
padding: 10px 20px;
|
|
margin: 5px;
|
|
cursor: pointer;
|
|
font-family: 'Courier New', monospace;
|
|
font-weight: bold;
|
|
}
|
|
|
|
button:hover {
|
|
border-color: red;
|
|
}
|
|
|
|
button:active {
|
|
background: red;
|
|
color: white;
|
|
}
|
|
|
|
.mode-indicator {
|
|
font-weight: bold;
|
|
padding: 8px 16px;
|
|
border-radius: 4px;
|
|
border: 2px solid #dc3545;
|
|
background: #f8d7da;
|
|
display: inline-block;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
pre {
|
|
background: #f8f9fa;
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
overflow-x: auto;
|
|
font-size: 12px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<h1>🔐 Session Isolation Test</h1>
|
|
|
|
<div class="info-box">
|
|
<h3>📋 Test Instructions</h3>
|
|
<ol>
|
|
<li><strong>Isolated Session:</strong> Each tab/window has independent authentication</li>
|
|
<li>Login in this tab/window - it will persist on refresh</li>
|
|
<li>Open new windows/tabs - they will start unauthenticated</li>
|
|
<li>Login with different users in different windows simultaneously</li>
|
|
<li>Refresh any window - authentication persists within that window only</li>
|
|
</ol>
|
|
</div>
|
|
|
|
<div class="mode-indicator">
|
|
🔒 ISOLATED MODE (sessionStorage)
|
|
</div>
|
|
|
|
<div class="isolated-notice">
|
|
<strong>🚨 Session Isolation Active:</strong>
|
|
<p>This tab uses sessionStorage - authentication is isolated to this window only. Refreshing will maintain your login state, but other tabs/windows are independent.</p>
|
|
</div>
|
|
|
|
<div class="status-panel">
|
|
<h3>Authentication Status</h3>
|
|
<div id="auth-status">Not authenticated</div>
|
|
<div id="auth-details"></div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3>Actions</h3>
|
|
<button onclick="login()">Login</button>
|
|
<button onclick="logout()">Logout</button>
|
|
<button onclick="checkStatus()">Check Status</button>
|
|
<button onclick="testSigning()">Test Signing</button>
|
|
<button onclick="openNewWindow()">Open New Window</button>
|
|
<button onclick="debugAuthentication()" style="border-color: orange;">Debug Auth State</button>
|
|
</div>
|
|
|
|
<div>
|
|
<h3>Storage Inspector</h3>
|
|
<button onclick="inspectStorage()">Inspect SessionStorage</button>
|
|
<button onclick="clearStorage()">Clear Session Storage</button>
|
|
<div id="storage-content"></div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3>Test Results</h3>
|
|
<div id="results"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="../lite/nostr.bundle.js"></script>
|
|
<script src="../lite/nostr-lite.js"></script>
|
|
|
|
<script>
|
|
let nostrLiteInstance = null;
|
|
|
|
// Initialize in isolated mode (always)
|
|
initializeIsolatedMode();
|
|
|
|
async function initializeIsolatedMode() {
|
|
try {
|
|
console.log('Initializing NOSTR_LOGIN_LITE in ISOLATED mode...');
|
|
|
|
nostrLiteInstance = await window.NOSTR_LOGIN_LITE.init({
|
|
theme: 'default',
|
|
persistence: true,
|
|
isolateSession: true, // Always isolated - each tab/window independent
|
|
methods: {
|
|
extension: true,
|
|
local: true,
|
|
readonly: true,
|
|
connect: true,
|
|
otp: true
|
|
},
|
|
floatingTab: {
|
|
enabled: true,
|
|
hPosition: 0.95,
|
|
vPosition: 0.1,
|
|
appearance: {
|
|
style: 'pill',
|
|
icon: '🔒',
|
|
text: 'ISOLATED',
|
|
iconOnly: false
|
|
},
|
|
behavior: {
|
|
hideWhenAuthenticated: false,
|
|
showUserInfo: true,
|
|
autoSlide: true
|
|
}
|
|
}
|
|
});
|
|
|
|
checkStatus();
|
|
|
|
console.log('NOSTR_LOGIN_LITE initialized successfully in ISOLATED mode');
|
|
console.log('Authentication will persist on refresh within this tab only');
|
|
|
|
} catch (error) {
|
|
console.error('Failed to initialize NOSTR_LOGIN_LITE:', error);
|
|
document.getElementById('results').innerHTML =
|
|
`<div style="color: red;">Initialization Error: ${error.message}</div>`;
|
|
}
|
|
}
|
|
|
|
function login() {
|
|
window.NOSTR_LOGIN_LITE.launch('login');
|
|
}
|
|
|
|
function logout() {
|
|
window.NOSTR_LOGIN_LITE.logout();
|
|
setTimeout(checkStatus, 100);
|
|
}
|
|
|
|
function debugAuthentication() {
|
|
console.log('=== AUTHENTICATION DEBUG ===');
|
|
|
|
// Check global storage-based authentication state (SINGLE SOURCE OF TRUTH)
|
|
const authState = window.NOSTR_LOGIN_LITE.getAuthState();
|
|
console.log('🔍 GLOBAL getAuthState():', authState);
|
|
console.log('🔍 Derived isAuthenticated():', !!authState);
|
|
console.log('🔍 Derived getUserInfo():', authState);
|
|
|
|
// Check window.nostr (should sync with global state)
|
|
console.log('window.nostr exists:', !!window.nostr);
|
|
console.log('window.nostr constructor:', window.nostr?.constructor?.name);
|
|
console.log('window.nostr.authState (getter):', window.nostr?.authState);
|
|
|
|
// Check NOSTR_LOGIN_LITE instance
|
|
const instance = window.NOSTR_LOGIN_LITE?._instance;
|
|
console.log('NOSTR_LOGIN_LITE instance exists:', !!instance);
|
|
console.log('Instance hasExtension:', instance?.hasExtension);
|
|
console.log('Instance facadeInstalled:', instance?.facadeInstalled);
|
|
|
|
// Check floating tab state (now queries global getAuthState() only)
|
|
const floatingTab = instance?.floatingTab;
|
|
console.log('FloatingTab exists:', !!floatingTab);
|
|
if (floatingTab) {
|
|
const tabAuthState = floatingTab._getAuthState();
|
|
console.log('FloatingTab _getAuthState():', tabAuthState);
|
|
console.log('FloatingTab derived authenticated:', !!tabAuthState);
|
|
}
|
|
|
|
// Check session storage directly
|
|
const sessionKeys = [];
|
|
const storageKey = 'nostr_login_lite_auth';
|
|
const sessionAuthData = sessionStorage.getItem(storageKey);
|
|
const localAuthData = localStorage.getItem(storageKey);
|
|
|
|
for (let i = 0; i < sessionStorage.length; i++) {
|
|
const key = sessionStorage.key(i);
|
|
if (key && key.startsWith('nl_')) {
|
|
const value = sessionStorage.getItem(key);
|
|
sessionKeys.push({ key, valueLength: value?.length || 0, hasValue: !!value });
|
|
}
|
|
}
|
|
console.log('SessionStorage nl_ keys:', sessionKeys);
|
|
console.log('SessionStorage auth data:', !!sessionAuthData);
|
|
console.log('LocalStorage auth data:', !!localAuthData);
|
|
|
|
// Display debug results
|
|
let debugHTML = '<h4>🔍 Storage-Based Authentication Debug</h4>';
|
|
debugHTML += '<div style="font-family: monospace; font-size: 12px; background: #f8f9fa; padding: 10px; border-radius: 4px;">';
|
|
debugHTML += `<strong>🎯 GLOBAL getAuthState():</strong> ${!!authState} ${authState ? `(${authState.method})` : ''}<br>`;
|
|
debugHTML += `<strong>🎯 Derived isAuthenticated():</strong> ${!!authState}<br>`;
|
|
debugHTML += `<strong>🎯 Derived getUserInfo():</strong> ${!!authState}<br>`;
|
|
debugHTML += `<strong>window.nostr exists:</strong> ${!!window.nostr} (${window.nostr?.constructor?.name})<br>`;
|
|
debugHTML += `<strong>window.nostr.authState (getter):</strong> ${!!window.nostr?.authState}<br>`;
|
|
debugHTML += `<strong>FloatingTab queries getAuthState():</strong> ${!!floatingTab?._getAuthState()}<br>`;
|
|
debugHTML += `<strong>SessionStorage 'nostr_login_lite_auth':</strong> ${!!sessionAuthData}<br>`;
|
|
debugHTML += `<strong>LocalStorage 'nostr_login_lite_auth':</strong> ${!!localAuthData}<br>`;
|
|
debugHTML += `<strong>Session storage nl_ keys:</strong> ${sessionKeys.length}<br>`;
|
|
debugHTML += `<strong>Instance hasExtension:</strong> ${instance?.hasExtension}<br>`;
|
|
debugHTML += `<strong>Facade installed:</strong> ${instance?.facadeInstalled}<br>`;
|
|
debugHTML += '</div>';
|
|
debugHTML += '<p><strong>Check the browser console for detailed debug output.</strong></p>';
|
|
debugHTML += '<p><strong>NEW Architecture:</strong> Global functions query localStorage/sessionStorage directly as single source of truth</p>';
|
|
|
|
// Check for consistency issues
|
|
const derivedAuth = !!authState;
|
|
const floatingTabAuth = !!floatingTab?._getAuthState();
|
|
|
|
if (floatingTabAuth !== derivedAuth) {
|
|
debugHTML += '<p style="color: red;"><strong>🚨 MISMATCH DETECTED:</strong> FloatingTab and global getAuthState() disagree!</p>';
|
|
debugHTML += '<p>Both should query the same storage - check implementation.</p>';
|
|
} else if (sessionAuthData && !derivedAuth) {
|
|
debugHTML += '<p style="color: orange;"><strong>⚠️ PARSING ISSUE:</strong> Session data exists but getAuthState() returns null!</p>';
|
|
debugHTML += '<p>Check getAuthState() function - it may not be parsing the stored data correctly.</p>';
|
|
} else if (!sessionAuthData && !localAuthData && derivedAuth) {
|
|
debugHTML += '<p style="color: orange;"><strong>⚠️ STORAGE ISSUE:</strong> No storage data but getAuthState() returns data!</p>';
|
|
debugHTML += '<p>getAuthState() may be reading from unexpected sources.</p>';
|
|
}
|
|
|
|
document.getElementById('results').innerHTML = debugHTML;
|
|
}
|
|
|
|
async function checkStatus() {
|
|
try {
|
|
console.log('🔍 Checking authentication status using GLOBAL functions...');
|
|
|
|
// Use the single global storage-based authentication state function
|
|
const authState = window.NOSTR_LOGIN_LITE.getAuthState();
|
|
|
|
console.log('🔍 GLOBAL getAuthState():', authState);
|
|
console.log('🔍 Derived isAuthenticated():', !!authState);
|
|
console.log('🔍 Derived getUserInfo():', authState);
|
|
console.log('🔍 window.nostr:', !!window.nostr);
|
|
console.log('🔍 window.nostr.constructor:', window.nostr?.constructor?.name);
|
|
|
|
// Check storage directly for debugging
|
|
const storageKey = 'nostr_login_lite_auth';
|
|
const sessionAuthData = sessionStorage.getItem(storageKey);
|
|
const localAuthData = localStorage.getItem(storageKey);
|
|
console.log('🔍 sessionStorage auth data:', !!sessionAuthData);
|
|
console.log('🔍 localStorage auth data:', !!localAuthData);
|
|
|
|
if (authState) {
|
|
let pubkey = null;
|
|
try {
|
|
if (window.nostr) {
|
|
pubkey = await window.nostr.getPublicKey();
|
|
} else if (authState.pubkey) {
|
|
pubkey = authState.pubkey;
|
|
}
|
|
} catch (err) {
|
|
console.warn('Could not get pubkey:', err.message);
|
|
pubkey = authState.pubkey;
|
|
}
|
|
|
|
const method = authState.method;
|
|
|
|
console.log('✅ Authentication detected via GLOBAL functions - method:', method, 'pubkey:', pubkey?.slice(0, 8) + '...');
|
|
|
|
document.getElementById('auth-status').innerHTML =
|
|
`<strong style="color: green;">✅ Authenticated (Session Isolated)</strong>`;
|
|
document.getElementById('auth-details').innerHTML =
|
|
`<strong>Method:</strong> ${method}<br>
|
|
<strong>Public Key:</strong> ${pubkey ? `${pubkey.slice(0, 16)}...${pubkey.slice(-8)}` : 'Available in authState'}<br>
|
|
<strong>Storage:</strong> ${sessionAuthData ? 'sessionStorage' : 'localStorage'} (${sessionAuthData ? 'isolated to this tab' : 'shared across tabs'})<br>
|
|
<strong>Persistence:</strong> Survives refresh${sessionAuthData ? ', isolated from other tabs' : ', shared with other tabs'}<br>
|
|
<strong>Debug:</strong> Global getAuthState() returns valid data`;
|
|
} else if (sessionAuthData || localAuthData) {
|
|
// We have storage data but getAuthState() returns null
|
|
console.log('⚠️ Storage data exists but getAuthState() returns null');
|
|
|
|
document.getElementById('auth-status').innerHTML =
|
|
`<strong style="color: orange;">⚠️ Authentication data found but not parsed</strong>`;
|
|
document.getElementById('auth-details').innerHTML =
|
|
`<strong>Storage:</strong> ${sessionAuthData ? 'sessionStorage' : 'localStorage'} has authentication data<br>
|
|
<strong>Issue:</strong> getAuthState() returns null<br>
|
|
<strong>Debug:</strong> Storage data: session=${!!sessionAuthData}, local=${!!localAuthData}<br>
|
|
<strong>Solution:</strong> Check getAuthState() function implementation`;
|
|
} else {
|
|
console.log('❌ No authentication detected via getAuthState()');
|
|
|
|
document.getElementById('auth-status').innerHTML =
|
|
`<strong style="color: red;">❌ Not authenticated</strong>`;
|
|
document.getElementById('auth-details').innerHTML =
|
|
`<strong>Storage:</strong> sessionStorage (isolated to this tab)<br>
|
|
<strong>Status:</strong> Ready for login - will persist on refresh<br>
|
|
<strong>Debug:</strong> getAuthState() returns no authentication data`;
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Error checking status:', error);
|
|
|
|
document.getElementById('auth-status').innerHTML =
|
|
`<strong style="color: orange;">⚠️ Error checking status</strong>`;
|
|
document.getElementById('auth-details').innerHTML =
|
|
`Error: ${error.message}<br>
|
|
<strong>Debug:</strong> Check browser console for details`;
|
|
}
|
|
}
|
|
|
|
async function testSigning() {
|
|
try {
|
|
// Use global authentication state to check if authenticated
|
|
const authState = window.NOSTR_LOGIN_LITE.getAuthState();
|
|
if (!authState) {
|
|
throw new Error('Not authenticated (checked via global getAuthState())');
|
|
}
|
|
|
|
if (!window.nostr) {
|
|
throw new Error('window.nostr not available for signing');
|
|
}
|
|
|
|
const event = {
|
|
kind: 1,
|
|
content: `Test message from ISOLATED session - ${new Date().toISOString()}`,
|
|
tags: [],
|
|
created_at: Math.floor(Date.now() / 1000)
|
|
};
|
|
|
|
const signedEvent = await window.nostr.signEvent(event);
|
|
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>✅ Signing Test Successful (Session Isolated)</h4>
|
|
<p>This signature was created using the storage-based authentication system.</p>
|
|
<p><strong>Authentication Method:</strong> getAuthState() confirmed authentication before signing</p>
|
|
<pre>${JSON.stringify(signedEvent, null, 2)}</pre>`;
|
|
|
|
} catch (error) {
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>❌ Signing Test Failed</h4>
|
|
<p style="color: red;">${error.message}</p>
|
|
<p><strong>Debug Info:</strong></p>
|
|
<ul>
|
|
<li>getAuthState(): ${!!window.NOSTR_LOGIN_LITE.getAuthState()}</li>
|
|
<li>window.nostr exists: ${!!window.nostr}</li>
|
|
<li>Auth method: ${JSON.stringify(window.NOSTR_LOGIN_LITE.getAuthState()?.method || null)}</li>
|
|
</ul>`;
|
|
}
|
|
}
|
|
|
|
function openNewWindow() {
|
|
const newWindow = window.open(
|
|
window.location.href,
|
|
'_blank',
|
|
'width=900,height=700,scrollbars=yes,resizable=yes'
|
|
);
|
|
|
|
if (newWindow) {
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>🪟 New Window Opened - Independent Session</h4>
|
|
<p><strong>Session Isolation Test:</strong></p>
|
|
<ol>
|
|
<li>The new window starts unauthenticated (independent session)</li>
|
|
<li>Login in the new window with a different method or user</li>
|
|
<li>Both windows maintain separate authentication states</li>
|
|
<li>Refresh either window - authentication persists within that window only</li>
|
|
<li>Close a window - its authentication is lost (sessionStorage cleared)</li>
|
|
</ol>
|
|
<p><strong>Expected Behavior:</strong> Each window/tab has completely independent authentication that persists on refresh but doesn't leak to other windows.</p>`;
|
|
} else {
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>❌ Failed to Open Window</h4>
|
|
<p>Please allow popups and try again</p>`;
|
|
}
|
|
}
|
|
|
|
function inspectStorage() {
|
|
const sessionStorage_keys = [];
|
|
|
|
// Inspect sessionStorage (our isolated storage)
|
|
for (let i = 0; i < sessionStorage.length; i++) {
|
|
const key = sessionStorage.key(i);
|
|
if (key && key.startsWith('nl_')) {
|
|
sessionStorage_keys.push({
|
|
key,
|
|
value: sessionStorage.getItem(key)
|
|
});
|
|
}
|
|
}
|
|
|
|
let content = '<h4>📊 Session Storage Inspection</h4>';
|
|
content += '<p><strong>Note:</strong> This tab uses sessionStorage for isolation - data here is independent of other tabs/windows.</p>';
|
|
|
|
content += '<h5>sessionStorage (This tab only):</h5>';
|
|
if (sessionStorage_keys.length === 0) {
|
|
content += '<p style="color: #666;">No authentication data found in this session</p>';
|
|
} else {
|
|
content += '<p style="color: green;">✅ Authentication data found (persists on refresh)</p>';
|
|
content += '<pre>' + JSON.stringify(sessionStorage_keys, null, 2) + '</pre>';
|
|
}
|
|
|
|
// Show what would be in localStorage if we weren't using isolation
|
|
const localStorage_keys = [];
|
|
for (let i = 0; i < localStorage.length; i++) {
|
|
const key = localStorage.key(i);
|
|
if (key && key.startsWith('nl_')) {
|
|
localStorage_keys.push(key);
|
|
}
|
|
}
|
|
|
|
content += '<h5>localStorage (Not used in isolated mode):</h5>';
|
|
if (localStorage_keys.length === 0) {
|
|
content += '<p style="color: #666;">No NOSTR_LOGIN_LITE data (expected in isolated mode)</p>';
|
|
} else {
|
|
content += '<p style="color: orange;">⚠️ Found some data - might be from non-isolated sessions</p>';
|
|
}
|
|
|
|
document.getElementById('storage-content').innerHTML = content;
|
|
}
|
|
|
|
function clearStorage() {
|
|
// Clear only sessionStorage (our isolated storage)
|
|
const sessionKeys = [];
|
|
|
|
for (let i = 0; i < sessionStorage.length; i++) {
|
|
const key = sessionStorage.key(i);
|
|
if (key && key.startsWith('nl_')) {
|
|
sessionKeys.push(key);
|
|
}
|
|
}
|
|
|
|
sessionKeys.forEach(key => sessionStorage.removeItem(key));
|
|
|
|
document.getElementById('storage-content').innerHTML =
|
|
`<h4>🧹 Session Storage Cleared</h4>
|
|
<p>Removed ${sessionKeys.length} authentication items from this tab's sessionStorage</p>
|
|
<p><strong>Result:</strong> This tab is now logged out, but other tabs are unaffected</p>`;
|
|
|
|
// Update status
|
|
setTimeout(checkStatus, 100);
|
|
}
|
|
|
|
// Listen for authentication events
|
|
window.addEventListener('nlMethodSelected', (event) => {
|
|
console.log('Authentication successful in isolated session:', event.detail);
|
|
setTimeout(checkStatus, 100);
|
|
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>✅ Authentication Successful (Session Isolated)</h4>
|
|
<p><strong>Method:</strong> ${event.detail.method}</p>
|
|
<p><strong>Storage:</strong> sessionStorage (isolated to this tab)</p>
|
|
<p><strong>Persistence:</strong> Will survive refresh, won't affect other tabs</p>
|
|
<p><strong>Test:</strong> Open a new tab - it should start unauthenticated</p>`;
|
|
});
|
|
|
|
window.addEventListener('nlLogout', (event) => {
|
|
console.log('Logout detected in isolated session:', event.detail);
|
|
setTimeout(checkStatus, 100);
|
|
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>👋 Logged Out (Session Isolated)</h4>
|
|
<p>Authentication cleared from this tab's sessionStorage only</p>
|
|
<p><strong>Result:</strong> Other tabs remain unaffected by this logout</p>`;
|
|
});
|
|
|
|
// Check status on page load (should restore from sessionStorage if available)
|
|
window.addEventListener('load', () => {
|
|
setTimeout(checkStatus, 500);
|
|
|
|
// Show persistence message if we're restoring authentication
|
|
if (sessionStorage.getItem('nl_auth_state') || sessionStorage.getItem('nl_current')) {
|
|
setTimeout(() => {
|
|
document.getElementById('results').innerHTML =
|
|
`<h4>🔄 Session Restored</h4>
|
|
<p>Authentication state restored from sessionStorage on page load</p>
|
|
<p><strong>Isolation confirmed:</strong> This tab's login state is independent</p>`;
|
|
}, 1000);
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |