You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
6.8 KiB
255 lines
6.8 KiB
/** |
|
* NDK Authentication Patterns |
|
* |
|
* Examples from: src/lib/stores/auth.ts |
|
*/ |
|
|
|
import NDK from '@nostr-dev-kit/ndk' |
|
import { NDKNip07Signer, NDKPrivateKeySigner, NDKNip46Signer } from '@nostr-dev-kit/ndk' |
|
|
|
// ============================================================ |
|
// NIP-07 - BROWSER EXTENSION SIGNER |
|
// ============================================================ |
|
|
|
const loginWithExtension = async (ndk: NDK) => { |
|
try { |
|
// Create NIP-07 signer (browser extension like Alby, nos2x) |
|
const signer = new NDKNip07Signer() |
|
|
|
// Wait for signer to be ready |
|
await signer.blockUntilReady() |
|
|
|
// Set signer on NDK instance |
|
ndk.signer = signer |
|
|
|
// Get authenticated user |
|
const user = await signer.user() |
|
|
|
console.log('✅ Logged in via extension:', user.npub) |
|
return { user, signer } |
|
} catch (error) { |
|
console.error('❌ Extension login failed:', error) |
|
throw new Error('Failed to login with browser extension. Is it installed?') |
|
} |
|
} |
|
|
|
// ============================================================ |
|
// PRIVATE KEY SIGNER |
|
// ============================================================ |
|
|
|
const loginWithPrivateKey = async (ndk: NDK, privateKeyHex: string) => { |
|
try { |
|
// Validate private key format (64 hex characters) |
|
if (!/^[0-9a-f]{64}$/.test(privateKeyHex)) { |
|
throw new Error('Invalid private key format') |
|
} |
|
|
|
// Create private key signer |
|
const signer = new NDKPrivateKeySigner(privateKeyHex) |
|
|
|
// Wait for signer to be ready |
|
await signer.blockUntilReady() |
|
|
|
// Set signer on NDK instance |
|
ndk.signer = signer |
|
|
|
// Get authenticated user |
|
const user = await signer.user() |
|
|
|
console.log('✅ Logged in with private key:', user.npub) |
|
return { user, signer } |
|
} catch (error) { |
|
console.error('❌ Private key login failed:', error) |
|
throw error |
|
} |
|
} |
|
|
|
// ============================================================ |
|
// NIP-46 - REMOTE SIGNER (BUNKER) |
|
// ============================================================ |
|
|
|
const loginWithNip46 = async ( |
|
ndk: NDK, |
|
bunkerUrl: string, |
|
localPrivateKey?: string |
|
) => { |
|
try { |
|
// Create or use existing local signer |
|
const localSigner = localPrivateKey |
|
? new NDKPrivateKeySigner(localPrivateKey) |
|
: NDKPrivateKeySigner.generate() |
|
|
|
// Create NIP-46 remote signer |
|
const remoteSigner = new NDKNip46Signer(ndk, bunkerUrl, localSigner) |
|
|
|
// Wait for signer to be ready (may require user approval) |
|
await remoteSigner.blockUntilReady() |
|
|
|
// Set signer on NDK instance |
|
ndk.signer = remoteSigner |
|
|
|
// Get authenticated user |
|
const user = await remoteSigner.user() |
|
|
|
console.log('✅ Logged in via NIP-46:', user.npub) |
|
|
|
// Store local signer key for reconnection |
|
return { |
|
user, |
|
signer: remoteSigner, |
|
localSignerKey: localSigner.privateKey |
|
} |
|
} catch (error) { |
|
console.error('❌ NIP-46 login failed:', error) |
|
throw error |
|
} |
|
} |
|
|
|
// ============================================================ |
|
// AUTO-LOGIN FROM LOCAL STORAGE |
|
// ============================================================ |
|
|
|
const STORAGE_KEYS = { |
|
AUTO_LOGIN: 'nostr:auto-login', |
|
LOCAL_SIGNER: 'nostr:local-signer', |
|
BUNKER_URL: 'nostr:bunker-url', |
|
ENCRYPTED_KEY: 'nostr:encrypted-key' |
|
} |
|
|
|
const getAuthFromStorage = async (ndk: NDK) => { |
|
try { |
|
// Check if auto-login is enabled |
|
const autoLogin = localStorage.getItem(STORAGE_KEYS.AUTO_LOGIN) |
|
if (autoLogin !== 'true') { |
|
return null |
|
} |
|
|
|
// Try NIP-46 bunker connection |
|
const privateKey = localStorage.getItem(STORAGE_KEYS.LOCAL_SIGNER) |
|
const bunkerUrl = localStorage.getItem(STORAGE_KEYS.BUNKER_URL) |
|
|
|
if (privateKey && bunkerUrl) { |
|
return await loginWithNip46(ndk, bunkerUrl, privateKey) |
|
} |
|
|
|
// Try encrypted private key |
|
const encryptedKey = localStorage.getItem(STORAGE_KEYS.ENCRYPTED_KEY) |
|
if (encryptedKey) { |
|
// Would need decryption password from user |
|
return { needsPassword: true, encryptedKey } |
|
} |
|
|
|
// Fallback to extension |
|
return await loginWithExtension(ndk) |
|
} catch (error) { |
|
console.error('Auto-login failed:', error) |
|
return null |
|
} |
|
} |
|
|
|
// ============================================================ |
|
// SAVE AUTH TO STORAGE |
|
// ============================================================ |
|
|
|
const saveAuthToStorage = ( |
|
method: 'extension' | 'private-key' | 'nip46', |
|
data?: { |
|
privateKey?: string |
|
bunkerUrl?: string |
|
encryptedKey?: string |
|
} |
|
) => { |
|
// Enable auto-login |
|
localStorage.setItem(STORAGE_KEYS.AUTO_LOGIN, 'true') |
|
|
|
if (method === 'nip46' && data?.privateKey && data?.bunkerUrl) { |
|
localStorage.setItem(STORAGE_KEYS.LOCAL_SIGNER, data.privateKey) |
|
localStorage.setItem(STORAGE_KEYS.BUNKER_URL, data.bunkerUrl) |
|
} else if (method === 'private-key' && data?.encryptedKey) { |
|
localStorage.setItem(STORAGE_KEYS.ENCRYPTED_KEY, data.encryptedKey) |
|
} |
|
// Extension doesn't need storage |
|
} |
|
|
|
// ============================================================ |
|
// LOGOUT |
|
// ============================================================ |
|
|
|
const logout = (ndk: NDK) => { |
|
// Remove signer from NDK |
|
ndk.signer = undefined |
|
|
|
// Clear all auth storage |
|
Object.values(STORAGE_KEYS).forEach(key => { |
|
localStorage.removeItem(key) |
|
}) |
|
|
|
console.log('✅ Logged out successfully') |
|
} |
|
|
|
// ============================================================ |
|
// GET CURRENT USER |
|
// ============================================================ |
|
|
|
const getCurrentUser = async (ndk: NDK) => { |
|
if (!ndk.signer) { |
|
return null |
|
} |
|
|
|
try { |
|
const user = await ndk.signer.user() |
|
return { |
|
pubkey: user.pubkey, |
|
npub: user.npub, |
|
profile: await user.fetchProfile() |
|
} |
|
} catch (error) { |
|
console.error('Failed to get current user:', error) |
|
return null |
|
} |
|
} |
|
|
|
// ============================================================ |
|
// USAGE EXAMPLE |
|
// ============================================================ |
|
|
|
async function authExample(ndk: NDK) { |
|
// Try auto-login first |
|
let auth = await getAuthFromStorage(ndk) |
|
|
|
if (!auth) { |
|
// Manual login options |
|
console.log('Choose login method:') |
|
console.log('1. Browser Extension (NIP-07)') |
|
console.log('2. Private Key') |
|
console.log('3. Remote Signer (NIP-46)') |
|
|
|
// Example: login with extension |
|
auth = await loginWithExtension(ndk) |
|
saveAuthToStorage('extension') |
|
} |
|
|
|
if (auth && 'needsPassword' in auth) { |
|
// Handle encrypted key case |
|
console.log('Password required for encrypted key') |
|
return |
|
} |
|
|
|
// Get current user info |
|
const currentUser = await getCurrentUser(ndk) |
|
console.log('Current user:', currentUser) |
|
|
|
// Logout when done |
|
// logout(ndk) |
|
} |
|
|
|
export { |
|
loginWithExtension, |
|
loginWithPrivateKey, |
|
loginWithNip46, |
|
getAuthFromStorage, |
|
saveAuthToStorage, |
|
logout, |
|
getCurrentUser |
|
} |
|
|
|
|