feat: add "forgot account" reset flow with wallet preservation

Add account reset functionality for users who forgot their login credentials.
The reset clears authentication data while preserving wallet private keys and
exchange configs, which are automatically adopted by the new account on
re-registration to prevent fund loss.

- Add POST /api/reset-account endpoint
- Add "Forgot account?" button on login page (zh/en/id)
- Orphan ai_models and exchanges are re-assigned to new user on register
- Onboarding reuses existing claw402 wallet instead of generating new one

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
shinchan-zhai
2026-04-10 18:00:56 +08:00
parent 80272c0d5a
commit 4f0a922779
7 changed files with 133 additions and 0 deletions
+32
View File
@@ -8,6 +8,7 @@ import { DeepVoidBackground } from '../common/DeepVoidBackground'
import { LanguageSwitcher } from '../common/LanguageSwitcher'
import { OnboardingModeSelector } from './OnboardingModeSelector'
import type { UserMode } from '../../lib/onboarding'
import { invalidateSystemConfig } from '../../lib/config'
export function LoginPage() {
const { language } = useLanguage()
@@ -36,6 +37,27 @@ export function LoginPage() {
}
}, [language])
const handleResetAccount = async () => {
if (!window.confirm(t('forgotAccountConfirm', language))) return
try {
const res = await fetch('/api/reset-account', { method: 'POST' })
if (res.ok) {
localStorage.removeItem('auth_token')
localStorage.removeItem('auth_user')
localStorage.removeItem('user_id')
sessionStorage.removeItem('from401')
invalidateSystemConfig()
toast.success(t('forgotAccountSuccess', language))
setTimeout(() => { window.location.href = '/setup' }, 1500)
} else {
const data = await res.json()
toast.error(data.error || 'Reset failed')
}
} catch {
toast.error('Network error')
}
}
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
@@ -145,6 +167,16 @@ export function LoginPage() {
{loading ? t('loggingIn', language) || 'Signing in...' : t('signIn', language) || 'Sign In'}
</button>
</form>
<div className="mt-4 text-center">
<button
type="button"
onClick={handleResetAccount}
className="text-xs text-zinc-600 hover:text-red-400 transition-colors"
>
{t('forgotAccount', language)}
</button>
</div>
</div>
</div>
+9
View File
@@ -493,6 +493,9 @@ export const translations = {
registerNow: 'Sign up now',
loginNow: 'Sign in now',
forgotPassword: 'Forgot password?',
forgotAccount: 'Forgot account?',
forgotAccountConfirm: 'This will clear all account data and allow you to register a new account. Continue?',
forgotAccountSuccess: 'Account reset successful! You can now register a new account.',
rememberMe: 'Remember me',
resetPassword: 'Reset Password',
resetPasswordTitle: 'Reset your password',
@@ -1819,6 +1822,9 @@ export const translations = {
registerNow: '立即注册',
loginNow: '立即登录',
forgotPassword: '忘记密码?',
forgotAccount: '忘记账户?',
forgotAccountConfirm: '这将清除所有账户数据,允许您重新注册新账户。是否继续?',
forgotAccountSuccess: '账户已重置!现在可以注册新账户了。',
rememberMe: '记住我',
resetPassword: '重置密码',
resetPasswordTitle: '重置您的密码',
@@ -3080,6 +3086,9 @@ export const translations = {
registerNow: 'Daftar sekarang',
loginNow: 'Masuk sekarang',
forgotPassword: 'Lupa kata sandi?',
forgotAccount: 'Lupa akun?',
forgotAccountConfirm: 'Ini akan menghapus semua data akun dan memungkinkan Anda mendaftar akun baru. Lanjutkan?',
forgotAccountSuccess: 'Akun berhasil direset! Anda sekarang dapat mendaftar akun baru.',
rememberMe: 'Ingat saya',
resetPassword: 'Reset Kata Sandi',
resetPasswordTitle: 'Reset kata sandi Anda',