From 83f46e6619666249ef32e4d0a41736ee1f67fa7e Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Thu, 19 Feb 2026 15:26:21 +0100 Subject: [PATCH] implemented improved account verification flow with recovery and resend --- backend/api/views.py | 35 +++++++++++++++++++++++++- frontend/src/i18n.ts | 28 +++++++++++++++++++++ frontend/src/pages/Login.tsx | 49 +++++++++++++++++++++++++++--------- 3 files changed, 99 insertions(+), 13 deletions(-) diff --git a/backend/api/views.py b/backend/api/views.py index 23fd3d8..990b095 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -323,7 +323,40 @@ class UserViewSet(viewsets.ModelViewSet): status=status.HTTP_500_INTERNAL_SERVER_ERROR ) - return Response({'status': 'User created, please verify your email'}, status=status.HTTP_201_CREATED) + return Response({'status': 'User created, please verify your email', 'username': username, 'email': email}, status=status.HTTP_201_CREATED) + + @action(detail=False, methods=['post'], permission_classes=[permissions.AllowAny]) + def resend_verification(self, request): + username = request.data.get('username') + if not username: + return Response({'error': 'Username required'}, status=status.HTTP_400_BAD_REQUEST) + + try: + user = User.objects.get(username=username, is_active=False) + except User.DoesNotExist: + return Response({'error': 'User not found or already active'}, status=status.HTTP_404_NOT_FOUND) + + import secrets + import string + code = ''.join(secrets.choice(string.digits) for _ in range(6)) + user.email_confirmation_code = code + user.save() + + try: + send_mail( + 'Verify your Holy Flavors account', + f'Hi {user.username},\n\nYour new verification code is: {code}', + settings.DEFAULT_FROM_EMAIL, + [user.email], + fail_silently=False, + ) + except Exception as e: + return Response( + {'error': f'Failed to send email: {e}'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) + + return Response({'status': 'Verification code resent!'}) @action(detail=False, methods=['post'], permission_classes=[permissions.AllowAny]) def verify_signup(self, request): diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index b71a800..e98987e 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -118,6 +118,20 @@ const resources = { "save": "Save", "cancel": "Cancel" }, + "auth": { + "loginTitle": "Login", + "signupTitle": "Signup", + "verifyTitle": "Verify Account", + "verifyDesc": "Enter the code sent to your email for {{username}}.", + "resendButton": "Resend Verification Code", + "resendSuccess": "A new verification code has been sent to your email.", + "verifySuccess": "Account verified! You can now login.", + "unverifiedError": "Account not verified. Please verify your account to login.", + "loginFailed": "Login failed. Please check your credentials.", + "signupSuccess": "Signup successful! Please enter the verification code sent to your email.", + "forgotPassword": "Forgot Password?", + "backToAuth": "Back to Signup/Login" + }, "community": { "title": "Community Activity", "subtitle": "Stay updated with your circle's latest ratings and discussions.", @@ -344,6 +358,20 @@ const resources = { "save": "Speichern", "cancel": "Abbrechen" }, + "auth": { + "loginTitle": "Anmelden", + "signupTitle": "Registrieren", + "verifyTitle": "Konto verifizieren", + "verifyDesc": "Gib den Code ein, der an deine E-Mail für {{username}} gesendet wurde.", + "resendButton": "Verifizierungscode erneut senden", + "resendSuccess": "Ein neuer Verifizierungscode wurde an deine E-Mail gesendet.", + "verifySuccess": "Konto verifiziert! Du kannst dich jetzt anmelden.", + "unverifiedError": "Konto nicht verifiziert. Bitte verifiziere dein Konto, um dich anzumelden.", + "loginFailed": "Anmeldung fehlgeschlagen. Bitte überprüfe deine Zugangsdaten.", + "signupSuccess": "Registrierung erfolgreich! Bitte gib den Verifizierungscode ein, der an deine E-Mail gesendet wurde.", + "forgotPassword": "Passwort vergessen?", + "backToAuth": "Zurück zur Anmeldung" + }, "community": { "title": "Community-Aktivität", "subtitle": "Bleib auf dem Laufenden mit den neuesten Bewertungen und Diskussionen deines Kreises.", diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 0ca40d5..c20a3e1 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -16,6 +16,7 @@ const Login: React.FC = () => { const [email, setEmail] = useState(''); const [verificationCode, setVerificationCode] = useState(''); const [isVerifying, setIsVerifying] = useState(false); + const [showResend, setShowResend] = useState(false); const [isResetting, setIsResetting] = useState(false); const [resetStep, setResetStep] = useState(1); // 1: email, 2: code/new pass const [message, setMessage] = useState<{type: 'success' | 'error', text: string} | null>(null); @@ -31,7 +32,13 @@ const Login: React.FC = () => { localStorage.setItem('token', res.data.token); window.location.href = '/'; } catch (err: any) { - setMessage({ type: 'error', text: 'Login failed. Please check your credentials or verify your account.' }); + const errorMsg = err.response?.data?.non_field_errors?.[0] || t('auth.loginFailed'); + if (errorMsg.toLowerCase().includes('inactive') || errorMsg.toLowerCase().includes('verify') || errorMsg.includes('disabled')) { + setMessage({ type: 'error', text: t('auth.unverifiedError') }); + setIsVerifying(true); + } else { + setMessage({ type: 'error', text: errorMsg }); + } } }; @@ -41,7 +48,7 @@ const Login: React.FC = () => { try { await api.post('users/signup/', { username, email, password }); setIsVerifying(true); - setMessage({ type: 'success', text: 'Signup successful! Please enter the verification code sent to your email.' }); + setMessage({ type: 'success', text: t('auth.signupSuccess') }); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.error || 'Signup failed' }); } @@ -52,12 +59,24 @@ const Login: React.FC = () => { setMessage(null); try { await api.post('users/verify_signup/', { username, code: verificationCode }); - setMessage({ type: 'success', text: 'Account verified! You can now login.' }); + setMessage({ type: 'success', text: t('auth.verifySuccess') }); setIsVerifying(false); setTab(0); setPassword(''); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.error || 'Verification failed' }); + setShowResend(true); + } + }; + + const handleResendCode = async () => { + setMessage(null); + try { + await api.post('users/resend_verification/', { username }); + setMessage({ type: 'success', text: t('auth.resendSuccess') }); + setShowResend(false); + } catch (err: any) { + setMessage({ type: 'error', text: err.response?.data?.error || 'Failed to resend code' }); } }; @@ -93,20 +112,26 @@ const Login: React.FC = () => { if (isVerifying) { return (
- Verify Account - Enter the code sent to {email} + {t('auth.verifyTitle')} + setVerificationCode(e.target.value)} + autoFocus /> - + + ); @@ -156,8 +181,8 @@ const Login: React.FC = () => { return ( <> { setTab(v); setMessage(null); }} sx={{ mb: 2 }}> - - + + {tab === 0 ? ( @@ -178,10 +203,10 @@ const Login: React.FC = () => { onChange={(e) => setPassword(e.target.value)} /> ) : ( @@ -210,7 +235,7 @@ const Login: React.FC = () => { onChange={(e) => setPassword(e.target.value)} /> )}