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