Email verification (#230)
* Making the email separate for future enhancements * email-verification added * Github users are verified * update package * Bug fixes and improvements * jade to pug * Bug fix * changed route
This commit is contained in:
parent
ac9e65bb30
commit
2d781e22fb
9 changed files with 252 additions and 28 deletions
|
@ -10,7 +10,7 @@
|
|||
"build": "NODE_ENV=production webpack --config webpack.config.prod.js --progress",
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"fetch-examples": "node fetch-examples.js",
|
||||
"postinstall" : "git submodule update --remote --recursive"
|
||||
"postinstall": "git submodule update --remote --recursive"
|
||||
},
|
||||
"main": "index.js",
|
||||
"author": "Cassie Tarakajian",
|
||||
|
@ -83,10 +83,13 @@
|
|||
"express": "^4.13.4",
|
||||
"express-session": "^1.13.0",
|
||||
"file-type": "^3.8.0",
|
||||
"fs-promise": "^1.0.0",
|
||||
"htmlhint": "^0.9.13",
|
||||
"is_js": "^0.9.0",
|
||||
"js-beautify": "^1.6.4",
|
||||
"jsdom": "^9.8.3",
|
||||
"jshint": "^2.9.2",
|
||||
"jsonwebtoken": "^7.2.1",
|
||||
"lodash": "^4.16.4",
|
||||
"loop-protect": "git+https://git@github.com/catarak/loop-protect.git",
|
||||
"moment": "^2.14.1",
|
||||
|
@ -97,8 +100,9 @@
|
|||
"passport": "^0.3.2",
|
||||
"passport-github": "^1.1.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"q": "^1.4.1",
|
||||
"project-name-generator": "^2.1.3",
|
||||
"pug": "^2.0.0-beta6",
|
||||
"q": "^1.4.1",
|
||||
"react": "^15.1.0",
|
||||
"react-dom": "^15.1.0",
|
||||
"react-inlinesvg": "^0.4.2",
|
||||
|
|
|
@ -53,6 +53,7 @@ passport.use(new GitHubStrategy({
|
|||
existingEmailUser.username = existingEmailUser.username || profile.username;
|
||||
existingEmailUser.tokens.push({ kind: 'github', accessToken });
|
||||
existingEmailUser.name = existingEmailUser.name || profile.displayName;
|
||||
existingEmailUser.verified = 0;
|
||||
existingEmailUser.save((err) => {
|
||||
return done(null, existingEmailUser);
|
||||
});
|
||||
|
@ -63,6 +64,7 @@ passport.use(new GitHubStrategy({
|
|||
user.username = profile.username;
|
||||
user.tokens.push({ kind: 'github', accessToken });
|
||||
user.name = profile.displayName;
|
||||
user.verified = 0;
|
||||
user.save((err) => {
|
||||
return done(null, user);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import User from '../models/user';
|
||||
import crypto from 'crypto';
|
||||
import async from 'async';
|
||||
import nodemailer from 'nodemailer';
|
||||
import mg from 'nodemailer-mailgun-transport';
|
||||
import mail from '../utils/mail';
|
||||
import auth from '../utils/auth';
|
||||
|
||||
export function createUser(req, res, next) {
|
||||
const user = new User({
|
||||
|
@ -24,6 +24,13 @@ export function createUser(req, res, next) {
|
|||
if (loginErr) {
|
||||
return next(loginErr);
|
||||
}
|
||||
mail.send('email-verification', {
|
||||
body: {
|
||||
link: `http://${req.headers.host}/verify?t=${auth.createVerificationToken(req.body.email)}`
|
||||
},
|
||||
to: req.body.email,
|
||||
subject: 'Email Verification',
|
||||
}, (result) => { // eslint-disable-line no-unused-vars
|
||||
res.json({
|
||||
email: req.user.email,
|
||||
username: req.user.username,
|
||||
|
@ -33,6 +40,7 @@ export function createUser(req, res, next) {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function duplicateUserCheck(req, res) {
|
||||
|
@ -99,27 +107,13 @@ export function resetPasswordInitiate(req, res) {
|
|||
});
|
||||
},
|
||||
(token, user, done) => {
|
||||
const auth = {
|
||||
auth: {
|
||||
api_key: process.env.MAILGUN_KEY,
|
||||
domain: process.env.MAILGUN_DOMAIN
|
||||
}
|
||||
};
|
||||
|
||||
const transporter = nodemailer.createTransport(mg(auth));
|
||||
const message = {
|
||||
mail.send('reset-password', {
|
||||
body: {
|
||||
link: `http://${req.headers.host}/reset-password/${token}`,
|
||||
},
|
||||
to: user.email,
|
||||
from: 'p5.js Web Editor <noreply@p5js.org>',
|
||||
subject: 'p5.js Web Editor Password Reset',
|
||||
text: `You are receiving this email because you (or someone else) have requested the reset of the password for your account.
|
||||
\n\nPlease click on the following link, or paste this into your browser to complete the process:
|
||||
\n\nhttp://${req.headers.host}/reset-password/${token}
|
||||
\n\nIf you did not request this, please ignore this email and your password will remain unchanged.
|
||||
\n\nThanks for using the p5.js Web Editor!\n`
|
||||
};
|
||||
transporter.sendMail(message, (error) => {
|
||||
done(error);
|
||||
});
|
||||
}, done);
|
||||
}
|
||||
], (err) => {
|
||||
if (err) {
|
||||
|
@ -140,6 +134,28 @@ export function validateResetPasswordToken(req, res) {
|
|||
});
|
||||
}
|
||||
|
||||
export function verifyEmail(req, res) {
|
||||
const token = req.query.t;
|
||||
// verify the token
|
||||
auth.verifyEmailToken(token)
|
||||
.then((data) => {
|
||||
const email = data.email;
|
||||
// change the verified field for the user or throw if the user is not found
|
||||
User.findOne({ email })
|
||||
.then((user) => {
|
||||
// change the field for the user, and send the new cookie
|
||||
user.verified = 0; // eslint-disable-line
|
||||
user.save()
|
||||
.then((result) => { // eslint-disable-line
|
||||
res.json({ user });
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
res.json(err);
|
||||
});
|
||||
}
|
||||
|
||||
export function updatePassword(req, res) {
|
||||
User.findOne({ resetPasswordToken: req.params.token, resetPasswordExpires: { $gt: Date.now() } }, function (err, user) {
|
||||
if (!user) {
|
||||
|
|
|
@ -8,6 +8,7 @@ const userSchema = new Schema({
|
|||
password: { type: String },
|
||||
resetPasswordToken: String,
|
||||
resetPasswordExpires: Date,
|
||||
verified: { type: Number, default: -1 },
|
||||
github: { type: String },
|
||||
email: { type: String, unique: true },
|
||||
tokens: Array,
|
||||
|
|
|
@ -14,4 +14,6 @@ router.route('/reset-password/:token').get(UserController.validateResetPasswordT
|
|||
|
||||
router.route('/reset-password/:token').post(UserController.updatePassword);
|
||||
|
||||
router.route('/verify').get(UserController.verifyEmail);
|
||||
|
||||
export default router;
|
||||
|
|
37
server/utils/auth.js
Normal file
37
server/utils/auth.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const jwt = require('jsonwebtoken');
|
||||
|
||||
|
||||
class Auth {
|
||||
/**
|
||||
* Create a verification token using jwt
|
||||
*/
|
||||
createVerificationToken(email, fromEmail) {
|
||||
return jwt.sign({
|
||||
email,
|
||||
fromEmail,
|
||||
}, process.env.SECRET_TOKEN, {
|
||||
expiresIn: '1 day',
|
||||
subject: 'email-verification',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify token
|
||||
*/
|
||||
verifyEmailToken(token) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jwt.verify(token, process.env.SECRET_TOKEN, (err, data) => {
|
||||
if (err) {
|
||||
if (err.name === 'TokenExpiredError') {
|
||||
reject('The verification link has expired');
|
||||
} else if (err.name === 'JsonWebTokenError') {
|
||||
reject('Verification link is malformend');
|
||||
}
|
||||
}
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new Auth();
|
80
server/utils/mail.js
Normal file
80
server/utils/mail.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
/**
|
||||
* Mail service wrapping around mailgun
|
||||
*/
|
||||
|
||||
import fsp from 'fs-promise';
|
||||
import pug from 'pug';
|
||||
import is from 'is_js';
|
||||
import nodemailer from 'nodemailer';
|
||||
import mg from 'nodemailer-mailgun-transport';
|
||||
|
||||
const auth = {
|
||||
api_key: process.env.MAILGUN_KEY,
|
||||
domain: process.env.MAILGUN_DOMAIN,
|
||||
};
|
||||
|
||||
class Mail {
|
||||
constructor() {
|
||||
this.client = nodemailer.createTransport(mg({ auth }));
|
||||
this.sendOptions = {
|
||||
from: process.env.EMAIL_SENDER,
|
||||
replyTo: process.env.EMAIL_REPLY_TO,
|
||||
};
|
||||
}
|
||||
|
||||
getMailTemplate(type) {
|
||||
let mailTemp;
|
||||
switch (type) {
|
||||
case 'reset-password':
|
||||
mailTemp = 'server/views/mailTemplates/reset-password.pug';
|
||||
break;
|
||||
case 'email-verification':
|
||||
mailTemp = 'server/views/mailTemplates/email-verification.pug';
|
||||
break;
|
||||
}
|
||||
return mailTemp;
|
||||
}
|
||||
|
||||
sendMail(mailOptions) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.sendMail(mailOptions, (err, info) => {
|
||||
resolve(err, info);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dispatchMail(template, data, callback) {
|
||||
const self = this;
|
||||
return fsp.readFile(template, 'utf8')
|
||||
.then((file) => {
|
||||
const compiled = pug.compile(file, {
|
||||
filename: template,
|
||||
});
|
||||
const body = compiled(data.body);
|
||||
const mailOptions = {
|
||||
to: data.to,
|
||||
subject: data.subject,
|
||||
from: self.sendOptions.from,
|
||||
'h:Reply-To': self.sendOptions.replyTo,
|
||||
html: body,
|
||||
};
|
||||
return self.sendMail(mailOptions);
|
||||
})
|
||||
.then((err, res) => {
|
||||
callback(err, res);
|
||||
});
|
||||
}
|
||||
|
||||
send(type, data, callback) {
|
||||
let template = null;
|
||||
if (is.existy(data.template)) {
|
||||
template = data.template;
|
||||
} else {
|
||||
template = this.getMailTemplate(type);
|
||||
}
|
||||
return this.dispatchMail(template, data, callback);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Mail();
|
41
server/views/mailTemplates/email-verification.pug
Normal file
41
server/views/mailTemplates/email-verification.pug
Normal file
|
@ -0,0 +1,41 @@
|
|||
doctype html
|
||||
html(xmlns='http://www.w3.org/1999/xhtml')
|
||||
head
|
||||
meta(http-equiv='Content-Type', content='text/html; charset=utf-8')
|
||||
|
||||
body(paddingwidth='0', paddingheight='0', style='padding-top: 0; padding-bottom: 0; background-repeat: repeat; width: 100% !important; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -webkit-font-smoothing: antialiased;background-color:white color:black;', offset='0', toppadding='0', leftpadding='0')
|
||||
|
||||
table.tableContent(border='0', cellspacing='0', cellpadding='0', align='center', bgcolor='white', style='font-family: Helvetica, Arial,serif;')
|
||||
tr
|
||||
td
|
||||
table(width='650', border='0', cellspacing='0', cellpadding='0', align='center')
|
||||
tr
|
||||
td
|
||||
table(width='650', border='0', cellspacing='0', cellpadding='0', align='center', )
|
||||
tr
|
||||
td(valign='top', align='center')
|
||||
//.contentEditableContainer.contentImageEditable
|
||||
.contentEditable
|
||||
//- img(src='/img/mail-header-new.png', width='100%',alt='', data-default='placeholder', data-max-width='650')
|
||||
tr
|
||||
td(height='20')
|
||||
tr
|
||||
td.movableContentContainer(valign='top')
|
||||
table(cellspacing="15")
|
||||
tbody(style="font-size : 15px")
|
||||
tr
|
||||
td(style='text-align: center;font-family: sans-serif;font-size: 18px;')
|
||||
h3 Email Verification
|
||||
tr
|
||||
td(style="color:black;line-height: 130%;padding: 10px;white-space:pre;")
|
||||
| Hello,
|
||||
| To verify you email, click on the button below:
|
||||
tr(style="color:black;text-align:center")
|
||||
td
|
||||
a(href="#{link}" style="text-align:center;font-size:20px;font-family:Helvetica,arial,sans-serif;color:white;font-weight:bold; padding-left: 10px;display:inline-block;min-height:27px;padding : 4px 25px 4px 25px;line-height:27px;border-radius:2px;border-width:1px; background-color:black;" target="_blank") Verify Email
|
||||
tr
|
||||
td(style="color:black;padding:10px 10px 0 10px") Or copy and paste the URL into your browser:
|
||||
tr(style="color:black")
|
||||
td(width="560px" style='padding:10px;') #{link}
|
||||
tr(style="color:black;padding:0 10px")
|
||||
td This link is only valid for the next 24 hours.
|
41
server/views/mailTemplates/reset-password.pug
Normal file
41
server/views/mailTemplates/reset-password.pug
Normal file
|
@ -0,0 +1,41 @@
|
|||
doctype html
|
||||
html(xmlns='http://www.w3.org/1999/xhtml')
|
||||
head
|
||||
meta(http-equiv='Content-Type', content='text/html; charset=utf-8')
|
||||
|
||||
body(paddingwidth='0', paddingheight='0', style='padding-top: 0; padding-bottom: 0; background-repeat: repeat; width: 100% !important; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -webkit-font-smoothing: antialiased;background-color:white color:black;', offset='0', toppadding='0', leftpadding='0')
|
||||
|
||||
table.tableContent(border='0', cellspacing='0', cellpadding='0', align='center', bgcolor='white', style='font-family: Helvetica, Arial,serif;')
|
||||
tr
|
||||
td
|
||||
table(width='650', border='0', cellspacing='0', cellpadding='0', align='center')
|
||||
tr
|
||||
td
|
||||
table(width='650', border='0', cellspacing='0', cellpadding='0', align='center', )
|
||||
tr
|
||||
td(valign='top', align='center')
|
||||
//.contentEditableContainer.contentImageEditable
|
||||
.contentEditable
|
||||
//- img(src='/img/mail-header-new.png', width='100%',alt='', data-default='placeholder', data-max-width='650')
|
||||
tr
|
||||
td(height='20')
|
||||
tr
|
||||
td.movableContentContainer(valign='top')
|
||||
table(cellspacing="15")
|
||||
tbody(style="font-size : 15px")
|
||||
tr
|
||||
td(style='text-align: center;font-family: sans-serif;font-size: 18px;')
|
||||
h3 Reset your password
|
||||
tr
|
||||
td(style="color:black;line-height: 130%;padding: 10px;white-space:pre;")
|
||||
| Hello,
|
||||
| We received a request to reset the password for your account. To reset your password, click on the button below:
|
||||
tr(style="color:black;text-align:center")
|
||||
td
|
||||
a(href="#{link}" style="text-align:center;font-size:20px;font-family:Helvetica,arial,sans-serif;color:white;font-weight:bold; padding-left: 10px;display:inline-block;min-height:27px;padding : 4px 25px 4px 25px;line-height:27px;border-radius:2px;border-width:1px; background-color:black;" target="_blank") Reset password
|
||||
tr
|
||||
td(style="color:black;padding:10px 10px 0 10px") Or copy and paste the URL into your browser:
|
||||
tr(style="color:black")
|
||||
td(width="560px" style='padding:10px;') #{link}
|
||||
tr(style="color:black;padding:0 10px")
|
||||
td If you did not request this, please ignore this email and your password will remain unchanged. Thanks for using the p5.js Web Editor!
|
Loading…
Reference in a new issue