/**
* @fileOverview Authentication API Controller
* @class authentication
* @author Calvin Ho
*/
const UserDataController = require('./user-data');
const passport = require('passport');
const bcrypt = require('bcrypt-nodejs');
const jwt = require('jsonwebtoken');
const randtoken = require('rand-token');
//const mongoose = require('mongoose');
//const nev = require('email-verification')(mongoose);
const User = require('../models/user');
const TempUser = require('../models/tempuser');
const Churches = require('../controllers/churches');
const plivo = require('plivo');
const client = new plivo.Client(); // best practice is to store the id and token in env var $PLIVO_AUTH_ID and $PLIVO_AUTH_TOKEN
const emailVerificationConfig = require('../../config/email-verification');
const authConfig = require('../../config/auth');
const nodemailer = require('nodemailer');
const nodemailerSettings = require('../../config/nodemailer');
const transporter = nodemailer.createTransport(nodemailerSettings);
const path = require('path');
//const EmailTemplate = require('email-templates').EmailTemplate;
const Email = require('email-templates');
const templatesDir = path.resolve(__dirname, '..', '..', 'templates');
function generateMobileAppToken(user){
return jwt.sign(user, authConfig.secret, {
expiresIn: "100 years"
});
}
function generateBrowserToken(user){
return jwt.sign(user, authConfig.secret, {
expiresIn: "7 days"
});
}
/**
* @func login
* @desc Authenticates user login
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} returns object with JWT token and user
*/
exports.login = (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
if (!user) return res.status(401).json(info);
req.user = user;
let userInfoToGenerateToken = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name,
};
delete user.suspensionExpiredAt; //hide this from the API response for security reasons
if (req.body.loginDeviceType === 'mobile'){
res.status(200).json({
token: 'JWT ' + generateMobileAppToken(userInfoToGenerateToken),
user: user
});
} else {
res.status(200).json({
token: 'JWT ' + generateBrowserToken(userInfoToGenerateToken),
user: user
});
}
})(req, res, next);
};
/**
* @func checkToken
* @desc Checks JWT Token
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} returns success status
*/
exports.checkToken = (req, res, next) => {
console.log("req", req.path);
passport.authenticate('jwt', (err, user, info) => {
if (req.path === '/protected') {
if (err) return next(err);
if (!user) return res.status(401).json(info);
delete user.suspensionExpiredAt; //hide this from the API response for security reasons
res.status(200).send({
content: 'Success',
user: user
});
} else {
if (user) {
req.user = user;
}
return next();
}
})(req, res, next);
};
/**
* @func registerMobile
* @desc Runs some validation on number, registers phone number and sends a verification text
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} Sends status messages regarding phone number validation
*/
exports.registerMobile = async (req, res, next) => {
try {
let mobile_phone = req.body.mobile_phone;
if (!mobile_phone) {
return res.status(422).json({
msg: 'You must enter a mobile number',
message: 'You must enter a mobile number'
});
}
let code = Math.floor(Math.random() * 900000) + 100000;
console.log("code", mobile_phone, code);
let users = [];
try {
users = await User.find({
$or: [{ mobile: mobile_phone }, { mobile_phone: mobile_phone }]
});
} catch (err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to access the user database.', err: err});
}
if (users.length) { // if there is at least one account found
try {
promises = users.map( async (user) => {
//Found the verified phone number. Add the recovery code, and send it to the phone
user.recoveryCode = code;
if (user.mobile) {
user.lockedAt = new Date(); //if the account already has a verified cell number, lock the account
}
await User.updateOne({ _id: user._id}, { $set: user });
});
await Promise.all(promises);
} catch (err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to add recovery code to database.', err: err});
}
//send code to mobile phone
client.messages.create(
+18777207028,
mobile_phone,
'Your Restvo verification code: ' + code.toString()
).then((message_created) => {
console.log(message_created);
return res.json({
success: true,
msg: 'You will receive a code to verify your identity.',
message: 'You will receive a code to verify your identity.'
});
}, (err) => {
console.log(err);
return res.json({
success: true,
msg: 'Cannot send verification code. Please try again later.',
message: 'Cannot send verification code. Please try again later.'
});
});
} else {
TempUser.findOne({ mobile: mobile_phone }, function(err, tempuser) {
if (err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to access the temp-recovery user database.', err: err});
}
let first_name = req.body.first_name;
let last_name = req.body.last_name;
if (tempuser) {
//if already registered but needed the code again
if (req.body.type === 'register') { //update first and last name
tempuser.first_name = first_name;
tempuser.last_name = last_name;
}
tempuser.GENERATED_VERIFYING_SMS_CODE = code;
tempuser.save(function(err){
if(err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to update temp-recovery user.', err: err});
}
//re-send code to mobile phone
client.messages.create(
+18777207028,
mobile_phone,
'Your Restvo verification code: ' + code.toString()
).then(function(message_created) {
console.log(message_created)
});
return res.json({
success: true,
msg: 'We have sent you a code to verify your identity.',
message: 'We have sent you a code to verify your identity.'
});
})
} else {
if (req.body.type === 'register') { //provides first and last name
//if this is a new account
data = {
mobile: mobile_phone,
mobile_phone: mobile_phone,
first_name: first_name,
last_name: last_name,
primary_email: req.body.primary_email,
password: req.body.password,
role: 'user',
enablePushNotification: false,
appName: "com.restvo.app",
GENERATED_VERIFYING_SMS_CODE: code
};
console.log("create tempuser: ", JSON.stringify(data));
TempUser.create(data, function (err) {
if (err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to create temp-recovery user.', err: err});
}
//send code to phone
client.messages.create(
+18777207028,
mobile_phone,
'Your Restvo verification code: ' + code.toString()
).then(function(message_created) {
console.log(message_created)
});
return res.json({
success: true,
msg: 'We have sent you a code to verify your identity.',
message: 'We have sent you a code to verify your identity.'
});
})
} else {
return res.json({
msg: 'This phone number is not associated with an account. Please check your entry or create a new account.',
message: 'This phone number is not associated with an account. Please check your entry or create a new account.'
});
}
}
});
}
} catch (err) {
next({topic: 'Mongodb Error', title: 'registerMobile', err: err});
}
};
/**
* @func verifyMobile
* @desc Verifies that One-Time-Password is correct for user
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} returns success status and user object
*/
exports.verifyMobile = async (req, res, next) => {
try {
let mobile_phone = req.body.mobile_phone;
let loginDeviceType = req.body.loginDeviceType || 'mobile';
let code = req.body.code;
if (!code) {
return res.json({
msg: 'You must enter a code',
message: 'You must enter a code',
});
}
if (!mobile_phone || !code) {
return res.json({
msg: 'This verification code is expired.',
message: 'This verification code is expired.',
});
}
query = {
mobile: mobile_phone,
GENERATED_VERIFYING_SMS_CODE: code
};
let user = await User.findOne({ mobile: mobile_phone, recoveryCode: code });
if (user) { // verified user who tries to recover his account
user.recoveryCode = 0;
user.mobile = mobile_phone;
try {
await user.save();
let userData = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name,
role: user.role,
mobile: user.mobile,
appName: "com.restvo.app",
enablePushNotification: true
};
if (loginDeviceType === 'mobile'){
token = generateMobileAppToken(userData);
} else {
token = generateBrowserToken(userData);
}
return res.json({
success: true,
msg: 'Mobile number verified.',
message: 'Mobile number verified.',
token: 'JWT ' + token,
user: user //return the full user document
});
} catch (err) {
return next({topic: 'Mongodb Error', title: 'verifyMobile - Failed to save to the user database.', err: err});
}
} else {
try { // if no existing user found, look for a temp-recovery user doc
const tempUserData = await TempUser.findOne(query);
if (tempUserData) { // temp-recovery user is found (i.e. user verified mobile SMS code before the code expired)
let userData = JSON.parse(JSON.stringify(tempUserData)), // copy data
user;
delete userData.GENERATED_VERIFYING_SMS_CODE;
user = new User(userData);
// save the temporary user to the persistent user collection
try {
await user.save();
UserDataController.findNewUserConnections(user._id, false); //call this to search for new user connections
try {
await TempUser.deleteMany(query);
//log the user in
if(loginDeviceType === 'mobile'){
token = generateMobileAppToken(userData);
}
else{
token = generateBrowserToken(userData);
}
return res.json({
success: true,
msg: "Mobile number verified.",
message: "Mobile number verified.",
token: 'JWT ' + token,
user: user //return the full user document
});
} catch (err) {
return next({topic: 'Mongodb Error', title: 'verifyMobile - Failed to remove the temp-recovery user from database.', err: err});
}
} catch (err) {
return next({topic: 'Mongodb Error', title: 'verifyMobile - Failed to save the persistent user collection.', err: err});
}
} else { // verifying a cell number for an existing account
const check_verified_user = await User.findOne({ mobile: mobile_phone });
if (check_verified_user) {
return res.json({
success: false,
msg: 'This number has already been used and been verified for another user. Please try a different number.',
message: 'This number has already been used and been verified for another user. Please try a different number.'
});
} else {
if (req.body._id) { // verifying from the User Profile page
const user = await User.findOneAndUpdate({_id: req.body._id, recoveryCode: code}, {$set: { mobile: mobile_phone, recoveryCode: 0 }}, { new: true });
let userData = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name,
role: user.role,
mobile: user.mobile,
appName: "com.restvo.app",
enablePushNotification: true
};
if (loginDeviceType === 'mobile'){
token = generateMobileAppToken(userData);
} else {
token = generateBrowserToken(userData);
}
return res.json({
success: true,
msg: 'Mobile number verified.',
message: 'Mobile number verified.',
token: 'JWT ' + token,
user: user //return the full user document
});
} else { // recovering an account of an unverified cell number, with the condition that only one such account exists. it fails if multiple accounts are associated with this unverified number
const unverified_users = await User.find({ mobile_phone: mobile_phone, recoveryCode: code });
if (unverified_users && unverified_users.length === 1) {
user = unverified_users[0];
user.recoveryCode = 0;
user.mobile = mobile_phone;
try {
await user.save();
let userData = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name,
role: user.role,
mobile: user.mobile,
appName: "com.restvo.app",
enablePushNotification: true
};
if (loginDeviceType === 'mobile'){
token = generateMobileAppToken(userData);
} else {
token = generateBrowserToken(userData);
}
return res.json({
success: true,
msg: 'Mobile number verified.',
message: 'Mobile number verified.',
token: 'JWT ' + token,
user: user //return the full user document
});
} catch (err) {
return next({topic: 'Mongodb Error', title: 'verifyMobile - Failed to verify cell number for an existing account.', err: err});
}
} else {
return res.json({
msg: 'The verification code is incorrect, or it is expired. Please try again.',
message: 'The verification code is incorrect, or it is expired. Please try again.',
});
}
}
}
}
} catch (err) {
return next({topic: 'Mongodb Error', title: 'verifyMobile - Failed to load the Temp User database.', err: err});
}
}
} catch (err) {
next({topic: 'System Error', title: 'verifyMobile', err: err});
}
};
/**
* @func registerEmail
* @desc Step 1 - In registration via email. An email is sent to the user inbox. Upon clicking on the email, it will load the webapp showfeature.ts, which will process the verification token.
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} sends verification email object
*/
exports.registerEmail = async (req, res, next) => {
try {
let email = req.body.email;
let url = randtoken.generate(emailVerificationConfig.URLLength);
if (email === 'calvin+e2e1@restvo.com') { // for testing purposes, this url token will always be used for e2e testing account
url = '9LL1tFgDTH9skXdYmoofMmPmgwYLaAQ78elfIWu6xRebj2L7';
}
if (req.body.type === 'register') { // in Register page, when user is registering a new email address
let password = req.body.password;
let first_name = req.body.first_name;
let last_name = req.body.last_name;
let newUser = new TempUser({
email: email,
password: password,
role: 'user', //default all new users to 'user' role
first_name: first_name,
last_name: last_name,
shareContactInfo: false,
enablePushNotification: false,
appName: "com.restvo.app",
GENERATED_VERIFYING_URL: url,
listInDirectory: true
});
if (!email) {
return res.json({
msg: 'You must enter an email address',
message: 'You must enter an email address',
});
}
if (!password) {
return res.json({
msg: 'You must enter a password',
message: 'You must enter a password',
});
}
try { //if this is a new account creation
existingPersistentUser = await User.findOne({
$or: [{ email: email }, { primary_email: email }]
});
// user already exists in persistent collection
if (existingPersistentUser) {
const result = await exports.saveURLSendVerificationEmail(existingPersistentUser._id, existingPersistentUser.first_name, email);
res.json(result);
} else {
// user has registered but has not verified
const tempUser = await TempUser.findOne({ email: email });
if (tempUser) {
if (email === 'calvin+e2e1@restvo.com') { // in e2e test, just send response but do not send email
res.json({
success: true,
message: 'This is a E2E test. No email is sent. The test will continue to proceed.',
msg: 'This is a E2E test. No email is sent. The test will continue to proceed.',
});
} else if (tempUser.GENERATED_VERIFYING_URL) {
const template = new Email({
views: {
options: {
extension: 'hbs'
}
},
message: { from: '"Restvo" <no-reply@restvo.com>' },
send: true,
transport: transporter,
subjectPrefix: process.env.CRON ? false : `[Dev] `,
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
relativeTo: path.join(templatesDir, 'verification-email')
}
}
});
template.send({
template: path.join(templatesDir, 'verification-email'),
message: { to: email },
locals: {
email: email,
link: 'https://app.restvo.com/activity/' + (req.body.routeId || '5d5785b462489003817fee18') + ';verify=' + tempUser.GENERATED_VERIFYING_URL + (req.body.serializedRouteParams || '')
}
})
.then(() => {
console.log("SENT");
res.json({
success: true,
message: 'An email has been sent to you. Please check it to activate your account. Check your spam folder in case the email was listed as spam.',
msg: 'An email has been sent to you. Please check it to activate your account. Check your spam folder in case the email was listed as spam.',
//info: results
});
})
.catch((err) => console.log(err))
} else { // cannot find the verification URL.
res.json({
message: 'We cannot create an account using this email address. Please try another email address.',
msg: 'We cannot create an account using this email address. Please try another email address.'
});
}
} else {
// user has not registered, so create the temp-recovery user document
TempUser.create(newUser, function (err) {
if (err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to create temp-recovery user.', err: err});
}
if (email === 'calvin+e2e1@restvo.com') { // in e2e test, just send response but do not send email
res.json({
success: true,
message: 'This is a E2E test. No email is sent. The test will continue to proceed.',
msg: 'This is a E2E test. No email is sent. The test will continue to proceed.',
});
} else {
const template = new Email({
views: {
options: {
extension: 'hbs'
}
},
message: { from: '"Restvo" <no-reply@restvo.com>' },
send: true,
transport: transporter,
subjectPrefix: process.env.CRON ? false : `[Dev] `,
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
relativeTo: path.join(templatesDir, 'verification-email')
}
}
});
template.send({
template: path.join(templatesDir, 'verification-email'),
message: { to: email },
locals: {
email: email,
link: 'https://app.restvo.com/activity/' + (req.body.routeId || '5d5785b462489003817fee18') + ';verify=' + url + (req.body.serializedRouteParams || '')
}
})
.then(() => {
console.log("SENT");
res.json({
success: true,
msg: 'Please check your email and follow the instructions to reset your password.',
message: 'Please check your email and follow the instructions to reset your password.'
});
})
.catch((err) => console.log(err))
}
})
}
}
} catch (err) {
return next({topic: 'Mongodb Error', title: 'registerMobile - Failed to access the user database.', err: err});
}
// already a user
// 1) in authenticated view, user want to verify a new primary email address and convert it into a verified email
// 2) in register page, after a user verifies via SMS code, it also verifies the email address
} else {
User.findOne({ email: email }, function(err, existingPersistentUser){
if(err) {
return next({topic: 'Mongodb Error', title: 'registerEmail - Failed to load the User database.', err: err});
}
if (existingPersistentUser){
return res.json(
{ msg: 'Another account is already associated with this email address.',
message: 'Another account is already associated with this email address.'});
} else { // case 1 and 2, new email is not used by other users yet
User.findById(req.body._id, (err, user) => {
if (err) {
return next({topic: 'Mongodb Error', title: 'registerEmail - Failed to load the User database.', err: err});
}
if (user) { // case 1 and 2, send verification email
User.updateOne({_id: req.body._id}, {$set: {recoveryURL: url}},
function (err) {
if (err) {
return next({topic: 'Mongodb Error', title: 'registerEmail - Failed to save the verification URL to the user record.', err: err});
}
const template = new Email({
views: {
options: {
extension: 'hbs'
}
},
message: { from: '"Restvo" <no-reply@restvo.com>' },
send: true,
transport: transporter,
subjectPrefix: process.env.CRON ? false : `[Dev] `,
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
relativeTo: path.join(templatesDir, 'verification-email')
}
}
});
template.send({
template: path.join(templatesDir, 'verification-email'),
message: { to: email },
locals: {
email: email,
link: 'https://app.restvo.com/activity/5d5785b462489003817fee18;verify=' + url
}
})
.then(() => {
console.log("SENT");
res.json({
success: true,
msg: 'Please check your email and follow the instructions to verify your email address.',
message: 'Please check your email and follow the instructions to verify your email address.'
});
})
.catch((err) => console.log(err))
});
} else { // this case is not possible, since user needs to be authenticated user. assuming the TempUser record has already been created
exports.resendVerificationEmail(email, res, next);
}
})
}
});
}
} catch (err) {
next({topic: 'System Error', title: 'registerEmail', err: err});
}
};
/**
* Resend the verification email to the user given only their email.
*
* @func resendVerificationEmail
* @param {object} email - the user's email address
* @param {function} callback - the callback function
*/
exports.resendVerificationEmail = async (email, res, next) => {
var query = {};
query[emailVerificationConfig.emailFieldName] = email;
TempUser.findOne(query, function(err, tempUser) {
if (err) {
return next({topic: 'System Error', title: 'registerEmail - send verification email FAILED', err: err});
}
// user found (i.e. user re-requested verification email before expiration)
if (tempUser) {
// generate new user token
tempUser[emailVerificationConfig.URLFieldName] = randtoken.generate(emailVerificationConfig.URLLength);
tempUser.save(function(err) {
if (err) {
return next({topic: 'System Error', title: 'registerEmail - send verification email FAILED', err: err});
}
const template = new Email({
views: {
options: {
extension: 'hbs'
}
},
message: { from: '"Restvo" <no-reply@restvo.com>' },
send: true,
transport: transporter,
subjectPrefix: process.env.CRON ? false : `[Dev] `,
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
relativeTo: path.join(templatesDir, 'verification-email')
}
}
});
template.send({
template: path.join(templatesDir, 'verification-email'),
message: {
to: tempUser[emailVerificationConfig.emailFieldName]
},
locals: {
email: tempUser[emailVerificationConfig.emailFieldName],
link: 'https://app.restvo.com/activity/5d5785b462489003817fee18;verify=' + tempUser[emailVerificationConfig.URLFieldName]
}
})
.then(() => {
console.log("success");
res.json({
success: true,
msg: 'An email has been sent to you. Please check it to activate your account. Check your spam folder in case the email was listed as spam.',
message: 'An email has been sent to you. Please check it to activate your account. Check your spam folder in case the email was listed as spam.'
});
})
.catch((err) => {
console.log(err);
return next({topic: 'System Error', title: 'registerEmail - send verification email FAILED', err: err});
})
});
} else {
res.json({
msg: 'Your verification code has expired. Please sign up again.',
message: 'Your verification code has expired. Please sign up again.'
});
}
});
};
/**
* @func verifyEmailVerificationToken
* @desc Step 2. In registration via email. Upon being called by the webapp to verify token, it authenticates the user and emit socket io to refresh webapp to load authenticated user
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} returns token and success status
*/
exports.verifyEmailVerificationToken = async (req, res, next) => {
try {
let token = req.body.verification_token; // retrieve the verification token
const user = await User.findOne({ recoveryURL: token });
if (user) { // existing user verifying a new email address
user.recoveryURL = "";
user.email = user.primary_email;
try {
await user.save();
let userInfoToGenerateToken = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name
};
token = 'JWT ' + generateMobileAppToken(userInfoToGenerateToken);
// send socket.io to refresh userData.user object
req.app.io.of('users-namespace').to(user._id).emit('refresh user status', user._id, { type: 'update user info' });
res.json({success: true,
token: token,
msg: 'Success',
message: 'Success'
})
} catch (err) {
return next({topic: 'System Error', title: 'verifyEmail', err: err});
}
} else { // new user being verified
exports.confirmTempUser(token, async (err, user) => { //return the newly created user doc
if (user) {
let userInfoToGenerateToken = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name
};
token = 'JWT ' + generateMobileAppToken(userInfoToGenerateToken);
await Churches.addUserToRestvoCommunity(user);
res.json({success: true,
token: token, // send back the authentication token
msg: 'Success',
message: 'Success'
});
UserDataController.findNewUserConnections(user._id, false); //find new user connections
} else {
res.json({success: false,
msg: 'This link has already been used to activate an account. You can try to log in using your credentials or create a new account.',
message: 'This link has already been used to activate an account. You can try to log in using your credentials or create a new account.'
})
}
});
}
} catch (err) {
if (err) {
next({topic: 'System Error', title: 'verifyEmail', err: err});
}
}
};
/**
* Transfer a temporary user from the temporary collection to the persistent
* user collection, removing the URL assigned to it.
*
* @func confirmTempUser
* @param {string} url - the randomly generated URL assigned to a unique email
* @param {function} callback - the callback function
*/
exports.confirmTempUser = function(url, callback) {
var TempUser = emailVerificationConfig.tempUserModel,
query = {};
query[emailVerificationConfig.URLFieldName] = url;
TempUser.findOne(query, function(err, tempUserData) {
if (err) {
return callback(err, null);
}
// temp user is found (i.e. user accessed URL before their data expired)
if (tempUserData) {
var userData = JSON.parse(JSON.stringify(tempUserData)), // copy data
User = emailVerificationConfig.persistentUserModel,
user;
delete userData[emailVerificationConfig.URLFieldName];
user = new User(userData);
// save the temporary user to the persistent user collection
user.save(function(err, savedUser) {
if (err) {
return callback(err, null);
}
TempUser.remove(query, function(err) {
if (err) {
return callback(err, null);
}
/*if (emailVerificationConfig.shouldSendConfirmation) {
sendConfirmationEmail(savedUser[emailVerificationConfig.emailFieldName], null);
}*/
return callback(null, user);
});
});
// temp user is not found (i.e. user accessed URL after data expired, or something else...)
} else {
return callback(null, null);
}
});
};
// QA by Calvin Ho on 01/22/2019
/**
* @func recoverPassword
* @desc Sends verification email and then if valid recovery url will update password
* @memberof authentication
* @author Calvin Ho
* @param req
* @param res
* @param next
* @returns {Object} sends verification email object as well as status if password successfully changed
*/
exports.recoverPassword = async (req, res, next) => {
try {
// recovery step 1
if (req.body.email) {
let email = req.body.email.toLowerCase();
let user = await User.findOne({ email: email }, { first_name: 1, email: 1});
if (user) { // if an authenticated email is found
try {
const result = await exports.saveURLSendVerificationEmail(user._id, user.first_name, email);
res.json(result);
} catch (err) {
return next({topic: 'System Error', title: 'recoverPassword - failed to send verification email.', err: err});
}
} else { // try check unauthenticated email
user = await User.findOne({ primary_email: email }, { first_name: 1, primary_email: 1});
if (user) {
res.json({
status: 'unverified email',
msg: 'You may have registered your account using a mobile number. Please try recovering your account using your mobile phone number.',
message: 'You may have registered your account using a mobile number. Please try recovering your account using your mobile phone number.'
});
} else {
tempUser = await TempUser.findOne({ email: email }, { first_name: 1, primary_email: 1});
if (tempUser) {
exports.resendVerificationEmail(email, res, next);
} else {
res.json({
status: 'no account',
msg: 'The email address you entered is not associated with any account. Please try again.',
message: 'The email address you entered is not associated with any account. Please try again.'
});
}
}
}
// recovery step 2
} else if (req.body.recoveryURL) {
const user = await User.findOne({ recoveryURL: req.body.recoveryURL }).select('first_name last_name role email recoveryURL');
if (user) {
let userData = {
_id: user._id,
first_name: user.first_name,
last_name: user.last_name,
role: user.role,
email: user.email,
appName: "com.restvo.app",
enablePushNotification: true
};
if (req.body.loginDeviceType === 'mobile'){
token = generateMobileAppToken(userData);
}
else {
token = generateBrowserToken(userData);
}
let SALT_FACTOR = 5;
bcrypt.genSalt(SALT_FACTOR, function (err, salt) {
if (err) {
return next({topic: 'System Error', title: 'recoverPassword - failed to generate salt.', err: err});
}
bcrypt.hash(req.body.password, salt, null, function (err, hashedPassword) {
if (err) {
return next({topic: 'System Error', title: 'recoverPassword - failed to hash password.', err: err});
}
User.updateOne({ _id: user._id }, {
$unset: { recoveryURL: "" },
$set: { password: hashedPassword }
}, function(err){
if (err) {
return next({topic: 'Mongodb Error', title: 'recoverPassword - failed to update user record.', err: err});
}
res.json({
status: 'success',
token: 'JWT ' + token,
msg: 'Password successfully changed. You can use your new password to log in to Restvo on your mobile devices or the browser.',
message: 'Password successfully changed. You can use your new password to log in to Restvo on your mobile devices or the browser.'
});
})
});
});
} else {
//URL is already used
res.json({
status: 'expired',
msg: 'The password reset URL has already been used or has expired.',
message: 'The password reset URL has already been used or has expired.'
});
}
}
} catch (err) {
next({topic: 'System Error', title: 'recoverPassword', err: err});
}
};
/**
* @func saveURLSendVerificationEmail
* @desc Sends recovery URL in separate email
* @memberof authentication
* @author Calvin Ho
* @param userId
* @param name
* @param email
* @returns {Object} sends status of sending recovery url email
*/
exports.saveURLSendVerificationEmail = function (userId, name, email) {
return new Promise(function(resolve, reject) {
let url = randtoken.generate(emailVerificationConfig.URLLength);
User.updateOne({_id: userId}, {$set: {recoveryURL: url}}, function (err) {
if (err) reject({
msg:'Failed to save the recovery URL to the user record.',
message:'Failed to save the recovery URL to the user record.'
});
let URL = "https://app.restvo.com/recover;url=" + url;
const template = new Email({
views: {
options: {
extension: 'hbs'
}
},
message: { from: '"Restvo" <no-reply@restvo.com>' },
send: true,
transport: transporter,
subjectPrefix: process.env.CRON ? false : `[Dev] `,
juice: true,
juiceResources: {
preserveImportant: true,
webResources: {
relativeTo: path.join(templatesDir, 'recovery-email')
}
}
});
template.send({
template: path.join(templatesDir, 'recovery-email'),
message: { to: email },
locals: {
name: name,
URL: URL
}
})
.then(() => {
console.log("SENT");
resolve({
success: true,
msg: 'Please check your email and follow the instructions to reset your password.',
message: 'Please check your email and follow the instructions to reset your password.'
});
})
.catch((err) => {
console.log(err);
reject({
msg: 'Failed to send the recovery email. Please try again later.',
message: 'Failed to send the recovery email. Please try again later.'
})
})
});
});
};
/**
* @func roleAuthorization
* @desc Checks if role is authorized for action
* @memberof authentication
* @author Calvin Ho
* @param roles
* @returns {Object} Status of authorization check
*/
exports.roleAuthorization = function (roles){
return function(req, res, next){
let user = req.user;
if (!user) {
res.status(422).json({error: 'No req found.'});
}
User.findById(user._id, function(err, foundUser){
if (err){
res.status(422).json({error: 'No user found.'});
return next(err);
}
if (roles.indexOf(foundUser.role) > -1){
return next();
}
res.status(401).json({error: 'You are not authorized to view this content'});
return next('Unauthorized');
});
}
};