Skip to content

Commit 0389f5d

Browse files
committed
# Commit Message
**Refactor Email Verification Process and Update Configuration** - **Update `.gitignore`:** - Added `**/debug/**` to ignore debug directories. - Added `.DS_Store` to ignore macOS system files. - **Configuration Schema (`src/configs/config.schema.js`):** - Removed `SENDGRID_VERIFICATION_TEMPLATE_ID` field. - **User Schema (`src/domains/user/schema.js`):** - Added `verificationEmailSentAt` field to track when the verification email was last sent. - **User Service (`src/domains/user/service.js`):** - Changed verification token expiry from 1 minute to 24 hours. - Updated logic to use `verificationEmailSentAt` for rate limiting resend attempts. - Enhanced logging for debug mode. - **Email Service (`src/libraries/email/emailService.js`):** - Simplified debug mode configuration. - Added `loadTemplate` function to compile HTML email templates. - Refactored `sendVerificationEmail` to use HTML templates instead of SendGrid dynamic templates. - Improved debug email saving with HTML content.
1 parent cbbee07 commit 0389f5d

File tree

6 files changed

+101
-21
lines changed

6 files changed

+101
-21
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,7 @@ dist
130130
config.development.json
131131
config.production.json
132132
config.test.json
133+
134+
# Debug emails
135+
**/debug/**
136+
.DS_Store

src/configs/config.schema.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ const schema = Joi.object({
3636
// SendGrid Configuration
3737
SENDGRID_API_KEY: Joi.string().required(),
3838
SENDGRID_FROM_EMAIL: Joi.string().email().required(),
39-
SENDGRID_VERIFICATION_TEMPLATE_ID: Joi.string().required(),
4039
});
4140

4241
module.exports = schema;

src/domains/user/schema.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ const schema = new mongoose.Schema({
9696
verificationTokenExpiry: {
9797
type: Date,
9898
},
99+
verificationEmailSentAt: {
100+
type: Date,
101+
},
99102

100103
// Auth and status flags
101104
isDemo: {

src/domains/user/service.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@ const canResendVerification = async (userId) => {
231231
throw new AppError('already-verified', 'Email is already verified', 400);
232232
}
233233

234-
// Check if last token was generated less than 1 minute ago
235-
if (user.verificationTokenExpiry) {
236-
const timeSinceLastEmail = new Date() - user.verificationTokenExpiry;
237-
const oneMinuteInMs = 1 * 60 * 1000;
234+
// Check if last verification email was sent less than 1 minute ago
235+
if (user.verificationEmailSentAt) {
236+
const timeSinceLastEmail = new Date() - user.verificationEmailSentAt;
237+
const oneMinuteInMs = 60000; // 1 minute in milliseconds
238238

239239
if (timeSinceLastEmail < oneMinuteInMs) {
240240
const remainingSeconds = Math.ceil((oneMinuteInMs - timeSinceLastEmail) / 1000);
@@ -261,12 +261,14 @@ const refreshVerificationToken = async (email) => {
261261

262262
// Generate new verification token
263263
const verificationToken = generateVerificationToken();
264-
const verificationTokenExpiry = new Date(Date.now() + 1 * 60 * 1000); // 1 minute
264+
const verificationTokenExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
265+
const verificationEmailSentAt = new Date();
265266

266-
// Update user with new token
267+
// Update user with new token and email sent timestamp
267268
await updateById(user._id, {
268269
verificationToken,
269270
verificationTokenExpiry,
271+
verificationEmailSentAt,
270272
updatedAt: new Date()
271273
});
272274

src/libraries/email/emailService.js

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const path = require('path');
88
sgMail.setApiKey(config.SENDGRID_API_KEY);
99

1010
// Debug mode configuration
11-
const isDebugMode = process.env.NODE_ENV === 'development' && process.env.EMAIL_DEBUG === 'true';
11+
const isDebugMode = process.env.EMAIL_DEBUG === 'true';
12+
console.log('isDebugMode', isDebugMode, 'process.env.NODE_ENV', process.env.NODE_ENV, 'process.env.EMAIL_DEBUG', process.env.EMAIL_DEBUG);
1213
const debugDir = path.join(__dirname, 'debug');
1314

1415
// Ensure debug directory exists
@@ -38,36 +39,54 @@ const saveDebugEmail = async (email, content) => {
3839
}
3940
};
4041

42+
// Function to load and compile email template
43+
const loadTemplate = async (templateName, replacements) => {
44+
const templatePath = path.join(__dirname, 'templates', templateName);
45+
let template = await fs.readFile(templatePath, 'utf8');
46+
47+
// Replace all placeholders with actual values
48+
Object.keys(replacements).forEach(key => {
49+
const regex = new RegExp(`{{${key}}}`, 'g');
50+
template = template.replace(regex, replacements[key]);
51+
});
52+
53+
return template;
54+
};
55+
4156
const sendVerificationEmail = async (email, verificationToken) => {
4257
const verificationLink = `${config.CLIENT_HOST}/verify-email?token=${verificationToken}`;
43-
58+
4459
try {
4560
await ensureDebugDir();
46-
61+
62+
// Load and compile the HTML template
63+
const htmlContent = await loadTemplate('verification2.html', {
64+
appName: 'CommitStreams',
65+
userEmail: email,
66+
verificationLink: verificationLink,
67+
expiryHours: '24'
68+
});
69+
4770
const msg = {
4871
to: email,
4972
from: config.SENDGRID_FROM_EMAIL,
50-
templateId: config.SENDGRID_VERIFICATION_TEMPLATE_ID,
51-
dynamicTemplateData: {
52-
appName: 'CommitStreams',
53-
userEmail: email,
54-
verificationLink: verificationLink,
55-
expiryHours: '1'
56-
}
73+
subject: 'Verify your email address',
74+
html: htmlContent,
5775
};
5876

5977
if (isDebugMode) {
60-
// In debug mode, save email data locally
78+
// In debug mode, save email locally
6179
await saveDebugEmail(email, `
6280
<h2>Email Debug Information</h2>
6381
<pre>
6482
To: ${msg.to}
6583
From: ${msg.from}
66-
Template ID: ${msg.templateId}
67-
Dynamic Template Data: ${JSON.stringify(msg.dynamicTemplateData, null, 2)}
84+
Subject: ${msg.subject}
6885
</pre>
86+
<hr>
87+
${htmlContent}
6988
`);
70-
logger.info('Debug mode: Email data saved locally', { email });
89+
logger.info('Debug mode: Email saved locally', { email });
7190
} else {
7291
// In production mode, send via SendGrid
7392
await sgMail.send(msg);
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Verify your email address</title>
6+
<style>
7+
body {
8+
font-family: Arial, Helvetica, sans-serif;
9+
background-color: #f4f4f4;
10+
color: #333333;
11+
}
12+
.container {
13+
max-width: 600px;
14+
margin: 0 auto;
15+
padding: 20px;
16+
background-color: #ffffff;
17+
}
18+
h1 {
19+
color: #007bff;
20+
}
21+
.button {
22+
display: inline-block;
23+
padding: 10px 20px;
24+
background-color: #007bff;
25+
color: #ffffff;
26+
text-decoration: none;
27+
border-radius: 4px;
28+
}
29+
.footer {
30+
margin-top: 20px;
31+
text-align: center;
32+
color: #666666;
33+
font-size: 12px;
34+
}
35+
</style>
36+
</head>
37+
<body>
38+
<div class="container">
39+
<h1>Welcome to {{appName}}!</h1>
40+
<p>Hi there,</p>
41+
<p>Thanks for signing up with {{appName}}. To complete your registration, we just need you to verify your email address:</p>
42+
<p><a href="{{verificationLink}}" class="button">Verify Email Address</a></p>
43+
<p>If the button above doesn't work, copy and paste this link into your browser:</p>
44+
<p><a href="{{verificationLink}}">{{verificationLink}}</a></p>
45+
<p>This link will expire in {{expiryHours}} hours, so please verify soon.</p>
46+
<p>If you did not sign up for an account with {{appName}}, you can safely ignore this email.</p>
47+
<p>Thanks,<br>The {{appName}} Team</p>
48+
<div class="footer">
49+
This email was sent to {{userEmail}}. If you'd rather not receive this kind of email, you can <a href="#">unsubscribe</a>.
50+
</div>
51+
</div>
52+
</body>
53+
</html>

0 commit comments

Comments
 (0)