Skip to content

Commit e346008

Browse files
committed
Added verification email things to settings
1 parent 72fda5e commit e346008

File tree

10 files changed

+335
-6
lines changed

10 files changed

+335
-6
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const { PrismaClient } = require('@prisma/client');
2+
3+
const prisma = new PrismaClient();
4+
5+
async function checkUnverifiedUsers() {
6+
try {
7+
const users = await prisma.user.findMany({
8+
where: { emailVerified: false },
9+
select: {
10+
email: true,
11+
createdAt: true,
12+
welcomeEmailSentAt: true,
13+
}
14+
});
15+
16+
console.log('📧 Unverified users:');
17+
users.forEach((user, index) => {
18+
console.log(`${index + 1}. ${user.email}`);
19+
console.log(` Created: ${user.createdAt}`);
20+
console.log(` Welcome email sent: ${user.welcomeEmailSentAt || 'Not sent'}`);
21+
22+
if (user.welcomeEmailSentAt) {
23+
const hoursSinceEmail = (Date.now() - new Date(user.welcomeEmailSentAt).getTime()) / (1000 * 60 * 60);
24+
console.log(` Hours since email: ${hoursSinceEmail.toFixed(1)}`);
25+
if (hoursSinceEmail > 1) {
26+
console.log(` ⚠️ Token likely expired (sent ${hoursSinceEmail.toFixed(1)} hours ago)`);
27+
}
28+
}
29+
console.log('');
30+
});
31+
32+
console.log(`Total unverified users: ${users.length}`);
33+
} catch (error) {
34+
console.error('❌ Failed to check unverified users:', error);
35+
} finally {
36+
await prisma.$disconnect();
37+
}
38+
}
39+
40+
checkUnverifiedUsers();
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Utility script to manually verify a user's email (emergency use)
3+
* Usage: node scripts/manuallyVerifyEmail.js <email>
4+
*/
5+
6+
const { PrismaClient } = require('@prisma/client');
7+
8+
const prisma = new PrismaClient();
9+
10+
async function manuallyVerifyEmail(email) {
11+
try {
12+
console.log(`🔍 Looking for user with email: ${email}`);
13+
14+
const user = await prisma.user.findUnique({
15+
where: { email },
16+
select: {
17+
id: true,
18+
email: true,
19+
username: true,
20+
emailVerified: true,
21+
}
22+
});
23+
24+
if (!user) {
25+
console.error(`❌ User with email ${email} not found`);
26+
process.exit(1);
27+
}
28+
29+
console.log(`👤 Found user:`, user);
30+
31+
if (user.emailVerified) {
32+
console.log(`✅ User ${email} is already verified`);
33+
process.exit(0);
34+
}
35+
36+
console.log(`🔄 Manually verifying email for ${email}...`);
37+
38+
const updatedUser = await prisma.user.update({
39+
where: { email },
40+
data: { emailVerified: true },
41+
select: {
42+
id: true,
43+
email: true,
44+
username: true,
45+
emailVerified: true,
46+
}
47+
});
48+
49+
console.log(`✅ Successfully verified email:`, updatedUser);
50+
console.log(`🎉 ${email} can now access all features!`);
51+
52+
} catch (error) {
53+
console.error('❌ Failed to verify email:', error);
54+
process.exit(1);
55+
} finally {
56+
await prisma.$disconnect();
57+
}
58+
}
59+
60+
// Get email from command line arguments
61+
const email = process.argv[2];
62+
63+
if (!email) {
64+
console.error('❌ Please provide an email address');
65+
console.log('Usage: node scripts/manuallyVerifyEmail.js <email>');
66+
console.log('Example: node scripts/manuallyVerifyEmail.js [email protected]');
67+
process.exit(1);
68+
}
69+
70+
manuallyVerifyEmail(email);
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Utility script to resend verification emails to unverified users
3+
* Usage: node scripts/resendVerificationEmails.js
4+
*/
5+
6+
const { PrismaClient } = require('@prisma/client');
7+
const { generateEmailVerificationToken } = require('../src/utils/jwt');
8+
const { sendVerificationEmail } = require('../src/utils/email');
9+
10+
const prisma = new PrismaClient();
11+
12+
async function resendVerificationEmails() {
13+
try {
14+
console.log('🔍 Looking for unverified users...');
15+
16+
const unverifiedUsers = await prisma.user.findMany({
17+
where: { emailVerified: false },
18+
select: {
19+
id: true,
20+
email: true,
21+
createdAt: true,
22+
welcomeEmailSentAt: true,
23+
}
24+
});
25+
26+
if (unverifiedUsers.length === 0) {
27+
console.log('✅ No unverified users found!');
28+
return;
29+
}
30+
31+
console.log(`📧 Found ${unverifiedUsers.length} unverified users. Resending verification emails...`);
32+
33+
for (const user of unverifiedUsers) {
34+
try {
35+
console.log(`📤 Sending verification email to ${user.email}...`);
36+
37+
// Generate new verification token (24h expiry)
38+
const verificationToken = generateEmailVerificationToken(user.id);
39+
40+
// Send verification email
41+
await sendVerificationEmail(user.email, verificationToken);
42+
43+
// Update the welcomeEmailSentAt timestamp
44+
await prisma.user.update({
45+
where: { id: user.id },
46+
data: {
47+
welcomeEmailSentAt: new Date(),
48+
welcomeEmailSent: true
49+
}
50+
});
51+
52+
console.log(`✅ Verification email sent to ${user.email}`);
53+
54+
// Add delay to avoid rate limits
55+
await new Promise(resolve => setTimeout(resolve, 2000));
56+
57+
} catch (error) {
58+
console.error(`❌ Failed to send email to ${user.email}:`, error.message);
59+
}
60+
}
61+
62+
console.log('🎉 Finished resending verification emails!');
63+
console.log('📝 Note: New tokens are valid for 24 hours instead of 1 hour.');
64+
65+
} catch (error) {
66+
console.error('❌ Failed to resend verification emails:', error);
67+
} finally {
68+
await prisma.$disconnect();
69+
}
70+
}
71+
72+
resendVerificationEmails();
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Test script to verify the resend verification email endpoint works
3+
* Usage: node scripts/testResendEndpoint.js
4+
*/
5+
6+
const { generateEmailVerificationToken, verifyEmailVerificationToken } = require('../src/utils/jwt');
7+
8+
function testResendEndpoint() {
9+
try {
10+
console.log('🧪 Testing resend verification email functionality...');
11+
12+
// Test token generation and verification
13+
const testUserId = 'test-user-123';
14+
const token = generateEmailVerificationToken(testUserId);
15+
console.log('✅ Generated email verification token');
16+
17+
const { userId } = verifyEmailVerificationToken(token);
18+
console.log('✅ Verified email verification token');
19+
20+
if (userId === testUserId) {
21+
console.log('🎉 Email verification token system is working!');
22+
console.log('📧 The resend endpoint should work correctly');
23+
console.log('⏰ Tokens are valid for 24 hours');
24+
console.log('');
25+
console.log('📝 To test the full flow:');
26+
console.log('1. Deploy this code to production');
27+
console.log('2. User goes to Settings page');
28+
console.log('3. User sees email verification status');
29+
console.log('4. User clicks "Resend Email" if not verified');
30+
console.log('5. User receives new email with 24-hour token');
31+
} else {
32+
console.error('❌ Token verification failed - user ID mismatch');
33+
}
34+
35+
} catch (error) {
36+
console.error('❌ Test failed:', error.message);
37+
}
38+
}
39+
40+
testResendEndpoint();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Test script to verify the new email verification tokens work correctly
3+
*/
4+
5+
const { generateEmailVerificationToken, verifyEmailVerificationToken } = require('../src/utils/jwt');
6+
7+
function testVerificationTokens() {
8+
try {
9+
console.log('🧪 Testing email verification tokens...');
10+
11+
const testUserId = 'test-user-123';
12+
13+
// Generate token
14+
console.log('📝 Generating verification token...');
15+
const token = generateEmailVerificationToken(testUserId);
16+
console.log('✅ Token generated:', token.substring(0, 20) + '...');
17+
18+
// Verify token
19+
console.log('🔍 Verifying token...');
20+
const { userId } = verifyEmailVerificationToken(token);
21+
console.log('✅ Token verified! User ID:', userId);
22+
23+
if (userId === testUserId) {
24+
console.log('🎉 Email verification system is working correctly!');
25+
console.log('📅 Tokens are valid for 24 hours');
26+
} else {
27+
console.error('❌ Token verification failed - user ID mismatch');
28+
}
29+
30+
} catch (error) {
31+
console.error('❌ Token test failed:', error.message);
32+
}
33+
}
34+
35+
testVerificationTokens();

backend/src/controllers/authController.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,36 @@ export class AuthController {
274274
}
275275
}
276276

277+
async resendVerificationEmail(req: Request, res: Response) {
278+
try {
279+
if (!req.user) {
280+
return res.status(401).json({
281+
error: {
282+
code: 'UNAUTHORIZED',
283+
message: 'Authentication required',
284+
},
285+
timestamp: new Date().toISOString(),
286+
path: req.path,
287+
});
288+
}
289+
290+
await authService.resendVerificationEmail(req.user.userId);
291+
292+
res.json({
293+
message: 'Verification email sent successfully',
294+
});
295+
} catch (error: any) {
296+
res.status(400).json({
297+
error: {
298+
code: 'RESEND_VERIFICATION_FAILED',
299+
message: error.message,
300+
},
301+
timestamp: new Date().toISOString(),
302+
path: req.path,
303+
});
304+
}
305+
}
306+
277307
async logout(req: Request, res: Response) {
278308
// For JWT tokens, logout is handled client-side by removing the token
279309
// In a more complex setup, you might maintain a blacklist of tokens

backend/src/routes/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ router.get('/verify-email', authController.verifyEmail);
1616
// Protected routes
1717
router.post('/logout', authenticateToken, authController.logout);
1818
router.get('/profile', authenticateToken, authController.getProfile);
19+
router.post('/resend-verification', authenticateToken, authController.resendVerificationEmail);
1920

2021
// Debug routes
2122
router.get('/check-token', authController.checkToken);

backend/src/services/authService.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import bcrypt from 'bcryptjs';
22
import { PrismaClient } from '@prisma/client';
33
import { RegisterRequest, LoginRequest, AuthResponse } from '../types/auth';
4-
import { generateTokens, generatePasswordResetToken } from '../utils/jwt';
4+
import { generateTokens, generatePasswordResetToken, generateEmailVerificationToken, verifyEmailVerificationToken } from '../utils/jwt';
55
import { sendVerificationEmail, sendPasswordResetEmail } from '../utils/email';
66

77
const prisma = new PrismaClient();
@@ -72,7 +72,7 @@ export class AuthService {
7272

7373
// Send verification email (don't await to avoid blocking)
7474
if (process.env.EMAIL_HOST && process.env.EMAIL_USER) {
75-
const verificationToken = generatePasswordResetToken(user.id);
75+
const verificationToken = generateEmailVerificationToken(user.id);
7676
sendVerificationEmail(user.email, verificationToken)
7777
.then(async () => {
7878
// Mark welcome email as sent
@@ -228,8 +228,7 @@ export class AuthService {
228228

229229
async verifyEmail(token: string): Promise<void> {
230230
try {
231-
const { verifyPasswordResetToken } = await import('../utils/jwt');
232-
const { userId } = verifyPasswordResetToken(token);
231+
const { userId } = verifyEmailVerificationToken(token);
233232

234233
await prisma.user.update({
235234
where: { id: userId },
@@ -240,6 +239,36 @@ export class AuthService {
240239
}
241240
}
242241

242+
async resendVerificationEmail(userId: string): Promise<void> {
243+
const user = await prisma.user.findUnique({
244+
where: { id: userId },
245+
select: { email: true, emailVerified: true }
246+
});
247+
248+
if (!user) {
249+
throw new Error('User not found');
250+
}
251+
252+
if (user.emailVerified) {
253+
throw new Error('Email is already verified');
254+
}
255+
256+
// Generate new verification token
257+
const verificationToken = generateEmailVerificationToken(userId);
258+
259+
// Send verification email
260+
await sendVerificationEmail(user.email, verificationToken);
261+
262+
// Update the welcomeEmailSentAt timestamp
263+
await prisma.user.update({
264+
where: { id: userId },
265+
data: {
266+
welcomeEmailSentAt: new Date(),
267+
welcomeEmailSent: true
268+
}
269+
});
270+
}
271+
243272
async getUserById(userId: string) {
244273
return await prisma.user.findUnique({
245274
where: { id: userId },

backend/src/services/welcomeEmailService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PrismaClient } from '@prisma/client';
22
import { sendVerificationEmail } from '../utils/email';
3-
import { generatePasswordResetToken } from '../utils/jwt';
3+
import { generateEmailVerificationToken } from '../utils/jwt';
44

55
const prisma = new PrismaClient();
66

@@ -43,7 +43,7 @@ export class WelcomeEmailService {
4343

4444
for (const user of usersWithoutWelcomeEmail) {
4545
try {
46-
const verificationToken = generatePasswordResetToken(user.id);
46+
const verificationToken = generateEmailVerificationToken(user.id);
4747

4848
// Add longer delay between emails to avoid rate limits
4949
if (successCount > 0) {

0 commit comments

Comments
 (0)