diff --git a/backend/models/user.js b/backend/models/user.js index 2c584f94a..0b9ab5339 100644 --- a/backend/models/user.js +++ b/backend/models/user.js @@ -21,8 +21,9 @@ const userSchema = new Schema( favouriteThemes: [], refactored: { type: Boolean, default: true }, banned: { type: Boolean, default: false }, - verified: { type: Boolean, default: false }, //what's the difference between verified and email verified + verified: { type: Boolean, default: false }, //Verified is actually whether or not discord account is connected emailVerified: { type: Boolean, default: false }, + verificationHashes: [{ type: String }], lbMemory: { //short for leaderboard memory time15: { diff --git a/backend/mongo-todo.md b/backend/mongo-todo.md index 6cf4eb1f8..154db66c3 100644 --- a/backend/mongo-todo.md +++ b/backend/mongo-todo.md @@ -1,8 +1,7 @@ -## Todo +# Todo - Get google login working - Account data should be updated when new result is added/test completed -- Add email verification - Fix localhost, production, development server detection - Should be a setting in the .env - Maybe it could be set through package.json @@ -12,13 +11,17 @@ - Check for tag pb doesn't always work - Leaderboard doesn't show the time until the daily reset - User's Leaderboard history is not edited, and therefore distance moved on leaderboard does not work properly +- Graph bugs out when new result is added but page is not refreshed + - Graph loops back from earliest point to the new points + - Results list isn't updated either ### leaderboard - Does clearDailyLeaderboards cause a memory leak? - Try commenting it out and seeing if it makes a difference - Identify bugs -- Username not highlighted and added to the bottom if current user made the leaderboard + - Leaderboard says glb is undefined on first item + - No global or daily items are returned ## After beta is ready diff --git a/backend/server.js b/backend/server.js index d90c21864..d96c63b8f 100644 --- a/backend/server.js +++ b/backend/server.js @@ -3,6 +3,7 @@ const express = require("express"); const bodyParser = require("body-parser"); const mongoose = require("mongoose"); const jwt = require("jsonwebtoken"); +const nodemailer = require("nodemailer"); const bcrypt = require("bcrypt"); const saltRounds = 10; const { User } = require("./models/user"); @@ -20,6 +21,19 @@ mongoose.connect("mongodb://localhost:27017/monkeytype", { useUnifiedTopology: true, }); +let transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + //should use OAuth in production + //type: 'OAuth2', + user: process.env.MAIL_ADDRESS, + pass: process.env.MAIL_PASSWORD, + //clientId: process.env.OAUTH_CLIENTID, + //clientSecret: process.env.OAUTH_CLIENT_SECRET, + //refreshToken: process.env.OAUTH_REFRESH_TOKEN + }, +}); + const mtRootDir = __dirname.substring(0, __dirname.length - 8); //will this work for windows and mac computers? app.use(express.static(mtRootDir + "/dist")); app.use(bodyParser.json()); @@ -557,10 +571,57 @@ app.post("/api/updateName", (req, res) => { const name = req.body.name; }); -app.post("/api/sendEmailVerification", (req, res) => { - const uid = req.body.uid; - //Add send Email verification code here - //should be a seperate sendEmailVerification function that can be called from sign up as well +function sendVerificationEmail(username, email) { + const host = "localhost:5000"; + const hash = Math.random().toString(16).substr(2, 12); + const link = `http://${host}/verifyEmail?name=${username}&hash=${hash}`; + User.findOne({ name: username }, (err, user) => { + user.verificationHashes.push(hash); + user.save(); + }); + const mailOptions = { + from: process.env.MAIL_ADDRESS, + to: email, + subject: "Monkeytype User Verification", + text: `Hello ${username},\nFollow this link to verify your email address:\n${link}\nIf you didn’t ask to verify this address, you can ignore this email.\nThanks,\nYour monkeytype team`, + }; + + transporter.sendMail(mailOptions, function (error, info) { + if (error) { + console.log(error); + } else { + console.log("Email sent: " + info.response); + } + }); +} + +app.get("/verifyEmail", (req, res) => { + let success = false; + User.findOne({ name: req.query.name }, (err, user) => { + if (user.verificationHashes.includes(req.query.hash)) { + success = true; + user.verificationHashes = []; + user.verified = true; + user.emailVerified = true; + user.save(); + } + }).then(() => { + if (success) { + res.send( + "
Go back to monkeytype
" + ); + } else { + res.send( + "Go back to monkeytype
" + ); + } + }); +}); + +app.post("/api/sendEmailVerification", authenticateToken, (req, res) => { + User.findOne({ name: req.name }, (err, user) => { + sendVerificationEmail(req.name, user.email); + }); res.sendStatus(200); }); @@ -569,8 +630,9 @@ app.post("/api/signIn", (req, res) => { //Login and send tokens User.findOne({ email: req.body.email }, (err, user) => { if (err) res.status(500).send({ error: err }); - if (user == null) { + if (user === null) { res.status(500).send({ error: "No user found with that email" }); + return; } bcrypt.compare(req.body.password, user.password, (err, result) => { if (err) @@ -628,7 +690,7 @@ app.post("/api/signUp", (req, res) => { .save() .then((user) => { //send email verification - + sendVerificationEmail(user.name, user.email); //add account created event to analytics //return user data and access token diff --git a/example.env b/example.env new file mode 100644 index 000000000..68be6a0ae --- /dev/null +++ b/example.env @@ -0,0 +1,9 @@ +# Secrets can be generated at https://www.grc.com/passwords.htm +ACCESS_TOKEN_SECRET=6JlduNw96JONRtmg7Ru6tCW0UN42LQyzlHE0e03p2HO4m5Gm7PrYjRCinHCfeMM +REFRESH_TOKEN_SECRET=bnTfeI0J84XucqTWkHRPBCrewoJGIQySdHnL2bDrZp212tDyMG0fs5nf9aUXT9N + +#Gmail login for email verification +#App password can be generated on account page under security, sigining in to Google +#Must enable 2 step verification before generating app password +MAIL_ADDRESS=youremail@gmail.com +MAIL_PASSWORD=cqvpgasbggytzfjq diff --git a/package-lock.json b/package-lock.json index d9d45ce4d..cada57934 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "express": "^4.17.1", "jsonwebtoken": "^8.5.1", "mongoose": "^5.12.8", + "nodemailer": "^6.6.1", "tinycolor2": "^1.4.2" }, "devDependencies": { @@ -9032,6 +9033,14 @@ "node": ">=0.8.0" } }, + "node_modules/nodemailer": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.1.tgz", + "integrity": "sha512-1xzFN3gqv+/qJ6YRyxBxfTYstLNt0FCtZaFRvf4Sg9wxNGWbwFmGXVpfSi6ThGK6aRxAo+KjHtYSW8NvCsNSAg==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", @@ -20602,6 +20611,11 @@ } } }, + "nodemailer": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.1.tgz", + "integrity": "sha512-1xzFN3gqv+/qJ6YRyxBxfTYstLNt0FCtZaFRvf4Sg9wxNGWbwFmGXVpfSi6ThGK6aRxAo+KjHtYSW8NvCsNSAg==" + }, "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 cea4860dc..862fd7dbc 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "express": "^4.17.1", "jsonwebtoken": "^8.5.1", "mongoose": "^5.12.8", + "nodemailer": "^6.6.1", "tinycolor2": "^1.4.2" } } diff --git a/src/js/misc.js b/src/js/misc.js index d108b31b9..e91ffe02c 100644 --- a/src/js/misc.js +++ b/src/js/misc.js @@ -317,9 +317,7 @@ export function sendVerificationEmail() { Loader.show(); let cu = DB.currentUser(); axiosInstance - .post("/api/sendEmailVerification", { - uid: cu.uid, - }) + .post("/api/sendEmailVerification", {}) .then(() => { Loader.hide(); showNotification("Email sent to " + cu.email, 4000);