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.
This commit is contained in:
@ -4,6 +4,7 @@ import moment from 'moment';
|
|||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
import { isEmail } from 'validator';
|
import { isEmail } from 'validator';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
import { saveUser, observeMethod } from '../../server/utils/rx';
|
import { saveUser, observeMethod } from '../../server/utils/rx';
|
||||||
import { blacklistedUsernames } from '../../server/utils/constants';
|
import { blacklistedUsernames } from '../../server/utils/constants';
|
||||||
@ -89,7 +90,7 @@ module.exports = function(User) {
|
|||||||
'You\'re email has been confirmed!'
|
'You\'re email has been confirmed!'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
ctx.res.redirect('/email-signin');
|
ctx.res.redirect('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
User.beforeRemote('create', function({ req, res }, _, next) {
|
User.beforeRemote('create', function({ req, res }, _, next) {
|
||||||
@ -340,7 +341,52 @@ module.exports = function(User) {
|
|||||||
new Error(`${email} is already associated with another account.`)
|
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: [
|
returns: [
|
||||||
{
|
{
|
||||||
arg: 'status',
|
arg: 'message',
|
||||||
type: 'object'
|
type: 'string'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
http: {
|
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 = {
|
User.themes = {
|
||||||
night: true,
|
night: true,
|
||||||
default: true
|
default: true
|
||||||
};
|
};
|
||||||
|
|
||||||
User.prototype.updateTheme = function updateTheme(theme) {
|
User.prototype.updateTheme = function updateTheme(theme) {
|
||||||
if (!this.constructor.themes[theme]) {
|
if (!this.constructor.themes[theme]) {
|
||||||
const err = new Error(
|
const err = new Error(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Observable } from 'rx';
|
import { Observable } from 'rx';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
import { isEmail } from 'validator';
|
import { isEmail } from 'validator';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:user:remote');
|
const debug = debugFactory('fcc:user:remote');
|
||||||
|
|
||||||
@ -15,7 +16,6 @@ module.exports = function(app) {
|
|||||||
var User = app.models.User;
|
var User = app.models.User;
|
||||||
var UserIdentity = app.models.UserIdentity;
|
var UserIdentity = app.models.UserIdentity;
|
||||||
var UserCredential = app.models.UserCredential;
|
var UserCredential = app.models.UserCredential;
|
||||||
var Email = app.models.Email;
|
|
||||||
User.observe('before delete', function(ctx, next) {
|
User.observe('before delete', function(ctx, next) {
|
||||||
debug('removing user', ctx.where);
|
debug('removing user', ctx.where);
|
||||||
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
|
var id = ctx.where && ctx.where.id ? ctx.where.id : null;
|
||||||
@ -70,21 +70,18 @@ module.exports = function(app) {
|
|||||||
to: user.email,
|
to: user.email,
|
||||||
from: 'Team@freecodecamp.com',
|
from: 'Team@freecodecamp.com',
|
||||||
subject: 'Welcome to Free Code Camp!',
|
subject: 'Welcome to Free Code Camp!',
|
||||||
redirect: '/',
|
template: path.join(
|
||||||
text: [
|
__dirname,
|
||||||
'Greetings from San Francisco!\n\n',
|
'..',
|
||||||
'Thank you for joining our community.\n',
|
'views',
|
||||||
'Feel free to email us at this address if you have ',
|
'emails',
|
||||||
'any questions about Free Code Camp.\n',
|
'a-extend-user-welcome.ejs'
|
||||||
'And if you have a moment, check out our blog: ',
|
),
|
||||||
'medium.freecodecamp.com.\n\n',
|
redirect: '/'
|
||||||
'Good luck with the challenges!\n\n',
|
|
||||||
'- the Free Code Camp Team'
|
|
||||||
].join('')
|
|
||||||
};
|
};
|
||||||
|
|
||||||
debug('sending welcome email');
|
debug('sending welcome email');
|
||||||
return Email.send(mailOptions, function(err) {
|
return user.verify(mailOptions, function(err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
return req.logIn(user, function(err) {
|
return req.logIn(user, function(err) {
|
||||||
if (err) { return next(err); }
|
if (err) { return next(err); }
|
||||||
|
@ -18,7 +18,8 @@ import {
|
|||||||
import { observeMethod } from '../utils/rx';
|
import { observeMethod } from '../utils/rx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ifNoUserSend
|
ifNoUserSend,
|
||||||
|
flashIfNotVerified
|
||||||
} from '../utils/middleware';
|
} from '../utils/middleware';
|
||||||
|
|
||||||
import getFromDisk$ from '../utils/getFromDisk$';
|
import getFromDisk$ from '../utils/getFromDisk$';
|
||||||
@ -419,7 +420,10 @@ module.exports = function(app) {
|
|||||||
redirectToNextChallenge
|
redirectToNextChallenge
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get('/challenges/:challengeName', showChallenge);
|
router.get('/challenges/:challengeName',
|
||||||
|
flashIfNotVerified,
|
||||||
|
showChallenge
|
||||||
|
);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
|
@ -19,6 +19,8 @@ import {
|
|||||||
calcLongestStreak
|
calcLongestStreak
|
||||||
} from '../utils/user-stats';
|
} from '../utils/user-stats';
|
||||||
|
|
||||||
|
import { flashIfNotVerified } from '../utils/middleware';
|
||||||
|
|
||||||
const debug = debugFactory('fcc:boot:user');
|
const debug = debugFactory('fcc:boot:user');
|
||||||
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
||||||
const certIds = {
|
const certIds = {
|
||||||
@ -183,6 +185,7 @@ module.exports = function(app) {
|
|||||||
router.get(
|
router.get(
|
||||||
'/settings',
|
'/settings',
|
||||||
sendNonUserToMap,
|
sendNonUserToMap,
|
||||||
|
flashIfNotVerified,
|
||||||
getSettings
|
getSettings
|
||||||
);
|
);
|
||||||
router.get('/vote1', vote1);
|
router.get('/vote1', vote1);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import dedent from 'dedent';
|
||||||
|
|
||||||
export function ifNoUserRedirectTo(url, message, type = 'errors') {
|
export function ifNoUserRedirectTo(url, message, type = 'errors') {
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
const { path } = req;
|
const { path } = req;
|
||||||
@ -28,3 +30,24 @@ export function ifNoUser401(req, res, next) {
|
|||||||
}
|
}
|
||||||
return res.status(401).end();
|
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 <a href="\settings"> Settings Page.</a>`
|
||||||
|
});
|
||||||
|
} 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();
|
||||||
|
}
|
||||||
|
@ -46,10 +46,10 @@ block content
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.done(data =>{
|
.done(data =>{
|
||||||
if(data.status && data.status.count){
|
if(data && data.message){
|
||||||
$('#flash-content').html("Your email has been updated successfully!");
|
$('#flash-content').html(data.message);
|
||||||
$('#flash-board')
|
$('#flash-board')
|
||||||
.removeClass('alert-danger')
|
.removeClass('alert-info')
|
||||||
.addClass('alert-success')
|
.addClass('alert-success')
|
||||||
.fadeIn();
|
.fadeIn();
|
||||||
}
|
}
|
||||||
|
24
server/views/emails/a-extend-user-welcome.ejs
Normal file
24
server/views/emails/a-extend-user-welcome.ejs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<h2>
|
||||||
|
Greetings from San Francisco!
|
||||||
|
</h2>
|
||||||
|
<p>
|
||||||
|
Thank you for joining our community.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please verify your email by following the link below:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="<%= verifyHref %>"><%= verifyHref %></a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Feel free to email us at this address if you have any questions about Free Code Camp.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
And if you have a moment, check out our blog: https://medium.freecodecamp.com.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Good luck with the challenges!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
- the Free Code Camp Team.
|
||||||
|
</p>
|
15
server/views/emails/user-email-verify.ejs
Normal file
15
server/views/emails/user-email-verify.ejs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<h3>
|
||||||
|
Thank you, for updating you contact details.
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
Please verify your email by following the link below:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="<%= verifyHref %>"><%= verifyHref %></a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Feel free to email us at this address if you have any questions about Free Code Camp.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
- the Free Code Camp Team.
|
||||||
|
</p>
|
Reference in New Issue
Block a user