| … | … | @@ -2194,127 +2194,219 @@ async function checkEncryptionStatus() { |
| 2194 | 2194 | return null // Unknown status |
| 2195 | 2195 | } |
| 2196 | 2196 | |
| 2197 | | // Prompt for password to unlock wallet |
| 2197 | // Prompt for password to unlock wallet using dialog |
| 2198 | 2198 | async function promptForPassword() { |
| 2199 | | let attempts = 0 |
| 2200 | 2199 | const maxAttempts = 3 |
| 2200 | let attempts = 0 |
| 2201 | 2201 | |
| 2202 | 2202 | // Check if this is first-time password setup or regular unlock |
| 2203 | 2203 | const isEncrypted = await checkEncryptionStatus() |
| 2204 | 2204 | const isFirstTimeSetup = isEncrypted === false |
| 2205 | 2205 | |
| 2206 | | while (attempts < maxAttempts) { |
| 2207 | | let promptMessage |
| 2208 | | |
| 2209 | | if (isFirstTimeSetup && attempts === 0) { |
| 2210 | | // First-time password setup - clear warning |
| 2211 | | promptMessage = `⚠️ SET WALLET PASSWORD (First Time Setup) |
| 2212 | | |
| 2213 | | This will ENCRYPT your wallet mnemonics with a password. |
| 2214 | | |
| 2215 | | IMPORTANT: |
| 2216 | | • You MUST remember this password |
| 2217 | | • Required every time you access the wallet |
| 2218 | | • Cannot be recovered if forgotten |
| 2219 | | • Minimum 8 characters |
| 2220 | | |
| 2221 | | Enter a strong password:` |
| 2222 | | } else if (isFirstTimeSetup) { |
| 2223 | | // First-time setup, retry attempt |
| 2224 | | promptMessage = `⚠️ SET WALLET PASSWORD |
| 2225 | | |
| 2226 | | Enter a strong password (min 8 characters): |
| 2227 | | |
| 2228 | | (Attempt ${attempts + 1}/${maxAttempts})` |
| 2206 | return new Promise((resolve) => { |
| 2207 | const dialog = document.getElementById('password-dialog') |
| 2208 | const titleEl = document.getElementById('password-dialog-title') |
| 2209 | const warningEl = document.getElementById('password-dialog-warning') |
| 2210 | const passwordInput = document.getElementById('password-input') |
| 2211 | const confirmGroup = document.getElementById('password-confirm-group') |
| 2212 | const confirmInput = document.getElementById('password-confirm-input') |
| 2213 | const matchError = document.getElementById('password-match-error') |
| 2214 | const strengthDiv = document.getElementById('password-strength') |
| 2215 | const strengthText = document.getElementById('password-strength-text') |
| 2216 | const strengthBar = document.getElementById('password-strength-bar') |
| 2217 | const submitBtn = document.getElementById('password-submit-btn') |
| 2218 | const cancelBtn = document.getElementById('password-cancel-btn') |
| 2219 | const attemptsEl = document.getElementById('password-attempts') |
| 2220 | |
| 2221 | // Configure dialog for first-time setup vs unlock |
| 2222 | if (isFirstTimeSetup) { |
| 2223 | titleEl.textContent = '⚠️ SET WALLET PASSWORD' |
| 2224 | warningEl.style.display = 'block' |
| 2225 | confirmGroup.style.display = 'block' |
| 2226 | strengthDiv.style.display = 'block' |
| 2229 | 2227 | } else { |
| 2230 | | // Regular unlock |
| 2231 | | promptMessage = `🔐 Unlock Wallet |
| 2232 | | |
| 2233 | | Enter your wallet password: |
| 2234 | | |
| 2235 | | (Attempt ${attempts + 1}/${maxAttempts})` |
| 2228 | titleEl.textContent = '🔐 Unlock Wallet' |
| 2229 | warningEl.style.display = 'none' |
| 2230 | confirmGroup.style.display = 'none' |
| 2231 | strengthDiv.style.display = 'none' |
| 2236 | 2232 | } |
| 2237 | 2233 | |
| 2238 | | const password = prompt(promptMessage) |
| 2234 | // Show attempts counter |
| 2235 | if (attempts > 0) { |
| 2236 | attemptsEl.textContent = `Attempt ${attempts + 1} of ${maxAttempts}` |
| 2237 | attemptsEl.style.display = 'block' |
| 2238 | } else { |
| 2239 | attemptsEl.style.display = 'none' |
| 2240 | } |
| 2241 | |
| 2242 | // Password strength calculator |
| 2243 | function checkPasswordStrength(password) { |
| 2244 | if (!password) return { strength: 0, text: 'Too short', color: '#d32f2f' } |
| 2245 | |
| 2246 | let strength = 0 |
| 2247 | if (password.length >= 8) strength += 25 |
| 2248 | if (password.length >= 12) strength += 25 |
| 2249 | if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength += 20 |
| 2250 | if (/\d/.test(password)) strength += 15 |
| 2251 | if (/[^a-zA-Z0-9]/.test(password)) strength += 15 |
| 2252 | |
| 2253 | if (strength < 40) return { strength, text: 'Weak', color: '#d32f2f' } |
| 2254 | if (strength < 60) return { strength, text: 'Fair', color: '#ff9800' } |
| 2255 | if (strength < 80) return { strength, text: 'Good', color: '#2196f3' } |
| 2256 | return { strength, text: 'Strong', color: '#4caf50' } |
| 2257 | } |
| 2258 | |
| 2259 | // Validation function |
| 2260 | function validateInputs() { |
| 2261 | const password = passwordInput.value |
| 2262 | const confirm = confirmInput.value |
| 2263 | |
| 2264 | // Update strength indicator (only for first-time setup) |
| 2265 | if (isFirstTimeSetup) { |
| 2266 | const { strength, text, color} = checkPasswordStrength(password) |
| 2267 | strengthText.textContent = text |
| 2268 | strengthText.style.color = color |
| 2269 | strengthBar.style.width = `${strength}%` |
| 2270 | strengthBar.style.background = color |
| 2271 | } |
| 2239 | 2272 | |
| 2240 | | // User cancelled |
| 2241 | | if (password === null) { |
| 2242 | | if (attempts === 0) { |
| 2243 | | return false // First cancellation - let them refresh |
| 2273 | // Check password length |
| 2274 | if (password.length < 8) { |
| 2275 | submitBtn.disabled = true |
| 2276 | return |
| 2277 | } |
| 2278 | |
| 2279 | // Check password match (only for first-time setup) |
| 2280 | if (isFirstTimeSetup) { |
| 2281 | if (password !== confirm) { |
| 2282 | matchError.style.display = 'block' |
| 2283 | submitBtn.disabled = true |
| 2284 | return |
| 2285 | } else { |
| 2286 | matchError.style.display = 'none' |
| 2287 | } |
| 2244 | 2288 | } |
| 2245 | | continue // Subsequent cancellations - give another chance |
| 2246 | | } |
| 2247 | 2289 | |
| 2248 | | // Validate password length |
| 2249 | | if (password.length < 8) { |
| 2250 | | alert('❌ Password must be at least 8 characters') |
| 2251 | | attempts++ |
| 2252 | | continue |
| 2290 | // All validations passed |
| 2291 | submitBtn.disabled = false |
| 2253 | 2292 | } |
| 2254 | 2293 | |
| 2255 | | // For first-time setup, ask for confirmation |
| 2294 | // Add input listeners |
| 2295 | passwordInput.addEventListener('input', validateInputs) |
| 2256 | 2296 | if (isFirstTimeSetup) { |
| 2257 | | const confirmPassword = prompt('🔐 CONFIRM PASSWORD\n\nPlease re-enter your password to confirm:') |
| 2297 | confirmInput.addEventListener('input', validateInputs) |
| 2298 | } |
| 2299 | |
| 2300 | // Handle form submission |
| 2301 | async function handleSubmit() { |
| 2302 | const password = passwordInput.value |
| 2303 | |
| 2304 | try { |
| 2305 | submitBtn.disabled = true |
| 2306 | submitBtn.textContent = 'Submitting...' |
| 2307 | |
| 2308 | // Call unlock endpoint |
| 2309 | const res = await fetch(`${API_BASE}/unlock`, { |
| 2310 | method: 'POST', |
| 2311 | headers: { 'Content-Type': 'application/json' }, |
| 2312 | body: JSON.stringify({ password }) |
| 2313 | }) |
| 2314 | |
| 2315 | if (!res.ok) { |
| 2316 | const error = await res.json() |
| 2317 | if (res.status === 401) { |
| 2318 | attempts++ |
| 2319 | |
| 2320 | if (attempts >= maxAttempts) { |
| 2321 | alert('❌ Maximum unlock attempts reached. Please refresh the page to try again.') |
| 2322 | dialog.close() |
| 2323 | resolve(false) |
| 2324 | return |
| 2325 | } |
| 2326 | |
| 2327 | // Show error and reset |
| 2328 | if (isFirstTimeSetup) { |
| 2329 | alert('❌ Failed to set password. Please try again.') |
| 2330 | } else { |
| 2331 | alert('❌ Invalid password. Please try again.') |
| 2332 | } |
| 2333 | |
| 2334 | passwordInput.value = '' |
| 2335 | confirmInput.value = '' |
| 2336 | submitBtn.textContent = 'Submit' |
| 2337 | validateInputs() |
| 2338 | |
| 2339 | // Update attempts counter |
| 2340 | attemptsEl.textContent = `Attempt ${attempts + 1} of ${maxAttempts}` |
| 2341 | attemptsEl.style.display = 'block' |
| 2342 | return |
| 2343 | } |
| 2344 | throw new Error(error.error || 'Unlock failed') |
| 2345 | } |
| 2258 | 2346 | |
| 2259 | | if (confirmPassword === null) { |
| 2260 | | continue // Let them try again |
| 2261 | | } |
| 2347 | const data = await res.json() |
| 2262 | 2348 | |
| 2263 | | if (password !== confirmPassword) { |
| 2264 | | alert('❌ Passwords do not match. Please try again.') |
| 2265 | | attempts++ |
| 2266 | | continue |
| 2267 | | } |
| 2268 | | } |
| 2349 | // Store API token |
| 2350 | saveToken(data.token) |
| 2269 | 2351 | |
| 2270 | | try { |
| 2271 | | // Call unlock endpoint |
| 2272 | | const res = await fetch(`${API_BASE}/unlock`, { |
| 2273 | | method: 'POST', |
| 2274 | | headers: { 'Content-Type': 'application/json' }, |
| 2275 | | body: JSON.stringify({ password }) |
| 2276 | | }) |
| 2352 | // Close dialog |
| 2353 | dialog.close() |
| 2277 | 2354 | |
| 2278 | | if (!res.ok) { |
| 2279 | | const error = await res.json() |
| 2280 | | if (res.status === 401) { |
| 2281 | | if (isFirstTimeSetup) { |
| 2282 | | alert('❌ Failed to set password. Please try again.') |
| 2283 | | } else { |
| 2284 | | alert('❌ Invalid password. Please try again.') |
| 2285 | | } |
| 2286 | | attempts++ |
| 2287 | | continue |
| 2355 | // Show success message |
| 2356 | if (data.migrated) { |
| 2357 | alert('✅ SUCCESS!\n\nYour wallet mnemonics have been encrypted.\n\nREMEMBER YOUR PASSWORD - you will need it every time you access the wallet!') |
| 2358 | showSuccess('✅ Wallet encrypted and unlocked!') |
| 2359 | } else { |
| 2360 | showSuccess('✅ Wallet unlocked successfully') |
| 2288 | 2361 | } |
| 2289 | | throw new Error(error.error || 'Unlock failed') |
| 2290 | | } |
| 2291 | 2362 | |
| 2292 | | const data = await res.json() |
| 2363 | console.log('🔓 Wallet unlocked, public key:', data.publicKey) |
| 2364 | resolve(true) |
| 2293 | 2365 | |
| 2294 | | // Store API token using the correct function |
| 2295 | | saveToken(data.token) |
| 2366 | } catch (err) { |
| 2367 | console.error('Unlock error:', err) |
| 2368 | alert(`❌ Error: ${err.message}`) |
| 2369 | attempts++ |
| 2296 | 2370 | |
| 2297 | | // Show success message |
| 2298 | | if (data.migrated) { |
| 2299 | | alert('✅ SUCCESS!\n\nYour wallet mnemonics have been encrypted.\n\nREMEMBER YOUR PASSWORD - you will need it every time you access the wallet!') |
| 2300 | | showSuccess('✅ Wallet encrypted and unlocked!') |
| 2301 | | } else { |
| 2302 | | showSuccess('✅ Wallet unlocked successfully') |
| 2371 | if (attempts >= maxAttempts) { |
| 2372 | dialog.close() |
| 2373 | resolve(false) |
| 2374 | } else { |
| 2375 | submitBtn.textContent = 'Submit' |
| 2376 | submitBtn.disabled = false |
| 2377 | } |
| 2303 | 2378 | } |
| 2379 | } |
| 2304 | 2380 | |
| 2305 | | console.log('🔓 Wallet unlocked, public key:', data.publicKey) |
| 2306 | | return true |
| 2307 | | |
| 2308 | | } catch (err) { |
| 2309 | | console.error('Unlock error:', err) |
| 2310 | | alert(`❌ Error: ${err.message}`) |
| 2311 | | attempts++ |
| 2381 | // Button handlers |
| 2382 | submitBtn.onclick = handleSubmit |
| 2383 | cancelBtn.onclick = () => { |
| 2384 | dialog.close() |
| 2385 | resolve(false) |
| 2312 | 2386 | } |
| 2313 | | } |
| 2314 | 2387 | |
| 2315 | | // Max attempts reached |
| 2316 | | alert('❌ Maximum unlock attempts reached. Please refresh the page to try again.') |
| 2317 | | return false |
| 2388 | // Enter key submits |
| 2389 | passwordInput.addEventListener('keydown', (e) => { |
| 2390 | if (e.key === 'Enter' && !submitBtn.disabled) { |
| 2391 | handleSubmit() |
| 2392 | } |
| 2393 | }) |
| 2394 | confirmInput.addEventListener('keydown', (e) => { |
| 2395 | if (e.key === 'Enter' && !submitBtn.disabled) { |
| 2396 | handleSubmit() |
| 2397 | } |
| 2398 | }) |
| 2399 | |
| 2400 | // Reset and show dialog |
| 2401 | passwordInput.value = '' |
| 2402 | confirmInput.value = '' |
| 2403 | matchError.style.display = 'none' |
| 2404 | submitBtn.textContent = 'Submit' |
| 2405 | submitBtn.disabled = true |
| 2406 | |
| 2407 | dialog.showModal() |
| 2408 | passwordInput.focus() |
| 2409 | }) |
| 2318 | 2410 | } |
| 2319 | 2411 | |
| 2320 | 2412 | // Initialize on page load |