From 27ee0ae36560810406b0b292ece9bc581bab3bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Gierwia=C5=82o?= Date: Thu, 13 Nov 2025 18:59:28 +0100 Subject: [PATCH] fix: AWS SES configuration and email verification flow Changes: - Updated AWS_REGION to eu-central-1 in env examples - Fixed email verification to return new JWT token with updated emailVerified status - Added updateUser function to AuthContext for token refresh - Updated frontend to save new token after email verification - Fixed variable naming conflict (token vs jwtToken) in verification endpoints - Changed WSDC ID placeholder from 26997 to 12345 This ensures the verification banner disappears immediately after email verification without requiring re-login. --- backend/.env.production.example | 4 ++-- backend/src/controllers/auth.js | 24 ++++++++++++++++++++++-- frontend/src/contexts/AuthContext.jsx | 6 ++++++ frontend/src/pages/RegisterPage.jsx | 2 +- frontend/src/pages/VerifyEmailPage.jsx | 12 ++++++++++++ 5 files changed, 43 insertions(+), 5 deletions(-) diff --git a/backend/.env.production.example b/backend/.env.production.example index 9e45afd..97ba90c 100644 --- a/backend/.env.production.example +++ b/backend/.env.production.example @@ -21,8 +21,8 @@ JWT_EXPIRES_IN=24h # AWS SES - Production credentials # BEST PRACTICE: Use IAM roles instead of access keys AWS_REGION=us-east-1 -AWS_ACCESS_KEY_ID=AKIASOH3DHHDA557Z5N7 -AWS_SECRET_ACCESS_KEY=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 +AWS_ACCESS_KEY_ID=AK......... +AWS_SECRET_ACCESS_KEY=change-it SES_FROM_EMAIL=noreply@spotlight.cam SES_FROM_NAME=spotlight.cam diff --git a/backend/src/controllers/auth.js b/backend/src/controllers/auth.js index d7544af..fc353fc 100644 --- a/backend/src/controllers/auth.js +++ b/backend/src/controllers/auth.js @@ -191,7 +191,7 @@ async function verifyEmailByToken(req, res, next) { } // Update user - mark as verified and clear tokens - await prisma.user.update({ + const updatedUser = await prisma.user.update({ where: { id: user.id }, data: { emailVerified: true, @@ -208,9 +208,19 @@ async function verifyEmailByToken(req, res, next) { console.error('Failed to send welcome email:', emailError); } + // Generate new JWT token with updated emailVerified status + const jwtToken = generateToken({ userId: updatedUser.id }); + + // Remove sensitive data + const { passwordHash, verificationToken, verificationCode, verificationTokenExpiry, resetToken, resetTokenExpiry, ...userWithoutPassword } = updatedUser; + res.status(200).json({ success: true, message: 'Email verified successfully!', + data: { + user: userWithoutPassword, + token: jwtToken, + }, }); } catch (error) { next(error); @@ -261,7 +271,7 @@ async function verifyEmailByCode(req, res, next) { } // Update user - mark as verified and clear tokens - await prisma.user.update({ + const updatedUser = await prisma.user.update({ where: { id: user.id }, data: { emailVerified: true, @@ -278,9 +288,19 @@ async function verifyEmailByCode(req, res, next) { console.error('Failed to send welcome email:', emailError); } + // Generate new JWT token with updated emailVerified status + const jwtToken = generateToken({ userId: updatedUser.id }); + + // Remove sensitive data + const { passwordHash, verificationToken, verificationCode, verificationTokenExpiry, resetToken, resetTokenExpiry, ...userWithoutPassword } = updatedUser; + res.status(200).json({ success: true, message: 'Email verified successfully!', + data: { + user: userWithoutPassword, + token: jwtToken, + }, }); } catch (error) { next(error); diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 2ef907c..58e57f9 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -65,12 +65,18 @@ export const AuthProvider = ({ children }) => { setUser(null); }; + const updateUser = (userData) => { + setUser(userData); + localStorage.setItem('user', JSON.stringify(userData)); + }; + const value = { user, loading, login, register, logout, + updateUser, isAuthenticated: !!user, }; diff --git a/frontend/src/pages/RegisterPage.jsx b/frontend/src/pages/RegisterPage.jsx index 4691020..5193cab 100644 --- a/frontend/src/pages/RegisterPage.jsx +++ b/frontend/src/pages/RegisterPage.jsx @@ -195,7 +195,7 @@ const RegisterPage = () => { value={wsdcId} onChange={(e) => setWsdcId(e.target.value.replace(/\D/g, ''))} className="pl-10 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500" - placeholder="26997" + placeholder="12345" maxLength={10} /> {wsdcLoading && ( diff --git a/frontend/src/pages/VerifyEmailPage.jsx b/frontend/src/pages/VerifyEmailPage.jsx index b36f269..c33b215 100644 --- a/frontend/src/pages/VerifyEmailPage.jsx +++ b/frontend/src/pages/VerifyEmailPage.jsx @@ -1,11 +1,13 @@ import { useState, useEffect } from 'react'; import { useNavigate, useSearchParams, Link } from 'react-router-dom'; import { authAPI } from '../services/api'; +import { useAuth } from '../contexts/AuthContext'; import { Video, Mail, CheckCircle, XCircle, Loader2, ArrowRight } from 'lucide-react'; const VerifyEmailPage = () => { const [searchParams] = useSearchParams(); const navigate = useNavigate(); + const { updateUser } = useAuth(); const token = searchParams.get('token'); const [verificationMode, setVerificationMode] = useState(token ? 'token' : 'code'); @@ -31,6 +33,11 @@ const VerifyEmailPage = () => { try { const response = await authAPI.verifyEmailByToken(verificationToken); if (response.success) { + // Update user and token if returned + if (response.data?.user && response.data?.token) { + updateUser(response.data.user); + localStorage.setItem('token', response.data.token); + } setSuccess(true); } else { setError(response.error || 'Verification failed'); @@ -56,6 +63,11 @@ const VerifyEmailPage = () => { try { const response = await authAPI.verifyEmailByCode(email, code); if (response.success) { + // Update user and token if returned + if (response.data?.user && response.data?.token) { + updateUser(response.data.user); + localStorage.setItem('token', response.data.token); + } setSuccess(true); } else { setError(response.error || 'Verification failed');