Skip to content

Commit

Permalink
Changed the GET /verify tp PATCH /verify, added email templates
Browse files Browse the repository at this point in the history
  • Loading branch information
OomsOoms committed Aug 28, 2024
1 parent 98eccda commit d8ba3cf
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 39 deletions.
3 changes: 3 additions & 0 deletions server/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"jest.jestCommandLine": "npx jest --coverage"
}
44 changes: 43 additions & 1 deletion server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"express-promise-router": "^4.1.1",
"express-rate-limit": "^7.4.0",
"express-validator": "^7.0.1",
"handlebars": "^4.7.8",
"hcaptcha": "^0.2.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.2.0",
Expand Down
2 changes: 2 additions & 0 deletions server/public/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
While something like a content delivery network (CDN) might be used, I will just use the public folder, so any frontend assets, possibly including the css and js files will be stored within this folder.
I may change this in the future but for now the assets will be stored in this folder, this might all be very wrong im not sure but it seems to make sense to me, the public folder could be replaced by an external CDN
1 change: 1 addition & 0 deletions server/captcha.html → server/public/captcha.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- This file only exists in the public folder as a way to test the hCaptcha functionality when in prod -->
<!DOCTYPE html>
<html lang="en">

Expand Down
Binary file added server/public/images/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion server/src/api/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const { userService } = require('../services');
* @method GET
*/
async function verifyUser(req, res) {
const token = req.query.token;
const token = req.headers.authorization.split(' ')[1];
await userService.verifyUser(token);
res.status(200).json({ message: 'Email verified' });
}
Expand Down
70 changes: 40 additions & 30 deletions server/src/api/helpers/sendEmail.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
const nodemailer = require('nodemailer');
const handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');

const { logger } = require('../../config/logger');

module.exports = function (to, subject, text) {
const transporter = nodemailer.createTransport({
service: 'Gmail',
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
const mailOptions = {
from: process.env.FROM_ADDRESS, // If not set, it will be sent from the default email of the gmail account
to: to,
subject: subject,
text: text,
};
// If in development mode, do not send email
if (process.env.NODE_ENV === 'development') {
logger.debug('Email sent: ', mailOptions);
} else {
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
logger.error('Error sending email: ', error);
} else {
logger.debug('Email sent: ', info.response);
info.response;
}
});
}
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

const COMMON_DATA = {
frontendDomain: process.env.FRONTEND_DOMAIN,
backendDomain: process.env.BACKEND_DOMAIN,
};

async function sendEmail(to, subject, templateName, templateData) {
try {
const templatePath = path.join(__dirname, '../../views', `${templateName}.hbs`);

const source = fs.readFileSync(templatePath, 'utf8');
const template = handlebars.compile(source);
const html = template({ ...COMMON_DATA, ...templateData });

const mailOptions = {
from: process.env.FROM_ADDRESS,
to,
subject: subject,
html,
};

await transporter.sendMail(mailOptions);
logger.debug(
`Email sent to ${to}, with the subject ${subject} and template ${templateName} and the data ${JSON.stringify(templateData)}`
);
} catch (error) {
logger.error(error);
}
}

module.exports = sendEmail;
2 changes: 1 addition & 1 deletion server/src/api/routes/user.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const upload = multer({ storage: storage, limits: { fileSize: 1024 * 1024 * 5 }

// /api/users
router
.get('/verify', uc.verifyUser)
.patch('/verify', uc.verifyUser)
.post('/verify', uvr.requestVerification, validateRequest, uc.requestVerification)
.get('/', sessionAuth, uc.getAllUsers)
.get('/:username', uc.getUserByUsername)
Expand Down
9 changes: 3 additions & 6 deletions server/src/api/services/user.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ async function requestVerification(email) {
if (!user || !user.newEmail) throw Error.userNotFound('User not found or email already verified');

const token = generateJwt({ id: user._id }, { expiresIn: '24h' });
const verificationLink = `${process.env.DOMAIN}/api/users/verify?token=${token}`;
sendEmail(email, 'Verify your email', verificationLink);
sendEmail(email, 'Verify your email', 'verifyEmail', { username: user.username, token });
}

async function getAllUsers(id) {
Expand All @@ -51,8 +50,7 @@ async function registerUser(username, newEmail, password) {
const user = new User({ username, newEmail, password });
await user.save();
const token = generateJwt({ id: user._id }, { expiresIn: '24h' });
const verificationLink = `${process.env.DOMAIN}/api/users/verify?token=${token}`;
sendEmail(newEmail, 'Verify your email', verificationLink);
sendEmail(newEmail, 'Verify your email', 'verifyEmail', { username, token });
} catch (error) {
if (error.code === 11000) {
if (error.keyPattern.email || error.keyPattern.newEmail) throw Error.mongoConflictError('Email already exists');
Expand Down Expand Up @@ -134,8 +132,7 @@ async function updateUser(id, username, currentPassword, updatedFields, file) {
// Handle email change after user is saved to avoid duplicate key error
if (changes.newEmail) {
const token = generateJwt({ id: user._id }, { expiresIn: '24h' });
const verificationLink = `${process.env.DOMAIN}/api/users/verify?token=${token}`;
sendEmail(changes.newEmail, 'Verify your email', verificationLink);
sendEmail(changes.newEmail, 'Verify your email', 'verifyEmail', { username, token });
changes.newEmail.message = 'Email change requested, verify email';
}
} catch (error) {
Expand Down
Loading

0 comments on commit d8ba3cf

Please sign in to comment.