02-authentication.ts raw

   1  /**
   2   * NDK Authentication Patterns
   3   * 
   4   * Examples from: src/lib/stores/auth.ts
   5   */
   6  
   7  import NDK from '@nostr-dev-kit/ndk'
   8  import { NDKNip07Signer, NDKPrivateKeySigner, NDKNip46Signer } from '@nostr-dev-kit/ndk'
   9  
  10  // ============================================================
  11  // NIP-07 - BROWSER EXTENSION SIGNER
  12  // ============================================================
  13  
  14  const loginWithExtension = async (ndk: NDK) => {
  15    try {
  16      // Create NIP-07 signer (browser extension like Alby, nos2x)
  17      const signer = new NDKNip07Signer()
  18      
  19      // Wait for signer to be ready
  20      await signer.blockUntilReady()
  21      
  22      // Set signer on NDK instance
  23      ndk.signer = signer
  24      
  25      // Get authenticated user
  26      const user = await signer.user()
  27      
  28      console.log('✅ Logged in via extension:', user.npub)
  29      return { user, signer }
  30    } catch (error) {
  31      console.error('❌ Extension login failed:', error)
  32      throw new Error('Failed to login with browser extension. Is it installed?')
  33    }
  34  }
  35  
  36  // ============================================================
  37  // PRIVATE KEY SIGNER
  38  // ============================================================
  39  
  40  const loginWithPrivateKey = async (ndk: NDK, privateKeyHex: string) => {
  41    try {
  42      // Validate private key format (64 hex characters)
  43      if (!/^[0-9a-f]{64}$/.test(privateKeyHex)) {
  44        throw new Error('Invalid private key format')
  45      }
  46      
  47      // Create private key signer
  48      const signer = new NDKPrivateKeySigner(privateKeyHex)
  49      
  50      // Wait for signer to be ready
  51      await signer.blockUntilReady()
  52      
  53      // Set signer on NDK instance
  54      ndk.signer = signer
  55      
  56      // Get authenticated user
  57      const user = await signer.user()
  58      
  59      console.log('✅ Logged in with private key:', user.npub)
  60      return { user, signer }
  61    } catch (error) {
  62      console.error('❌ Private key login failed:', error)
  63      throw error
  64    }
  65  }
  66  
  67  // ============================================================
  68  // NIP-46 - REMOTE SIGNER (BUNKER)
  69  // ============================================================
  70  
  71  const loginWithNip46 = async (
  72    ndk: NDK,
  73    bunkerUrl: string,
  74    localPrivateKey?: string
  75  ) => {
  76    try {
  77      // Create or use existing local signer
  78      const localSigner = localPrivateKey
  79        ? new NDKPrivateKeySigner(localPrivateKey)
  80        : NDKPrivateKeySigner.generate()
  81      
  82      // Create NIP-46 remote signer
  83      const remoteSigner = new NDKNip46Signer(ndk, bunkerUrl, localSigner)
  84      
  85      // Wait for signer to be ready (may require user approval)
  86      await remoteSigner.blockUntilReady()
  87      
  88      // Set signer on NDK instance
  89      ndk.signer = remoteSigner
  90      
  91      // Get authenticated user
  92      const user = await remoteSigner.user()
  93      
  94      console.log('✅ Logged in via NIP-46:', user.npub)
  95      
  96      // Store local signer key for reconnection
  97      return {
  98        user,
  99        signer: remoteSigner,
 100        localSignerKey: localSigner.privateKey
 101      }
 102    } catch (error) {
 103      console.error('❌ NIP-46 login failed:', error)
 104      throw error
 105    }
 106  }
 107  
 108  // ============================================================
 109  // AUTO-LOGIN FROM LOCAL STORAGE
 110  // ============================================================
 111  
 112  const STORAGE_KEYS = {
 113    AUTO_LOGIN: 'nostr:auto-login',
 114    LOCAL_SIGNER: 'nostr:local-signer',
 115    BUNKER_URL: 'nostr:bunker-url',
 116    ENCRYPTED_KEY: 'nostr:encrypted-key'
 117  }
 118  
 119  const getAuthFromStorage = async (ndk: NDK) => {
 120    try {
 121      // Check if auto-login is enabled
 122      const autoLogin = localStorage.getItem(STORAGE_KEYS.AUTO_LOGIN)
 123      if (autoLogin !== 'true') {
 124        return null
 125      }
 126      
 127      // Try NIP-46 bunker connection
 128      const privateKey = localStorage.getItem(STORAGE_KEYS.LOCAL_SIGNER)
 129      const bunkerUrl = localStorage.getItem(STORAGE_KEYS.BUNKER_URL)
 130      
 131      if (privateKey && bunkerUrl) {
 132        return await loginWithNip46(ndk, bunkerUrl, privateKey)
 133      }
 134      
 135      // Try encrypted private key
 136      const encryptedKey = localStorage.getItem(STORAGE_KEYS.ENCRYPTED_KEY)
 137      if (encryptedKey) {
 138        // Would need decryption password from user
 139        return { needsPassword: true, encryptedKey }
 140      }
 141      
 142      // Fallback to extension
 143      return await loginWithExtension(ndk)
 144    } catch (error) {
 145      console.error('Auto-login failed:', error)
 146      return null
 147    }
 148  }
 149  
 150  // ============================================================
 151  // SAVE AUTH TO STORAGE
 152  // ============================================================
 153  
 154  const saveAuthToStorage = (
 155    method: 'extension' | 'private-key' | 'nip46',
 156    data?: {
 157      privateKey?: string
 158      bunkerUrl?: string
 159      encryptedKey?: string
 160    }
 161  ) => {
 162    // Enable auto-login
 163    localStorage.setItem(STORAGE_KEYS.AUTO_LOGIN, 'true')
 164    
 165    if (method === 'nip46' && data?.privateKey && data?.bunkerUrl) {
 166      localStorage.setItem(STORAGE_KEYS.LOCAL_SIGNER, data.privateKey)
 167      localStorage.setItem(STORAGE_KEYS.BUNKER_URL, data.bunkerUrl)
 168    } else if (method === 'private-key' && data?.encryptedKey) {
 169      localStorage.setItem(STORAGE_KEYS.ENCRYPTED_KEY, data.encryptedKey)
 170    }
 171    // Extension doesn't need storage
 172  }
 173  
 174  // ============================================================
 175  // LOGOUT
 176  // ============================================================
 177  
 178  const logout = (ndk: NDK) => {
 179    // Remove signer from NDK
 180    ndk.signer = undefined
 181    
 182    // Clear all auth storage
 183    Object.values(STORAGE_KEYS).forEach(key => {
 184      localStorage.removeItem(key)
 185    })
 186    
 187    console.log('✅ Logged out successfully')
 188  }
 189  
 190  // ============================================================
 191  // GET CURRENT USER
 192  // ============================================================
 193  
 194  const getCurrentUser = async (ndk: NDK) => {
 195    if (!ndk.signer) {
 196      return null
 197    }
 198    
 199    try {
 200      const user = await ndk.signer.user()
 201      return {
 202        pubkey: user.pubkey,
 203        npub: user.npub,
 204        profile: await user.fetchProfile()
 205      }
 206    } catch (error) {
 207      console.error('Failed to get current user:', error)
 208      return null
 209    }
 210  }
 211  
 212  // ============================================================
 213  // USAGE EXAMPLE
 214  // ============================================================
 215  
 216  async function authExample(ndk: NDK) {
 217    // Try auto-login first
 218    let auth = await getAuthFromStorage(ndk)
 219    
 220    if (!auth) {
 221      // Manual login options
 222      console.log('Choose login method:')
 223      console.log('1. Browser Extension (NIP-07)')
 224      console.log('2. Private Key')
 225      console.log('3. Remote Signer (NIP-46)')
 226      
 227      // Example: login with extension
 228      auth = await loginWithExtension(ndk)
 229      saveAuthToStorage('extension')
 230    }
 231    
 232    if (auth && 'needsPassword' in auth) {
 233      // Handle encrypted key case
 234      console.log('Password required for encrypted key')
 235      return
 236    }
 237    
 238    // Get current user info
 239    const currentUser = await getCurrentUser(ndk)
 240    console.log('Current user:', currentUser)
 241    
 242    // Logout when done
 243    // logout(ndk)
 244  }
 245  
 246  export {
 247    loginWithExtension,
 248    loginWithPrivateKey,
 249    loginWithNip46,
 250    getAuthFromStorage,
 251    saveAuthToStorage,
 252    logout,
 253    getCurrentUser
 254  }
 255  
 256