Added ability to change password

master
Keith Irwin 2017-04-12 13:41:27 -04:00
parent 46db57abd5
commit a15cf2c03f
No known key found for this signature in database
GPG Key ID: 378933C743E2BBC0
12 changed files with 219 additions and 63 deletions

View File

@ -19,15 +19,15 @@ module.exports = function(app, passport) {
failureFlash: true
},
loginCallback = function(req,res){
res.redirect( req.session.returnTo || '/settings' );
delete req.session.returnTo;
res.redirect( req.session.next || '/settings' );
delete req.session.next;
};
// Login/-out
app.route('/login')
.get( function(req,res){
if (req.isAuthenticated()){
res.redirect('/account'); }
res.redirect('/settings'); }
else { res.render('login'); }
})
.post( passport.authenticate('local',loginOutcome), loginCallback );
@ -75,7 +75,7 @@ module.exports = function(app, passport) {
// Forgot password
app.route('/login/forgot')
.all( function(req,res,next){
if (req.isAuthenticated()){ res.redirect('/account'); }
if (req.isAuthenticated()){ res.redirect('/settings'); }
else { next(); }
})
.get( function(req,res,next){
@ -103,8 +103,8 @@ module.exports = function(app, passport) {
from: '"Tracman" <NoReply@tracman.org>',
to: `"${user.name}"" <${user.email}>`,
subject: 'Reset your Tracman password',
text: `Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/account/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `,
html: `<p>Hi, </p><p>Did you request to reset your Tracman password? If so, follow this link to do so:<br><a href="${env.url}/account/password/${token}">${env.url}/account/password/${token}</a></p><p>If you didn't initiate this request, just ignore this email. </p>`
text: `Hi, \n\nDid you request to reset your Tracman password? If so, follow this link to do so:\n${env.url}/settings/password/${token}\n\nIf you didn't initiate this request, just ignore this email. `,
html: `<p>Hi, </p><p>Did you request to reset your Tracman password? If so, follow this link to do so:<br><a href="${env.url}/settings/password/${token}">${env.url}/settings/password/${token}</a></p><p>If you didn't initiate this request, just ignore this email. </p>`
}).then(function(){
req.flash('success', `An email has been sent to <u>${req.body.email}</u>. Check your email for instructions to reset your password. `);
res.redirect('/');
@ -131,11 +131,11 @@ module.exports = function(app, passport) {
} else { // Disconnect social account
req.user.auth[service] = undefined;
req.user.save(function(err){
if (err){ return next(err); }
if (err){ mw.throwErr(err,req); }
else {
req.flash('success', `${mw.capitalize(service)} account disconnected. `);
res.redirect('/account');
}
res.redirect('/settings');
});
}
@ -146,7 +146,7 @@ module.exports = function(app, passport) {
passport.authenticate(service, loginOutcome)(req,res,next);
} else {
req.flash('success', `${mw.capitalize(service)} account connected. `);
req.session.returnTo = '/account';
req.session.next = '/settings';
passport.authenticate(service, connectOutcome)(req,res,next);
}
}, loginCallback);
@ -190,7 +190,7 @@ module.exports = function(app, passport) {
// if (!user.name) { user.name=profile.displayName; }
// user.lastLogin = Date.now();
// user.save(function (err, raw) {
// if (err) { throwErr(req,err); }
// if (err) { throwErr(err,req); }
// }); done(null, user);
// }

View File

@ -5,7 +5,7 @@ const env = require('./env.js');
module.exports = {
// Throw error
throwErr: function(req,err){
throwErr: function(err,req=null){
console.error('Middleware error:'+err.message+'\nfor request:\n'+req);
if (env.mode==='production') {
req.flash('danger', 'An error occured. <br>Would you like to <a href="https://github.com/Tracman-org/Server/issues/new">report it</a>?');

View File

@ -53,17 +53,28 @@ const userSchema = new mongoose.Schema({
// Create password reset token
userSchema.methods.createToken = function(next){
//TODO: Use the same token if there already is one that's not yet expired.
var user = this;
crypto.randomBytes(16, function(err,buf){
if (err){ next(err,null); }
else {
user.auth.passToken = buf.toString('hex');
user.auth.tokenExpires = Date.now() + 3600000; // 1 hour
user.save();
return next(null,user.auth.passToken);
}
});
if ( user.auth.tokenExpires <= Date.now() ){
// Reuse old token, resetting clock
user.auth.tokenExpires = Date.now() + 3600000; // 1 hour
user.save();
return next(null.user.auth.passToken);
} else {
// Create new token
crypto.randomBytes(16, function(err,buf){
if (err){ next(err,null); }
else {
user.auth.passToken = buf.toString('hex');
user.auth.tokenExpires = Date.now() + 3600000; // 1 hour
user.save();
return next(null,user.auth.passToken);
}
});
}
};
// Check for valid password

View File

@ -105,13 +105,13 @@ module.exports = function(passport) {
if (service==='google') {
User.findOne({'googleID':parseInt(profileId)}, function(err,user){
// console.log(`searched for user with googleID ${profileId}`);
if (err){ mw.throwErr(err); }
if (err){ mw.throwErr(err,req); }
if (user) {
// console.log(`Lazily updating schema for ${user.name}.`);
user.auth.google = profileId;
user.googleId = null;
user.save(function(err){
if (err){ mw.throwErr(err); }
if (err){ mw.throwErr(err,req); }
return done(null, user);
});
} else {

View File

@ -14,7 +14,7 @@ router.route('/')
var checkCBC = function(req,res,err){
if (err) {
req.flash('error', err.message);
console.log(err);
console.error(err);
}
if (cbc<1){ cbc++; }
else { // done
@ -26,7 +26,7 @@ router.route('/')
}
};
User.findById(req.session.passport.user, function(err, found) {
User.findById(req.user, function(err, found) {
res.locals.user = found;
checkCBC(req,res,err);
});
@ -62,7 +62,7 @@ router.route('/testmail').get(function(req,res,next){
console.log("Test email should have sent...");
res.sendStatus(200);
}).catch(function(err){
mw.throwErr(err);
mw.throwErr(err,req);
next();
});
});

View File

@ -40,7 +40,7 @@ router.get('/validate', function(req,res){
if (req.query.slug) { // validate unique slug
User.findOne({slug:slug(req.query.slug)}, function(err, existingUser){
if (err) { console.log('/validate error:',err); }
if (existingUser && existingUser.id!==req.session.passport.user) { res.sendStatus(400); }
if (existingUser && existingUser.id!==req.user) { res.sendStatus(400); }
else { res.sendStatus(200); }
});
}

View File

@ -16,7 +16,7 @@ router.get('/:slug?', function(req,res,next){
// Confirm sucessful queries
function checkQuery(err,found) {
if (err){ mw.throwErr(req,err); }
if (err){ mw.throwErr(err,req); }
if (found){ return found; }
}
@ -29,7 +29,7 @@ router.get('/:slug?', function(req,res,next){
// QUERIES
// Get logged in user -> user
if (req.isAuthenticated()) {
User.findById(req.session.passport.user, function(err, found) {
User.findById(req.user, function(err, found) {
user = checkQuery(err,found);
checkCBC();
});

View File

@ -17,15 +17,15 @@ router.route('/')
// Get settings form
.get(function(req,res,next){
User.findById(req.session.passport.user, function(err,user){
if (err){ console.log('Error finding settings for user:',err); mw.throwErr(req,err); }
User.findById(req.user, function(err,user){
if (err){ mw.throwErr(err,req); }
res.render('settings');
});
})
// Set new settings
.post(function(req,res,next){
User.findByIdAndUpdate(req.session.passport.user, {$set:{
User.findByIdAndUpdate(req.user, {$set:{
name: xss(req.body.name),
slug: slug(xss(req.body.slug)),
email: req.body.email,
@ -38,7 +38,7 @@ router.route('/')
showStreetview: (req.body.showStreet)?true:false
}
}}, function(err, user){
if (err) { console.log('Error updating user settings:',err); mw.throwErr(req,err); }
if (err) { mw.throwErr(err,req); }
else { req.flash('success', 'Settings updated. '); }
res.redirect('/settings');
});
@ -46,65 +46,131 @@ router.route('/')
// Delete user account
.delete(function(req,res,next){
User.findByIdAndRemove( req.session.passport.user,
User.findByIdAndRemove( req.user,
function(err) {
if (err) {
console.log('Error deleting user:',err);
mw.throwErr(req,err);
if (err) {
mw.throwErr(err,req);
} else {
req.flash('success', 'Your account has been deleted. ');
res.redirect('/');
}
res.redirect('/');
}
);
});
// Set password
router.route('/password')
router.route('/password/')
.all(mw.ensureAuth,function(req,res,next){
next();
})
// Email user a token, proceed at /password/:token
.get(function(req,res,next){
// Create token for password change
req.user.createToken(function(err,token){
if (err){ next(err); }
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}. `),
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>`)
}).then(function(){
req.flash('success',`An email has been sent to <u>${req.user.email}</u>. Check your inbox to complete your password change. `);
res.redirect(req.query.next||'/settings');
}).catch(function(err){
next(err);
});
});
if (err){ next(err); }
// 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>`)
}).catch(function(err){
mw.throwErr(err,req);
}).then(function(){
// 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( (req.isAuthenticated)?'/settings':'/login' );
});
});
});
router.route('/password/:token')
// Check token
.all(function(req,res,next){
User
.findOne({'auth.passToken': req.params.token})
.where('auth.tokenExpires').gt(Date.now())
//TODO: Add own promise libary
.exec((err, user) => {
if (err) { mw.throwErr(err,req); }
if (!user) {
req.flash('danger', 'Password reset token is invalid or has expired. ');
res.redirect( (req.isAuthenticated)?'/settings':'/login' );
} else {
res.locals.passwordUser = user;
next();
}
});
})
// Show password change form
.get(function(req,res){
res.render('password');
})
.post(function(req,res,next){
// Validate matching passwords
if (req.body.password!==req.body.repassword) {
mw.throwErr( new Error('Passwords do not match. '), req );
} else {
//TODO: Add logic for new users
// Delete token
res.locals.passwordUser.auth.passToken = undefined;
res.locals.passwordUser.auth.tokenExpires = undefined;
// Create hash
res.locals.passwordUser.generateHash(req.body.password, function(err,hash){
if (err){ mw.throwErr(err); }
else {
// Save new password to db
res.locals.passwordUser.auth.password = hash;
res.locals.passwordUser.save( function(err){
if (err){ mw.throwErr(err,req); }
else {
req.flash('success', 'Password set. You can use it to log in now. ');
}
});
}
});
}
res.redirect('/login');
});
// Tracman pro
router.route('/pro').all(mw.ensureAuth, function(req,res,next){
router.route('/pro')
.all(mw.ensureAuth, function(req,res,next){
next();
})
// Get info about pro
.get(function(req,res,next){
User.findById(req.session.passport.user, function(err, user){
if (err){ mw.throwErr(req,err); }
if (!user){ next(); }
else { res.render('pro'); }
});
res.render('pro');
})
// Join Tracman pro
.post(function(req,res){
User.findByIdAndUpdate(req.session.passport.user,
User.findByIdAndUpdate(req.user.id,
{$set:{ isPro:true }},
function(err, user){
if (err){ mw.throwErr(req,err); }
if (err){ mw.throwErr(err,req); }
else { req.flash('success','You have been signed up for pro. '); }
res.redirect('/map');
}

27
config/routes/test.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
const router = require('express').Router(),
mw = require('../middleware.js'),
mail = require('../mail.js');
router.route('/mail').get(function(req,res,next){
mail.send({
to: `"Keith Irwin" <hypergeek14@gmail.com>`,
from: mail.from,
subject: 'Test email',
text: mail.text("Looks like everything's working! "),
html: mail.html("<p>Looks like everything's working! </p>")
}).then(function(){
console.log("Test email should have sent...");
res.sendStatus(200);
}).catch(function(err){
mw.throwErr(err,req);
next();
});
});
router.route('/password').get(function(req,res){
res.render('password');
});
module.exports = router;

View File

@ -79,7 +79,7 @@ const
// console.log(`Setting local variables for request to ${req.path}.`);
// User account
res.locals.user = req.user;
// res.locals.user = req.user;
// console.log(`User set as ${res.locals.user}. `);
// Flash messages
@ -103,6 +103,11 @@ const
// Site administration
app.use( '/admin', require('./config/routes/admin.js') );
// Testing
if (env.mode == 'development') {
app.use( '/test', require('./config/routes/test.js' ) );
}
}
/* Errors */ {

47
views/password.html Normal file
View File

@ -0,0 +1,47 @@
{% extends 'templates/base.html' %}
{% block title %}{{super()}} | Set Password{% endblock %}
{% block head %}
{{super()}}
<link rel="stylesheet" type="text/css" href="/static/css/form.css">
{% endblock %}
{% block main %}
<section class='container'>
<h1>Set Password</h1>
<form id='password-form' role="form" method="post">
<style>
#password input {
min-width: 40%;
}
</style>
<p>Your password must be at least 8 characters long. You can use any letter, number, symbol, emoji, or spaces. Your password will be stored as a secure hash on the server. </p>
<div id='password' class='form-group' title="Type your new password here">
<input class='form-control' name="password" type="password" placeholder="enter password" minlength="8" maxlength="160">
<input class='form-control' name="repassword" type="password" placeholder="retype password" minlength="8" maxlength="160">
</div>
<div id='submit-group' class='form-group flexbox' style="padding:0 0 60px; justify-content:space-around">
<input class='btn yellow' style="width:50%; background:#333" type="submit" value="Save">
<a href="#" class='btn'>cancel</a>
</div>
</form>
</section>
{% endblock %}
{% block javascript %}
{{super()}}
<script>
</script>
{% endblock %}

View File

@ -97,7 +97,7 @@
</div>
<div id='password-delete' class='form-group'>
<a class='underline' href="/settings/password?next=/settings" title="Click here to {% if user.auth.password %}change{% else %}set{% endif %} your password. ">{% if user.auth.password %}Change{% else %}Set{% endif %} password</a>
<a class='underline' href="/settings/password" title="Click here to {% if user.auth.password %}change{% else %}set{% endif %} your password. ">{% if user.auth.password %}Change{% else %}Set{% endif %} password</a>
<a class='red underline' style="text-align:right" href="#" onclick="deleteAccount()" title="Permently delete your Tracman account. ">Delete account</a>
</div>