From 625a9cbf890dcb147f8c860ff4ad320410a0034d Mon Sep 17 00:00:00 2001 From: MantasMikal Date: Fri, 5 Feb 2021 21:10:11 +0000 Subject: [PATCH] feat: adds email verification --- config/dev.json | 2 +- config/production.json | 4 +-- helpers/emailTemplates.js | 7 +++++ helpers/mailer.js | 46 +++++++++++++++++++++++++++++++ models/accountVerificationCode.js | 22 +++++++++++++++ models/user.js | 4 +++ package-lock.json | 5 ++++ package.json | 1 + routes/register.js | 21 ++++++++++++-- routes/verification.js | 26 +++++++++++++++++ server.js | 3 +- 11 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 helpers/emailTemplates.js create mode 100644 helpers/mailer.js create mode 100644 models/accountVerificationCode.js create mode 100644 routes/verification.js diff --git a/config/dev.json b/config/dev.json index a5de5d0..b665c8d 100644 --- a/config/dev.json +++ b/config/dev.json @@ -3,7 +3,7 @@ "name": "WebAuthn UX Test API - Dev Mode", "port": 4000, "mode": "development", - "siteUrl": "https://localhost:3000", + "siteUrl": "https://localhost:3000/", "protocol": "http", "serverUrl": "localhost" } diff --git a/config/production.json b/config/production.json index 5e22cd5..2785b22 100644 --- a/config/production.json +++ b/config/production.json @@ -3,7 +3,7 @@ "name": "WebAuthn UX Test API - Prod Mode", "mode": "production", "protocol": "https", - "siteUrl": "https://web-authn.studio", - "serverUrl": "api.web-authn.studio" + "siteUrl": "https://fido2.app", + "serverUrl": "api.fido2.app" } } \ No newline at end of file diff --git a/helpers/emailTemplates.js b/helpers/emailTemplates.js new file mode 100644 index 0000000..6f4a695 --- /dev/null +++ b/helpers/emailTemplates.js @@ -0,0 +1,7 @@ +export const accountVerificationTemplate = (url) => ({ + subject: `🎉 Successfully created a new account on Fakebook`, + bodyHtml: ` +

Thanks for helping me with the study 😎

+ Verify your account by clicking here + ` +}); \ No newline at end of file diff --git a/helpers/mailer.js b/helpers/mailer.js new file mode 100644 index 0000000..b8973ae --- /dev/null +++ b/helpers/mailer.js @@ -0,0 +1,46 @@ +/** + * Mailer service to handle email sending + * @module service/mailer + */ + +import nodemailer from "nodemailer"; + +/** + * Sends an email + * @param {Array} recipients email addresses of recipients + * @param {String} subject email subject + * @param {String} bodyHtml email body + * @returns {Object} status of the email + */ +const mailTo = async (recipients, { subject, bodyHtml }) => { + const transporter = nodemailer.createTransport({ + host: "mail.privateemail.com", + port: 465, + auth: { + user: process.env.MAILER_USER, + pass: process.env.MAILER_PASSWORD, + }, + }); + const data = { + from: process.env.MAILER_USER, + to: recipients, + subject: subject, + html: bodyHtml, + }; + return await sendEmail(transporter, data); +}; + +/** + * Wrapper for sending emails that returns a promise + * @param {Object} transporter Nodemailer transporter object + * @param {Object} data data to be send + */ +const sendEmail = async (transporter, data) => + new Promise((resolve, reject) => { + transporter.sendMail(data, (err, info) => { + if (err) reject(err); + resolve(info); + }); + }); + +export default mailTo; diff --git a/models/accountVerificationCode.js b/models/accountVerificationCode.js new file mode 100644 index 0000000..61bbfe1 --- /dev/null +++ b/models/accountVerificationCode.js @@ -0,0 +1,22 @@ +import mongoose from "../helpers/db.js"; + +const AccountVerificationCode = mongoose.model( + "AccountVerificationCode", + new mongoose.Schema({ + email: { + type: String, + required: true, + }, + code: { + type: String, + required: true, + }, + dateCreated: { + type: Date, + default: Date.now(), + expires: 600, + }, + }) +); + +export default AccountVerificationCode; diff --git a/models/user.js b/models/user.js index 95b7be5..eaeaa9b 100644 --- a/models/user.js +++ b/models/user.js @@ -34,6 +34,10 @@ const User = mongoose.model( }, userAgent: { type: Object + }, + status: { + type: String, + default: 'pending' } }) ); diff --git a/package-lock.json b/package-lock.json index b30a3f4..5d0bf57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2128,6 +2128,11 @@ "webcrypto-core": "^0.1.27" } }, + "nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-89ps+SBGpo0D4Bi5ZrxcrCiRFaMmkCt+gItMXQGzEtZVR3uAD3QAQIDoxTWnx3ky0Dwwy/dhFrQ+6NNGXpw/qQ==" + }, "nodemon": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", diff --git a/package.json b/package.json index f434733..1e96693 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "glob": "^7.1.6", "helmet": "^4.4.1", "mongoose": "^5.11.12", + "nodemailer": "^6.4.17", "nodemon": "^2.0.7", "xss-clean": "^0.1.1" }, diff --git a/routes/register.js b/routes/register.js index d1c4eb6..4c36b3e 100644 --- a/routes/register.js +++ b/routes/register.js @@ -6,6 +6,9 @@ import bcrypt from "bcrypt"; import cfg from "config"; import User from "../models/user.js"; import { verifyPassword } from "../helpers/utils.js"; +import { accountVerificationTemplate } from "../helpers/emailTemplates.js"; +import mailTo from "../helpers/mailer.js"; +import AccountVerificationCode from "../models/accountVerificationCode.js"; const mode = process.env.NODE_ENV || "dev"; const config = cfg.get(mode); @@ -81,6 +84,7 @@ router.post("/register", async (req, res) => { req.session.publicKey = regResult.authnrData.get("credentialPublicKeyPem"); req.session.prevCounter = regResult.authnrData.get("counter"); const hash = bcrypt.hashSync(password, 10); + const user = await User.create({ id: req.session.userHandle, credentialId: base64RawId, @@ -91,10 +95,22 @@ router.post("/register", async (req, res) => { regDuration: regDuration, userAgent: userAgent, }); - user.save(); - console.log("Created new account for: ", email); + const secretVerificationCode = crypto.randomBytes(32).toString("hex") + const verificationCode = await AccountVerificationCode.create({ + email: user.email, + code: secretVerificationCode, + }); + + verificationCode.save(); + console.log("Created new account for: ", email); + await mailTo( + [email], + accountVerificationTemplate( + `${config.siteUrl}verification/verify-account/${user.email}/${secretVerificationCode}` + ) + ); res.json({ status: "ok" }); } catch (e) { console.log("error", e); @@ -125,7 +141,6 @@ router.post("/authenticate", async (req, res) => { const user = await User.findOne({ email }); if (user) { if (verifyPassword(user, password)) { - console.log("All good"); return res.status(200).json({ status: "ok" }); } else return res.status(401).json({ error: "Incorrect login details" }); } else return res.status(401).json({ error: "Incorrect login details" }); diff --git a/routes/verification.js b/routes/verification.js new file mode 100644 index 0000000..7a544aa --- /dev/null +++ b/routes/verification.js @@ -0,0 +1,26 @@ +import express from "express"; +// import cfg from "config"; +import User from "../models/user.js"; +import AccountVerificationCode from "../models/accountVerificationCode.js"; + +const mode = process.env.NODE_ENV || "dev"; +// const config = cfg.get(mode); +const router = express.Router(); + +router.post("/verify-account", async (req, res) => { + const { email, secretCode } = req.body; + try { + const verificationCode = await AccountVerificationCode.findOne({ code: secretCode }); + const user = await User.findOne({ email: email }); + if ((user && verificationCode) && verificationCode.email === user.email) { + await User.findOneAndUpdate({ email: email }, { status: "active" }); + return res.status(200).json({ status: "Success" }); + } else { + return res.status(401).json({ error: "Incorrect verification code" }); + } + } catch (err) { + return res.status(500).json({ error: "Server error: " + err }); + } +}); + +export default router; diff --git a/server.js b/server.js index dda095e..c140c42 100644 --- a/server.js +++ b/server.js @@ -10,6 +10,7 @@ import session from "express-session"; import errorHandler from "errorhandler"; import connectMongo from "connect-mongo"; import registerRoutes from "./routes/register.js"; +import verificationRoutes from "./routes/verification.js"; import dbConnection from "./helpers/db.js"; const app = express(); @@ -39,7 +40,6 @@ app.use( }) ); - app.use( bodyParser.urlencoded({ extended: false, @@ -73,6 +73,7 @@ app.use( app.use(errorHandler()); app.use("/api/v1", registerRoutes); +app.use("/api/v1/verification", verificationRoutes); app.listen(process.env.PORT || config.port, () => { console.log(chalk.yellow("......................................."));