From ff4dfb09da2dbd68c43b963770cc1bdafbd311d7 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra Date: Sat, 7 May 2016 17:46:39 +0530 Subject: [PATCH] Add email verification and notifications This commit - [x] Fixes the flash notice color (Trivial) - [x] Adds flash message for user with no email. - [x] Adds checks to see if user's email is verified, and displays corresponding notification. - [x] Adds email templates. --- common/models/user.js | 97 ++++++++++--------- server/boot/a-extendUser.js | 23 ++--- server/boot/challenge.js | 8 +- server/boot/user.js | 3 + server/utils/middleware.js | 23 +++++ server/views/account/update-email.jade | 6 +- server/views/emails/a-extend-user-welcome.ejs | 24 +++++ server/views/emails/user-email-verify.ejs | 15 +++ 8 files changed, 135 insertions(+), 64 deletions(-) create mode 100644 server/views/emails/a-extend-user-welcome.ejs create mode 100644 server/views/emails/user-email-verify.ejs diff --git a/common/models/user.js b/common/models/user.js index 4d2def2304..246a2984d1 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -4,6 +4,7 @@ import moment from 'moment'; import dedent from 'dedent'; import debugFactory from 'debug'; import { isEmail } from 'validator'; +import path from 'path'; import { saveUser, observeMethod } from '../../server/utils/rx'; import { blacklistedUsernames } from '../../server/utils/constants'; @@ -89,7 +90,7 @@ module.exports = function(User) { 'You\'re email has been confirmed!' ] }); - ctx.res.redirect('/email-signin'); + ctx.res.redirect('/'); }); User.beforeRemote('create', function({ req, res }, _, next) { @@ -340,7 +341,52 @@ module.exports = function(User) { new Error(`${email} is already associated with another account.`) ); } - return this.update$({ email }).toPromise(); + + const emailVerified = false; + return this.update$({ + email, emailVerified + }) + .do(() => { + this.email = email; + this.emailVerified = emailVerified; + }) + .flatMap(() => { + var mailOptions = { + type: 'email', + to: email, + from: 'Team@freecodecamp.com', + subject: 'Welcome to Free Code Camp!', + template: path.join( + __dirname, + '..', + '..', + 'server', + 'views', + 'emails', + 'user-email-verify.ejs' + ) + }; + return this.verify(mailOptions).then( + (data) => { + if (data) { + return Promise.resolve( + dedent` + Your email has been updated successfully, Please + follow the link we sent you, to confirm. + `); + } + return Promise.reject( + 'Oops, something went wrong, please try again later' + ); + }, + (error) => { + debug(error); + return Promise.reject( + 'Oops, something went wrong, please try again later' + ); + } + ); + }).toPromise(); }); }; @@ -358,8 +404,8 @@ module.exports = function(User) { ], returns: [ { - arg: 'status', - type: 'object' + arg: 'message', + type: 'string' } ], http: { @@ -486,52 +532,11 @@ module.exports = function(User) { } ); - User.prototype.updateEmail = function updateEmail(email) { - if (this.email && this.email === email) { - return Promise.reject(new Error( - `${email} is already associated with this account.` - )); - } - return User.doesExist(null, email) - .then(exists => { - if (exists) { - return Promise.reject( - new Error(`${email} is already associated with another account.`) - ); - } - return this.update$({ email }).toPromise(); - }); - }; - - User.remoteMethod( - 'updateEmail', - { - isStatic: false, - description: 'updates the email of the user object', - accepts: [ - { - arg: 'email', - type: 'string', - required: true - } - ], - returns: [ - { - arg: 'status', - type: 'object' - } - ], - http: { - path: '/update-email', - verb: 'POST' - } - } - ); - User.themes = { night: true, default: true }; + User.prototype.updateTheme = function updateTheme(theme) { if (!this.constructor.themes[theme]) { const err = new Error( diff --git a/server/boot/a-extendUser.js b/server/boot/a-extendUser.js index 932bd7dcdb..9d62c62e0f 100644 --- a/server/boot/a-extendUser.js +++ b/server/boot/a-extendUser.js @@ -1,6 +1,7 @@ import { Observable } from 'rx'; import debugFactory from 'debug'; import { isEmail } from 'validator'; +import path from 'path'; const debug = debugFactory('fcc:user:remote'); @@ -15,7 +16,6 @@ module.exports = function(app) { var User = app.models.User; var UserIdentity = app.models.UserIdentity; var UserCredential = app.models.UserCredential; - var Email = app.models.Email; User.observe('before delete', function(ctx, next) { debug('removing user', ctx.where); var id = ctx.where && ctx.where.id ? ctx.where.id : null; @@ -70,21 +70,18 @@ module.exports = function(app) { to: user.email, from: 'Team@freecodecamp.com', subject: 'Welcome to Free Code Camp!', - redirect: '/', - text: [ - 'Greetings from San Francisco!\n\n', - 'Thank you for joining our community.\n', - 'Feel free to email us at this address if you have ', - 'any questions about Free Code Camp.\n', - 'And if you have a moment, check out our blog: ', - 'medium.freecodecamp.com.\n\n', - 'Good luck with the challenges!\n\n', - '- the Free Code Camp Team' - ].join('') + template: path.join( + __dirname, + '..', + 'views', + 'emails', + 'a-extend-user-welcome.ejs' + ), + redirect: '/' }; debug('sending welcome email'); - return Email.send(mailOptions, function(err) { + return user.verify(mailOptions, function(err) { if (err) { return next(err); } return req.logIn(user, function(err) { if (err) { return next(err); } diff --git a/server/boot/challenge.js b/server/boot/challenge.js index 0325c5248a..380a6c9b03 100644 --- a/server/boot/challenge.js +++ b/server/boot/challenge.js @@ -18,7 +18,8 @@ import { import { observeMethod } from '../utils/rx'; import { - ifNoUserSend + ifNoUserSend, + flashIfNotVerified } from '../utils/middleware'; import getFromDisk$ from '../utils/getFromDisk$'; @@ -419,7 +420,10 @@ module.exports = function(app) { redirectToNextChallenge ); - router.get('/challenges/:challengeName', showChallenge); + router.get('/challenges/:challengeName', + flashIfNotVerified, + showChallenge + ); app.use(router); diff --git a/server/boot/user.js b/server/boot/user.js index 685a2aed5c..2391f25cd8 100644 --- a/server/boot/user.js +++ b/server/boot/user.js @@ -19,6 +19,8 @@ import { calcLongestStreak } from '../utils/user-stats'; +import { flashIfNotVerified } from '../utils/middleware'; + const debug = debugFactory('fcc:boot:user'); const sendNonUserToMap = ifNoUserRedirectTo('/map'); const certIds = { @@ -183,6 +185,7 @@ module.exports = function(app) { router.get( '/settings', sendNonUserToMap, + flashIfNotVerified, getSettings ); router.get('/vote1', vote1); diff --git a/server/utils/middleware.js b/server/utils/middleware.js index 458168bac3..51fe4e45ae 100644 --- a/server/utils/middleware.js +++ b/server/utils/middleware.js @@ -1,3 +1,5 @@ +import dedent from 'dedent'; + export function ifNoUserRedirectTo(url, message, type = 'errors') { return function(req, res, next) { const { path } = req; @@ -28,3 +30,24 @@ export function ifNoUser401(req, res, next) { } return res.status(401).end(); } + +export function flashIfNotVerified(req, res, next) { + const user = req.user; + if (!user) { + return next(); + } + const email = req.user.email; + const emailVerified = req.user.emailVerified; + if (!email) { + req.flash('info', { msg: + dedent `Please update your email address when you get a moment in + your Settings Page.` + }); + } else if (!emailVerified) { + req.flash('info', { msg: + dedent `We have your email address with us, but its not yet verified. + Please follow the link we sent you, when you get a moment.` + }); + } + return next(); +} diff --git a/server/views/account/update-email.jade b/server/views/account/update-email.jade index bef48afab9..c7dce563aa 100644 --- a/server/views/account/update-email.jade +++ b/server/views/account/update-email.jade @@ -46,10 +46,10 @@ block content } }) .done(data =>{ - if(data.status && data.status.count){ - $('#flash-content').html("Your email has been updated successfully!"); + if(data && data.message){ + $('#flash-content').html(data.message); $('#flash-board') - .removeClass('alert-danger') + .removeClass('alert-info') .addClass('alert-success') .fadeIn(); } diff --git a/server/views/emails/a-extend-user-welcome.ejs b/server/views/emails/a-extend-user-welcome.ejs new file mode 100644 index 0000000000..be361302e2 --- /dev/null +++ b/server/views/emails/a-extend-user-welcome.ejs @@ -0,0 +1,24 @@ +

+ Greetings from San Francisco! +

+

+ Thank you for joining our community. +

+

+ Please verify your email by following the link below: +

+

+ <%= verifyHref %> +

+

+ Feel free to email us at this address if you have any questions about Free Code Camp. +

+

+ And if you have a moment, check out our blog: https://medium.freecodecamp.com. +

+

+ Good luck with the challenges! +

+

+ - the Free Code Camp Team. +

diff --git a/server/views/emails/user-email-verify.ejs b/server/views/emails/user-email-verify.ejs new file mode 100644 index 0000000000..1df3b84627 --- /dev/null +++ b/server/views/emails/user-email-verify.ejs @@ -0,0 +1,15 @@ +

+ Thank you, for updating you contact details. +

+

+ Please verify your email by following the link below: +

+

+ <%= verifyHref %> +

+

+ Feel free to email us at this address if you have any questions about Free Code Camp. +

+

+ - the Free Code Camp Team. +