#58 added email confirmation

master
Keith Irwin 2017-04-18 03:08:57 -04:00
parent 5ae0704ef9
commit 1c03b997e1
No known key found for this signature in database
GPG Key ID: 378933C743E2BBC0
5 changed files with 145 additions and 62 deletions

View File

@ -8,11 +8,13 @@ const mongoose = require('mongoose'),
const userSchema = new mongoose.Schema({
name: {type:String},
email: {type:String, unique:true},
newEmail: String,
emailToken: String,
slug: {type:String, required:true, unique:true},
auth: {
password: String,
passToken: String,
tokenExpires: Date,
passTokenExpires: Date,
google: {type:String, unique:true},
facebook: {type:String, unique:true},
twitter: {type:String, unique:true},
@ -46,6 +48,21 @@ const userSchema = new mongoose.Schema({
//TODO: Return promises instead of taking callbacks
// Create email confirmation token
userSchema.methods.createEmailToken = function(next){
var user = this;
crypto.randomBytes(16, (err,buf)=>{
if (err){ next(err,null); }
else {
user.emailToken = buf.toString('hex');
user.save();
return next(null,user.emailToken);
}
});
};
// Generate hash for new password
userSchema.methods.generateHash = function(password,next){
bcrypt.genSalt(8, (err,salt)=>{
@ -55,29 +72,29 @@ const userSchema = new mongoose.Schema({
};
// Create password reset token
userSchema.methods.createToken = function(next){
userSchema.methods.createPassToken = function(next){
var user = this;
if ( user.auth.tokenExpires <= Date.now() ){
// Reuse old token, resetting clock
user.auth.tokenExpires = Date.now() + 3600000; // 1 hour
// Reuse old token, resetting clock
if ( user.auth.passTokenExpires <= Date.now() ){
user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour
user.save();
return next(null,user.auth.passToken);
} else {
// Create new token
}
// Create new token
else {
crypto.randomBytes(16, (err,buf)=>{
if (err){ next(err,null); }
else {
user.auth.passToken = buf.toString('hex');
user.auth.tokenExpires = Date.now() + 3600000; // 1 hour
user.auth.passTokenExpires = Date.now() + 3600000; // 1 hour
user.save();
return next(null,user.auth.passToken);
}
});
}
};
// Check for valid password

View File

@ -65,23 +65,26 @@ module.exports = (app, passport) => {
function sendToken(user){
// Create a password token
user.createToken((err,token)=>{
user.createPassToken((err,token)=>{
if (err){ mw.throwErr(err,req); }
// Email the instructions to continue
mail.send({
from: mail.from,
to: `<${user.email}>`,
subject: 'Complete your Tracman registration',
text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`),
html: mail.html(`<p>Welcome to Tracman! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p>`)
}).then(()=>{
from: mail.from,
to: `<${user.email}>`,
subject: 'Complete your Tracman registration',
text: mail.text(`Welcome to Tracman! \n\nTo complete your registration, follow this link and set your password:\n${env.url}/settings/password/${token}`),
html: mail.html(`<p>Welcome to Tracman! </p><p>To complete your registration, follow this link and set your password:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p>`)
})
.then(()=>{
req.flash('success', `An email has been sent to <u>${user.email}</u>. Check your inbox to complete your registration. `);
res.redirect('/login');
}).catch((err)=>{
})
.catch((err)=>{
mw.throwErr(err,req);
res.redirect('/login#signup');
});
});
}
@ -204,7 +207,7 @@ module.exports = (app, passport) => {
else {
// Create reset token
user.createToken( (err,token)=>{
user.createPassToken( (err,token)=>{
if (err){ next(err); }
// Email reset link

View File

@ -40,22 +40,49 @@ router.route('/')
}
else {
// Update user document
User.findByIdAndUpdate(req.user.id, {$set:{
name: xss(req.body.name),
slug: slug(xss(req.body.slug)),
email: req.body.email,
settings: {
units: req.body.units,
defaultMap: req.body.map,
defaultZoom: req.body.zoom,
showScale: (req.body.showScale)?true:false,
showSpeed: (req.body.showSpeed)?true:false,
showAlt: (req.body.showAlt)?true:false,
showStreetview: (req.body.showStreet)?true:false
}
}})
.then( (user)=>{
// Confirm email change
if (req.user.email!==req.body.email) {
req.user.newEmail = req.body.email;
req.user.createEmailToken((err,token)=>{
if (err){
mw.throwErr(err,req);
res.redirect(req.session.next||'/settings');
}
// Send token to user by email
mail.send({
to: `"${req.user.name}" <${req.body.email}>`,
from: mail.from,
subject: 'Confirm your new email address for Tracman',
text: mail.text(`A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. \n\nTo confirm your email, follow this link:\n${env.url}/settings/email/${token}. `),
html: mail.html(`<p>A request has been made to change your Tracman email address. If you did not initiate this request, please disregard it. </p><p>To confirm your email, follow this link:<br><a href="${env.url}/settings/email/${token}">${env.url}/settings/email/${token}</a>. </p>`)
})
.then( ()=>{
// Alert user to check email.
req.flash('warning',`An email has been sent to <u>${req.body.email}</u>. Check your inbox to confirm your new email. `);
})
.catch( (err)=>{
mw.throwErr(err,req);
});
});
}
// Set new user settings
req.user.name = xss(req.body.name);
req.user.slug = slug(xss(req.body.slug));
req.user.settings = {
units: req.body.units,
defaultMap: req.body.map,
defaultZoom: req.body.zoom,
showScale: (req.body.showScale)?true:false,
showSpeed: (req.body.showSpeed)?true:false,
showAlt: (req.body.showAlt)?true:false,
showStreetview: (req.body.showStreet)?true:false
};
req.user.save()
.then( ()=>{
req.flash('success', 'Settings updated. ');
res.redirect('/settings');
})
@ -74,17 +101,51 @@ router.route('/')
//TODO: Reenter password?
User.findByIdAndRemove(req.user)
.then(()=>{
.then( ()=>{
req.flash('success', 'Your account has been deleted. ');
res.redirect('/');
})
.catch((err)=>{
.catch( (err)=>{
mw.throwErr(err,req);
res.redirect('/settings');
});
} );
// Confirm email address
router.get('/email/:token', mw.ensureAuth, (req,res,next)=>{
// Check token
if ( req.user.emailToken===req.params.token) {
// Set new email
req.user.email = req.user.newEmail;
req.user.save().then( ()=>{
// Delete token and newEmail
req.user.emailToken = undefined;
req.user.newEmail = undefined;
req.user.save();
// Report success
req.flash('success',`Your email has been set to <u>${req.user.email}</u>. `);
res.redirect('/settings');
})
.catch( (err)=>{
mw.throwErr(err,req);
res.redirect(req.session.next||'/settings');
});
}
// Invalid token
else {
req.flash('danger', 'Email confirmation token is invalid. ');
res.redirect('/settings');
}
} );
// Set password
router.route('/password')
@ -96,30 +157,31 @@ router.route('/password')
.get( (req,res,next)=>{
// Create token for password change
req.user.createToken()
.then( (token)=>{
// Confirm password change request by email.
mail.send({
to: mail.to(req.user),
from: mail.from,
subject: 'Request to change your Tracman password',
text: mail.text(`A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org. \n\nTo change your password, follow this link:\n${env.url}/settings/password/${token}. \n\nThis request will expire in 1 hour. `),
html: mail.html(`<p>A request has been made to change your tracman password. If you did not initiate this request, please contact support at <a href="mailto:keith@tracman.org">keith@tracman.org</a>. </p><p>To change your password, follow this link:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a>. </p><p>This request will expire in 1 hour. </p>`)
}).then( ()=>{
// Alert user to check email.
req.flash('success',`An email has been sent to <u>${req.user.email}</u>. Check your inbox to complete your password change. `);
res.redirect('/login#login');
}).catch( (err)=>{
mw.throwErr(err,req);
res.redirect('/login#login');
});
req.user.createPassToken( (err,token)=>{
if (err){
mw.throwErr(err,req);
res.redirect(req.session.next||'/settings');
}
// Confirm password change request by email.
mail.send({
to: mail.to(req.user),
from: mail.from,
subject: 'Request to change your Tracman password',
text: mail.text(`A request has been made to change your tracman password. If you did not initiate this request, please contact support at keith@tracman.org. \n\nTo change your password, follow this link:\n${env.url}/settings/password/${token}. \n\nThis request will expire in 1 hour. `),
html: mail.html(`<p>A request has been made to change your tracman password. If you did not initiate this request, please contact support at <a href="mailto:keith@tracman.org">keith@tracman.org</a>. </p><p>To change your password, follow this link:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a>. </p><p>This request will expire in 1 hour. </p>`)
})
.then( ()=>{
// Alert user to check email.
req.flash('success',`An email has been sent to <u>${req.user.email}</u>. Check your inbox to complete your password change. `);
res.redirect('/login#login');
})
.catch( (err)=>{
mw.throwErr(err,req);
res.redirect('/password');
res.redirect('/login#login');
});
});
} );
@ -129,7 +191,7 @@ router.route('/password/:token')
.all( (req,res,next)=>{
User
.findOne({'auth.passToken': req.params.token})
.where('auth.tokenExpires').gt(Date.now())
.where('auth.passTokenExpires').gt(Date.now())
.then((user) => {
if (!user) {
req.flash('danger', 'Password reset token is invalid or has expired. ');
@ -163,7 +225,7 @@ router.route('/password/:token')
// Delete token
res.locals.passwordUser.auth.passToken = undefined;
res.locals.passwordUser.auth.tokenExpires = undefined;
res.locals.passwordUser.auth.passTokenExpires = undefined;
// Create hash
res.locals.passwordUser.generateHash( req.body.password, (err,hash)=>{

View File

@ -32,6 +32,7 @@ form input.btn[type="submit"] {
}
form #social-login {
justify-content: space-around;
flex-wrap: nowrap;
width: 100%;
}
#show {

View File

@ -43,7 +43,7 @@
{% endfor %}
{% for warning in warnings %}
<div class='alert alert-header alert-warning alert-dismissible shadow'>
<strong><i class="fa fa-exclamation-circle"></i> Whoops!</strong> {{ warning | safe }}
<strong><i class="fa fa-exclamation-circle"></i> Hey!</strong> {{ warning | safe }}
<a href="#" class='close' data-dismiss="alert" aria-label="close"><i class='fa fa-times'></i></a>
</div>
{% endfor %}