# NOSTR_LOGIN_LITE - Login Logic Analysis This document explains the complete login and authentication flow for the NOSTR_LOGIN_LITE library, including how state is maintained upon page refresh. ## System Architecture Overview The library uses a **modular authentication architecture** with these key components: 1. **FloatingTab** - UI component for login trigger and status display 2. **Modal** - UI component for authentication method selection 3. **NostrLite** - Main library coordinator and facade manager 4. **WindowNostr** - NIP-07 compliant facade for non-extension methods 5. **AuthManager** - Persistent state management with encryption 6. **Extension Bridge** - Browser extension detection and management ## Authentication Flow Diagrams ### Initial Page Load Flow ``` ┌─────────────────────┐ │ Page Loads │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ NOSTR_LOGIN_LITE │ │ .init() called │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ YES ┌─────────────────────┐ │ Real extension │──────────▶│ Extension-First │ │ detected? │ │ Mode: Don't install │ └─────────┬───────────┘ │ facade │ │ NO └─────────────────────┘ ▼ ┌─────────────────────┐ │ Install WindowNostr │ │ facade for local/ │ │ NIP-46/readonly │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ YES ┌─────────────────────┐ │ Persistence │──────────▶│ _attemptAuthRestore │ │ enabled? │ │ called │ └─────────┬───────────┘ └─────────┬───────────┘ │ NO │ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ Initialization │ │ Check storage for │ │ complete │ │ saved auth state │ └─────────────────────┘ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ YES │ Valid auth state │────────┐ │ found? │ │ └─────────┬───────────┘ │ │ NO │ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ Show login UI │ │ Restore auth & │ │ (FloatingTab,etc) │ │ dispatch events │ └─────────────────────┘ └─────────────────────┘ ``` ### User-Initiated Login Flow ``` ┌─────────────────────┐ ┌─────────────────────┐ │ User clicks │ │ User clicks │ │ FloatingTab │ │ Login Button │ └─────────┬───────────┘ └─────────┬───────────┘ │ │ ▼ ▼ ┌─────────────────────┐ │ │ Extension │ │ │ available? │ │ └─────────┬───────────┘ │ │ YES │ ▼ │ ┌─────────────────────┐ │ │ Auto-try extension │ │ │ authentication │ │ └─────────┬───────────┘ │ │ SUCCESS │ ▼ │ ┌─────────────────────┐ │ │ Authentication │ │ │ complete │◀──────────────────┘ └─────────────────────┘ │ FAIL OR ALWAYS ▼ ┌─────────────────────┐ │ Open Modal with │ │ method selection: │ │ • Extension │ │ • Local Key │ │ • NIP-46 Connect │ │ • Read-only │ │ • OTP/DM │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ User selects method │ │ and completes auth │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ Authentication │ │ complete │ └─────────────────────┘ ``` ### Authentication Storage & Persistence Flow ``` ┌─────────────────────┐ │ Authentication │ │ successful │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ nlMethodSelected │ │ event dispatched │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ Extension? ┌─────────────────────┐ │ AuthManager. │─────────────────▶│ Store verification │ │ saveAuthState() │ │ data only (no │ └─────────┬───────────┘ │ secrets) │ │ Local Key? └─────────────────────┘ ▼ ┌─────────────────────┐ │ Encrypt secret key │ │ with session │ │ password + AES-GCM │ └─────────┬───────────┘ │ NIP-46? ▼ ┌─────────────────────┐ │ Store connection │ │ parameters (no │ │ secrets) │ └─────────┬───────────┘ │ Read-only? ▼ ┌─────────────────────┐ │ Store method only │ │ (no secrets) │ └─────────┬───────────┘ │ ▼ ┌─────────────────────┐ isolateSession? ┌─────────────────────┐ │ Choose storage: │─────────YES─────────▶│ sessionStorage │ │ localStorage vs │ │ (per-window) │ │ sessionStorage │◀────────NO───────────┤ │ └─────────┬───────────┘ └─────────────────────┘ │ ▼ ┌─────────────────────┐ │ localStorage │ │ (cross-window) │ └─────────────────────┘ ``` ## Key Decision Points and Logic ### 1. Extension Detection Logic (Line 994-1046) **Function:** `NostrLite._isRealExtension(obj)` ```javascript // Conservative extension detection if (!obj || typeof obj !== 'object') return false; if (typeof obj.getPublicKey !== 'function' || typeof obj.signEvent !== 'function') return false; // Exclude our own classes const constructorName = obj.constructor?.name; if (constructorName === 'WindowNostr' || constructorName === 'NostrLite') return false; if (obj === window.NostrTools) return false; // Look for extension indicators const extensionIndicators = [ '_isEnabled', 'enabled', 'kind', '_eventEmitter', '_scope', '_requests', '_pubkey', 'name', 'version', 'description' ]; const hasIndicators = extensionIndicators.some(prop => obj.hasOwnProperty(prop)); const hasExtensionConstructor = constructorName && constructorName !== 'Object' && constructorName !== 'Function'; return hasIndicators || hasExtensionConstructor; ``` ### 2. Facade Installation Decision (Line 942-972) **Function:** `NostrLite._setupWindowNostrFacade()` ``` Extension detected? ──YES──▶ DON'T install facade Store reference for persistence │ NO ▼ Install WindowNostr facade ──▶ Handle local/NIP-46/readonly methods ``` ### 3. FloatingTab Click Behavior (Line 351-369) **Current UX Inconsistency Issue:** ```javascript async _handleClick() { if (this.isAuthenticated && this.options.behavior.showUserInfo) { this._showUserMenu(); // Show user options } else { // INCONSISTENCY: Auto-tries extension instead of opening modal if (window.nostr && this._isRealExtension(window.nostr)) { await this._tryExtensionLogin(window.nostr); // Automatic extension attempt } else { if (this.modal) { this.modal.open({ startScreen: 'login' }); // Fallback to modal } } } } ``` **Comparison with Login Button behavior:** - Login Button: **Always** opens modal for user choice - FloatingTab: **Auto-tries extension first**, only shows modal if denied ### 4. Authentication Restoration on Page Refresh **Two-Path System:** #### Path 1: Extension Mode (Line 1115-1173) ```javascript async _attemptExtensionRestore() { const authManager = new AuthManager({ isolateSession: this.options?.isolateSession }); const storedAuth = await authManager.restoreAuthState(); if (!storedAuth || storedAuth.method !== 'extension') return null; // Verify extension still works with same pubkey if (!window.nostr || !this._isRealExtension(window.nostr)) return null; const currentPubkey = await window.nostr.getPublicKey(); if (currentPubkey !== storedAuth.pubkey) return null; // Dispatch nlAuthRestored event for UI updates window.dispatchEvent(new CustomEvent('nlAuthRestored', { detail: extensionAuth })); } ``` #### Path 2: Non-Extension Mode (Line 1080-1098) ```javascript // Uses facade's restoreAuthState method if (this.facadeInstalled && window.nostr?.restoreAuthState) { const restoredAuth = await window.nostr.restoreAuthState(); if (restoredAuth) { // Handle NIP-46 reconnection if needed if (restoredAuth.requiresReconnection) { this._showReconnectionPrompt(restoredAuth); } } } ``` ### 5. Storage Strategy (Line 1408-1414) **Storage Type Selection:** ```javascript if (options.isolateSession) { this.storage = sessionStorage; // Per-window isolation } else { this.storage = localStorage; // Cross-window persistence } ``` ### 6. Event-Driven State Synchronization **Key Events:** - `nlMethodSelected` - Dispatched when user completes authentication - `nlAuthRestored` - Dispatched when authentication is restored from storage - `nlLogout` - Dispatched when user logs out - `nlReconnectionRequired` - Dispatched when NIP-46 needs reconnection **Event Listeners:** - FloatingTab listens to all auth events for UI updates (Line 271-295) - WindowNostr listens to nlMethodSelected/nlLogout for state management (Line 823-869) ## State Persistence Security Model ### By Authentication Method: **Extension:** - ✅ Store: pubkey, verification metadata - ❌ Never store: extension object, secrets - 🔒 Security: Minimal data, 1-hour expiry **Local Key:** - ✅ Store: encrypted secret key, pubkey - 🔒 Security: AES-GCM encryption with session-specific password - 🔑 Session password stored in sessionStorage (cleared on tab close) **NIP-46:** - ✅ Store: connection parameters, pubkey - ❌ Never store: session secrets - 🔄 Requires: User reconnection on restore **Read-only:** - ✅ Store: method type, pubkey - ❌ No secrets to store ## Current Issues Identified ### UX Inconsistency (THE MAIN ISSUE) **Problem:** FloatingTab and Login Button have different click behaviors - **FloatingTab:** Auto-tries extension → Falls back to modal if denied - **Login Button:** Always opens modal for user choice **Impact:** - Confusing user experience - Inconsistent interaction patterns - Users don't get consistent choice of authentication method **Root Cause:** Line 358-367 in FloatingTab._handleClick() method ### Proposed Solutions: #### Option 1: Make FloatingTab Consistent (Recommended) ```javascript async _handleClick() { if (this.isAuthenticated && this.options.behavior.showUserInfo) { this._showUserMenu(); } else { // Always open modal - consistent with login button if (this.modal) { this.modal.open({ startScreen: 'login' }); } } } ``` #### Option 2: Add Configuration Option ```javascript floatingTab: { behavior: { autoTryExtension: false, // Default to consistent behavior // ... other options } } ``` ## ⚠️ IMPLEMENTATION STATUS: READY FOR CODE CHANGES **User Decision:** FloatingTab should behave exactly like login buttons - always open modal for authentication method selection. **Required Changes:** 1. **File:** `lite/build.js` 2. **Method:** `FloatingTab._handleClick()` (lines 351-369) 3. **Action:** Remove extension auto-detection, always open modal **Current Code to Replace (lines 358-367):** ```javascript // Check if extension is available for direct login if (window.nostr && this._isRealExtension(window.nostr)) { console.log('FloatingTab: Extension available, attempting direct extension login'); await this._tryExtensionLogin(window.nostr); } else { // Open login modal if (this.modal) { this.modal.open({ startScreen: 'login' }); } } ``` **Replacement Code:** ```javascript // Always open login modal (consistent with login buttons) if (this.modal) { this.modal.open({ startScreen: 'login' }); } ``` **Critical Safety Notes:** - ✅ **DO NOT** change `_checkExistingAuth()` method (lines 299-349) - this handles automatic restoration on page refresh - ✅ **ONLY** change the click handler to remove manual extension detection - ✅ Authentication restoration will continue to work properly via the separate restoration system - ✅ Extension detection logic remains intact for other purposes (storage, verification, etc.) **After Implementation:** - Rebuild the library with `node lite/build.js` - Test that both floating tab and login buttons behave identically - Verify that automatic login restoration on page refresh still works properly ## Important Notes 1. **Extension-First Architecture:** The system never interferes with real browser extensions 2. **Dual Storage Support:** Supports both per-window (sessionStorage) and cross-window (localStorage) persistence 3. **Security-First:** Sensitive data is always encrypted or not stored 4. **Event-Driven:** All components communicate via custom events 5. **Automatic Restoration:** Authentication state is automatically restored on page refresh when possible The login logic is complex due to supporting multiple authentication methods, security requirements, and different storage strategies, but it provides a flexible and secure authentication system for Nostr applications.