Skip to content

Commit

Permalink
started adding adding email verification, lots to do still
Browse files Browse the repository at this point in the history
  • Loading branch information
OomsOoms committed May 24, 2024
1 parent c8718b9 commit e622179
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 11 deletions.
4 changes: 4 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
DATABASE_URI=mongodb://localhost:27017/DatabaseName
SECRET_KEY=SOME_SECRET_KEY
PORT=8000
SALT_ROUNDS=10
DOMAIN=http://localhost:8000
GMAIL_APP_KEY=xxx xxx xxx xxx
GMAIL_EMAIL=[email protected]
9 changes: 9 additions & 0 deletions 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 @@ -23,6 +23,7 @@
"express-validator": "^7.0.1",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.2.0",
"nodemailer": "^6.9.13",
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^5.0.0"
},
Expand Down
11 changes: 9 additions & 2 deletions server/src/api/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ async function getAllUsers(req, res) {
}

async function verifyUser(req, res) {
// Get the id from the verified token
// Get the id from the verified param token
const { id } = req.user;
console.log(id);
await userService.verifyUser(id);
res.status(200).json({ message: 'Email verified' });
}

async function requestVerification(req, res) {
// Get the id from the verified token
const { email } = req.body;
await userService.requestVerification(email);
res.status(200).json({ message: 'Verification email sent' });
}

/**
* @desc Get current user (From id in token)
* @method GET
Expand Down Expand Up @@ -93,6 +99,7 @@ module.exports = {
registerUser,
getAllUsers,
verifyUser,
requestVerification,
getCurrentUser,
updateUser,
deleteUser,
Expand Down
2 changes: 2 additions & 0 deletions server/src/api/helpers/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const Error = require('./error');
const generateJwt = require('./generateJwt');
const sendEmail = require('./sendEmail');

module.exports = {
...require('./passwordUtils'),
generateJwt,
Error,
sendEmail,
};

/**
Expand Down
29 changes: 29 additions & 0 deletions server/src/api/helpers/sendEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const nodemailer = require('nodemailer');

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,
},
});
console.log(process.env.EMAIL_USER, process.env.EMAIL_PASS);
const mailOptions = {
//from: "", // If not set, it will be sent from the default email of the gmail account
to: to,
subject: subject,
text: text,
};

transporter.sendMail(mailOptions, (error, info) => {
if (error) {
console.error('Error sending email: ', error);
} else {
console.log('Email sent: ', info.response);
}
});
};
9 changes: 7 additions & 2 deletions server/src/api/middlewares/verifyJwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ async function verifyJwt(req, res, next) {
try {
const decoded = jwt.verify(token, process.env.SECRET_KEY);
const user = await User.findById(decoded.id);
// remove this as its not stateless and defeats the purpose of JWT
if (!user || user.passwordChangedAt > decoded.iat) {
return res.status(401).json({ error: 'Token has expired' });
}
req.user = decoded;
console.log(user);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
} catch (err) {
if (err instanceof jwt.TokenExpiredError) {
return res.status(401).json({ error: 'Token has expired' });
} else {
return res.status(401).json({ error: 'Invalid token' });
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion server/src/api/routes/user.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ router
userController.registerUser
);

// Probably make its own jwt function since its a token param
router
.route('/verify')
.get(verifyJwt, userController.verifyUser)
.post(userController.requestVerification);

router.use(verifyJwt);
router
.route('/me')
Expand All @@ -22,6 +28,5 @@ router
)
.put(userValidator.updateUser, validateRequest, userController.updateUser)
.delete(userValidator.deleteUser, validateRequest, userController.deleteUser);
router.route('/verify').get(userController.verifyUser);

module.exports = router;
1 change: 1 addition & 0 deletions server/src/api/services/session.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { generateJwt } = require('../helpers');
const { comparePasswords } = require('../helpers');
const { Error } = require('../helpers');

// should send a verification email if the user is not active
async function loginUser(username, email, password) {
const user = await User.findOne({ $or: [{ username }, { email }] });
if (!user || !(await comparePasswords(password, user.password))) {
Expand Down
21 changes: 16 additions & 5 deletions server/src/api/services/user.service.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
const { User } = require('../models');
const { generateJwt } = require('../helpers');
const { comparePasswords } = require('../helpers');
const { generateJwt, comparePasswords, sendEmail } = require('../helpers');
const { Error } = require('../helpers');

// if a user hasnt verified their email within a certain time frame, they should be deleted
// jwt token method im using might not be the best for this so i might need to change it
async function registerUser(username, email, password) {
try {
const user = new User({ username, email, password });
await user.save();
const token = generateJwt({ id: user._id });
const token = generateJwt({ id: user._id }, { expiresIn: '10m' });
const verificationLink = `${process.env.DOMAIN}/api/users/verify?token=${token}`;
console.log(verificationLink); // would be emailed but ill leave that for now
sendEmail(email, 'Verify your email', verificationLink);

return true;
} catch (error) {
Expand Down Expand Up @@ -39,7 +40,16 @@ async function verifyUser(id) {
const user = await User.findById(id);
user.active = true;
await user.save();
console.log('shfjsdjfdsjsjjhjsjdfsjdfhjdfshjdfshjdsfjsdfhskdfhsdkjfh');
}

async function requestVerification(email) {
const user = await User.findOne({ email });
if (!user || user.active) {
throw Error.userNotFound('User not found or already verified');
}
const token = generateJwt({ id: user._id });
const verificationLink = `${process.env.DOMAIN}/api/users/verify?token=${token}`;
console.log(verificationLink); // would be emailed but ill leave that for now
}

async function getCurrentUser(id) {
Expand Down Expand Up @@ -103,6 +113,7 @@ module.exports = {
registerUser,
getAllUsers,
verifyUser,
requestVerification,
getCurrentUser,
updateUser,
deleteUser,
Expand Down
6 changes: 5 additions & 1 deletion server/src/config/corsOptions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const cors = require('cors');

const corsOptions = {
origin: ['https://localhost:3000', 'https://localhost:5500'],
origin: [
'https://localhost:3000',
'https://localhost:5500',
'https://s84dlvcl-8000.uks1.devtunnels.ms/',
],
optionSuccessStatus: 200,
};

Expand Down
20 changes: 20 additions & 0 deletions server/src/config/swagger/pathsComponents.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,19 @@ schemas:
type: string
location:
type: string

verificationEmailSent:
type: object
properties:
message:
type: string

verified:
type: object
properties:
message:
type: string

conflict:
type: object
properties:
Expand Down Expand Up @@ -125,3 +138,10 @@ requestBodies:
newUsername: john_doe
newEmail: [email protected]
newPassword: password
ResendVerification:
type: object
properties:
email:
type: string
example:
email: [email protected]
4 changes: 4 additions & 0 deletions server/src/config/swagger/swagger.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ const options = {
url: 'https://nettleship.net',
description: 'Production server',
},
{
url: 'https://s84dlvcl-8000.uks1.devtunnels.ms/',
description: 'DevTunnel server',
},
],
tags: {
...yaml.load(tags),
Expand Down
24 changes: 24 additions & 0 deletions server/src/config/swagger/userPaths.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,30 @@
$ref: "#/components/schemas/invalidCredentials"
/api/users/verify:
post:
summary: Resend verification email
description: Resend the verification email to the user. This is useful if the user did not receive the email or if the link expired.
tags:
- users
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/requestBodies/ResendVerification"
responses:
200:
description: Verification email sent successfully
content:
application/json:
schema:
$ref: "#/components/schemas/verificationEmailSent"
400:
description: Bad request, the email is invalid or the account is already verified
content:
application/json:
schema:
$ref: "#/components/schemas/badRequest"
get:
description: Verify a user account
summary: Verify a user account
tags:
Expand Down

0 comments on commit e622179

Please sign in to comment.