'use strict';
var async = require('async'),
appSettings,
appenvironment,
bcrypt = require('bcrypt'),
CoreController,
CoreUtilities,
ControllerHelper = require('periodicjs.core.controller'),
CoreMailer = require('periodicjs.core.mailer'),
Utilities = require('periodicjs.core.utilities'),
jwt = require('jsonwebtoken'),
loginExtSettings,
logger,
mongoose,
User,
path = require('path'),
passport;
// Utility Functions
var waterfall = function (array, cb) {
async.waterfall(array, cb);
};
var encode = function (data) {
return jwt.sign(data, loginExtSettings.token.secret);
};
var decode = function (data, cb) {
jwt.verify(data, loginExtSettings.token.secret, {}, function (err, decoded_token) {
if (err) {
console.log('Error from JWT.verify', err.name);
console.log('Error from JWT.verify', err.message);
cb(err);
}
else {
cb(null, decoded_token);
}
});
};
var hasExpired = function (token_expires_millis) {
var now = new Date();
var diff = (now.getTime() - token_expires_millis);
return diff > 0;
};
var invalidateUserToken = function (req, res, next, cb) {
var token = req.controllerData.token;
User.findOne({
'attributes.reset_token': token
}, function (err, usr) {
if (err) {
console.log('error finding the user for invalidate token fn');
cb(err, null);
}
else {
usr.attributes.reset_token = '';
usr.attributes.reset_token_link = '';
usr.attributes.reset_token_expires_millis = 0;
cb(false, req, res, next, usr);
}
});
};
var resetPassword = function (req, res, next, user, cb) {
var err;
//console.log('loginExtSettings', loginExtSettings);
if (req.body.password) {
if (req.body.password !== req.body.passwordconfirm) {
err = new Error('Passwords do not match');
req.flash('error', err);
cb(err, null);
}
else if (req.body.password === undefined || req.body.password.length < loginExtSettings.new_user_validation.length_of_password) {
err = new Error('Password is too short');
req.flash('error', err);
cb(err, null);
}
else {
var salt = bcrypt.genSaltSync(10),
hash = bcrypt.hashSync(req.body.password, salt);
user.password = hash;
cb(null, user, req);
}
}
};
/**
* description The save user function has two special fn calls on the model to mark the properties on it as changed/modified this gets around some werid edge cases when its being updated in memory but not save in mongo
*
*/
function saveUser(user, req, cb) {
user.markModified('attributes');
user.markModified('password');
user.save(function (err, usr) {
if (err) {
cb(err, null);
}
cb(null, usr, req);
});
}
var getUser = function (req, res, next, cb) {
User.findOne({
email: req.body.email
}, function (err, user) {
if (err) {
cb(err, null);
}
else if (user) {
cb(false, user, req);
}
else {
req.flash('error', 'No user with that email found!');
cb(new Error('No user with that email found.'), null);
}
});
};
var generateToken = function (user, req, cb) {
//Generate reset token and URL link; also, create expiry for reset token
//make sure attributes exists || create it via merge
var salt = bcrypt.genSaltSync(10);
var now = new Date();
var expires = new Date(now.getTime() + (loginExtSettings.token.resetTokenExpiresMinutes * 60 * 1000)).getTime();
user.attributes = {};
user.attributes.reset_token = encode({
email: user.email,
apikey: user.apikey
});
user.attributes.reset_token_link = CoreUtilities.makeNiceName(bcrypt.hashSync(user.attributes.reset_token, salt));
user.attributes.reset_token_expires_millis = expires;
//TODO: Look into why mongoose properties
//are not being saved during async fn calls
user.markModified('attributes');
user.save(function (err) {
if (err) {
cb(err, null);
}
cb(null, user, req);
});
};
// create a func for the mail options
var emailForgotPasswordLink = function (user, req, cb) {
CoreController.getPluginViewDefaultTemplate({
viewname: 'email/user/forgot_password_link',
themefileext: appSettings.templatefileextension
},
function (err, templatepath) {
if (err) {
cb(err);
}
else {
// console.log('user for forgot password', user);
if (templatepath === 'email/user/forgot_password_link') {
templatepath = path.resolve(process.cwd(), 'node_modules/periodicjs.ext.login/views', templatepath + '.' + appSettings.templatefileextension);
}
CoreMailer.sendEmail({
appenvironment: appenvironment,
to: user.email,
replyTo: appSettings.adminnotificationemail,
subject: appSettings.name + ' - Reset your password',
emailtemplatefilepath: templatepath,
emailtemplatedata: {
user: user,
appname: appSettings.name,
hostname: req.headers.host
}
}, cb);
}
}
);
// cb(null, options);
};
var emailResetPasswordNotification = function (user, req, cb) {
CoreController.getPluginViewDefaultTemplate({
viewname: 'email/user/reset_password_notification',
themefileext: appSettings.templatefileextension
},
function (err, templatepath) {
if (err) {
cb(err);
}
else {
// console.log('user for forgot password', user);
if (templatepath === 'email/user/reset_password_notification') {
templatepath = path.resolve(process.cwd(), 'node_modules/periodicjs.ext.login/views', templatepath + '.' + appSettings.templatefileextension);
}
CoreMailer.sendEmail({
appenvironment: appenvironment,
to: user.email,
replyTo: appSettings.adminnotificationemail,
subject: appSettings.name + ' - Password reset notification',
emailtemplatefilepath: templatepath,
emailtemplatedata: {
user: user,
appname: appSettings.name,
hostname: req.headers.host
}
}, cb);
}
}
);
// cb(null, options);
};
//Post to auth/forgot with the users email
var forgot = function (req, res, next) {
var arr = [
function (cb) {
cb(null, req, res, next);
},
getUser,
generateToken,
emailForgotPasswordLink
];
waterfall(arr,
function (err /*, results*/ ) {
if (err) {
req.flash('error', err.message);
res.redirect('/auth/forgot');
}
else {
req.flash('info', 'Password reset instructions were sent to your email address');
res.redirect(loginExtSettings.settings.authLoginPath);
}
});
};
var get_token = function (req, res, next) {
req.controllerData = (req.controllerData) ? req.controllerData : {};
User.findOne({
'attributes.reset_token_link': req.params.token
}, function (err, user_with_token) {
if (err) {
req.flash('error', err.message);
res.redirect(loginExtSettings.settings.authLoginPath);
}
else if (!user_with_token || !user_with_token.attributes.reset_token) {
req.flash('error', 'invalid reset token');
res.redirect(loginExtSettings.settings.authLoginPath);
}
else if (hasExpired(user_with_token.attributes.reset_token_expires_millis)) {
req.flash('error', 'Password reset token is has expired.');
res.redirect(loginExtSettings.settings.authLoginPath);
}
else {
req.controllerData.token = user_with_token.attributes.reset_token;
next();
}
});
};
//GET if the user token is vaild show the change password page
var reset = function (req, res) {
var token = req.controllerData.token,
// current_user,
decode_token;
decode(token, function (err, decode) {
if (err) {
CoreController.handleDocumentQueryErrorResponse({
err: err,
res: res,
req: req,
errorflash: err.message
});
}
else {
decode_token = decode;
//Find the User by their token
User.findOne({
'attributes.reset_token': token
}, function (err, found_user) {
if (err || !found_user) {
req.flash('error', 'Password reset token is invalid.');
res.redirect(loginExtSettings.settings.authLoginPath);
}
// current_user = found_user;
//Check to make sure token hasn't expired
//Check to make sure token is valid and sign by us
else if (found_user.email !== decode_token.email && found_user.api_key !== decode_token.api_key) {
req.flash('error', 'This token is not valid please try again');
res.redirect(loginExtSettings.settings.authLoginPath);
}
else {
CoreController.getPluginViewDefaultTemplate({
viewname: 'user/reset',
themefileext: appSettings.templatefileextension,
extname: 'periodicjs.ext.login'
},
function (err, templatepath) {
CoreController.handleDocumentQueryRender({
res: res,
req: req,
renderView: templatepath,
responseData: {
pagedata: {
title: 'Reset Password',
current_user: found_user
},
user: req.user
}
});
});
}
});
}
});
};
//POST change the users old password to the new password in the form
var token = function (req, res, next) {
var user_token = req.controllerData.token;
waterfall([
function (cb) {
cb(null, req, res, next);
},
invalidateUserToken,
resetPassword,
saveUser,
emailResetPasswordNotification
],
function (err /*, results*/ ) {
if (err) {
req.flash('error', err.message);
res.redirect('/auth/reset/' + user_token);
}
else {
req.flash('success', 'Password Sucessfully Changed!');
res.redirect(loginExtSettings.settings.authLoginPath);
}
});
};
var tokenController = function (resources, passportResources) {
appSettings = resources.settings;
CoreController = new ControllerHelper(resources);
CoreUtilities = new Utilities(resources);
loginExtSettings = passportResources.loginExtSettings;
logger = resources.logger;
mongoose = resources.mongoose;
passport = passportResources.passport;
User = mongoose.model('User');
appenvironment = appSettings.application.environment;
return {
forgot: forgot,
reset: reset,
get_token: get_token,
token: token
};
};
module.exports = tokenController;