Files
nostr_login_lite/login_logic.md

413 lines
17 KiB
Markdown

# 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.