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.
This commit is contained in:
Radosław Gierwiało
2025-11-13 18:59:28 +01:00
parent 3ff966defc
commit 27ee0ae365
5 changed files with 43 additions and 5 deletions

View File

@@ -21,8 +21,8 @@ JWT_EXPIRES_IN=24h
# AWS SES - Production credentials # AWS SES - Production credentials
# BEST PRACTICE: Use IAM roles instead of access keys # BEST PRACTICE: Use IAM roles instead of access keys
AWS_REGION=us-east-1 AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIASOH3DHHDA557Z5N7 AWS_ACCESS_KEY_ID=AK.........
AWS_SECRET_ACCESS_KEY=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 AWS_SECRET_ACCESS_KEY=change-it
SES_FROM_EMAIL=noreply@spotlight.cam SES_FROM_EMAIL=noreply@spotlight.cam
SES_FROM_NAME=spotlight.cam SES_FROM_NAME=spotlight.cam

View File

@@ -191,7 +191,7 @@ async function verifyEmailByToken(req, res, next) {
} }
// Update user - mark as verified and clear tokens // Update user - mark as verified and clear tokens
await prisma.user.update({ const updatedUser = await prisma.user.update({
where: { id: user.id }, where: { id: user.id },
data: { data: {
emailVerified: true, emailVerified: true,
@@ -208,9 +208,19 @@ async function verifyEmailByToken(req, res, next) {
console.error('Failed to send welcome email:', emailError); 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({ res.status(200).json({
success: true, success: true,
message: 'Email verified successfully!', message: 'Email verified successfully!',
data: {
user: userWithoutPassword,
token: jwtToken,
},
}); });
} catch (error) { } catch (error) {
next(error); next(error);
@@ -261,7 +271,7 @@ async function verifyEmailByCode(req, res, next) {
} }
// Update user - mark as verified and clear tokens // Update user - mark as verified and clear tokens
await prisma.user.update({ const updatedUser = await prisma.user.update({
where: { id: user.id }, where: { id: user.id },
data: { data: {
emailVerified: true, emailVerified: true,
@@ -278,9 +288,19 @@ async function verifyEmailByCode(req, res, next) {
console.error('Failed to send welcome email:', emailError); 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({ res.status(200).json({
success: true, success: true,
message: 'Email verified successfully!', message: 'Email verified successfully!',
data: {
user: userWithoutPassword,
token: jwtToken,
},
}); });
} catch (error) { } catch (error) {
next(error); next(error);

View File

@@ -65,12 +65,18 @@ export const AuthProvider = ({ children }) => {
setUser(null); setUser(null);
}; };
const updateUser = (userData) => {
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
};
const value = { const value = {
user, user,
loading, loading,
login, login,
register, register,
logout, logout,
updateUser,
isAuthenticated: !!user, isAuthenticated: !!user,
}; };

View File

@@ -195,7 +195,7 @@ const RegisterPage = () => {
value={wsdcId} value={wsdcId}
onChange={(e) => setWsdcId(e.target.value.replace(/\D/g, ''))} 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" 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} maxLength={10}
/> />
{wsdcLoading && ( {wsdcLoading && (

View File

@@ -1,11 +1,13 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate, useSearchParams, Link } from 'react-router-dom'; import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { authAPI } from '../services/api'; import { authAPI } from '../services/api';
import { useAuth } from '../contexts/AuthContext';
import { Video, Mail, CheckCircle, XCircle, Loader2, ArrowRight } from 'lucide-react'; import { Video, Mail, CheckCircle, XCircle, Loader2, ArrowRight } from 'lucide-react';
const VerifyEmailPage = () => { const VerifyEmailPage = () => {
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const navigate = useNavigate(); const navigate = useNavigate();
const { updateUser } = useAuth();
const token = searchParams.get('token'); const token = searchParams.get('token');
const [verificationMode, setVerificationMode] = useState(token ? 'token' : 'code'); const [verificationMode, setVerificationMode] = useState(token ? 'token' : 'code');
@@ -31,6 +33,11 @@ const VerifyEmailPage = () => {
try { try {
const response = await authAPI.verifyEmailByToken(verificationToken); const response = await authAPI.verifyEmailByToken(verificationToken);
if (response.success) { 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); setSuccess(true);
} else { } else {
setError(response.error || 'Verification failed'); setError(response.error || 'Verification failed');
@@ -56,6 +63,11 @@ const VerifyEmailPage = () => {
try { try {
const response = await authAPI.verifyEmailByCode(email, code); const response = await authAPI.verifyEmailByCode(email, code);
if (response.success) { 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); setSuccess(true);
} else { } else {
setError(response.error || 'Verification failed'); setError(response.error || 'Verification failed');