const User = require("../models/User"); const Message = require("../models/Message"); const generator = require("generate-password"); const bcrypt = require("bcrypt"); const saltRounds = 10; // External Dependancies // offers http-friendly error objects. const boom = require("boom"); var twilio = require("twilio"); // var twilioAccountSid = "ACb743d0f1aa003ecae7548f69796ba26e"; // Your Account SID from www.twilio.com/console // var twilioAuthToken = "2b1d785f485b07cfd5052c023871bd8d"; // Your Auth Token from www.twilio.com/console var twilioAccountSid = "ACd29a8d6cf5c99b57bb7f3ce61cad4511"; // Your Account SID from www.twilio.com/console - armintatank var twilioAuthToken = "7710db3aea89b94027155e6ae774b688"; // Your Auth Token from www.twilio.com/console - armintatank const libphonenumberjs = require("libphonenumber-js"); const emailValidator = require("email-validator"); //function to encrypt password. //used bcrypt module. async function bcryptPassword(password) { encryptedPwd = bcrypt.hash(password, saltRounds); return encryptedPwd; } isUserFormUrlEncoded = (req) => { var isUserFormUrlEncoded = false; console.log("check is user encoe url funtion"); // This iterates through the req headers object. // could not access req.headers.content-type due to the hyphen in the content-type key. // console.log(`${key}: ${value}`); for (const [key, value] of Object.entries(req.headers)) { if (`${key}` === "content-type") { if (`${value}` == "application/x-www-form-urlencoded") { // console.log( "data supplied is with content type," , `${value}`) // set isUserFormUrlEncoded value to true isUserFormUrlEncoded = true; // create user object with form variables . Password is used from the request object directly. user = { username: req.body.username, phone: req.body.phone, address: req.body.address, password: req.body.password, emails: [ { email: req.body.email, }, ], profile: { firstName: req.body.firstName, lastName: req.body.lastName, }, }; return { isUserFormUrlEncoded: isUserFormUrlEncoded, user: user }; } else { return { isUserFormUrlEncoded: false, user: "" }; } } } }; const sendTextToPhone = async (phone, text) => { var accountSid = twilioAccountSid; // Your Account SID from www.twilio.com/console var authToken = twilioAuthToken; // Your Auth Token from www.twilio.com/console var client = new twilio(accountSid, authToken); console.log(accountSid, authToken, client); // Message format to allow app to autopickup sms message and // verify the code. // Be no longer than 140 bytes // Begin with the prefix <#> // Contain a one-time code that the client sends back to your server to complete the verification flow // End with an 11-character hash string that identifies your app - this is hardcoded as FA+9qCX9VSu as a reminder. return await client.messages.create({ body: text, to: phone, // Text this number from: "+16193135693", // From a valid Twilio number }); }; // Function to check if user exists in the system exports.verifyUser = async (req, reply) => { try { var user = new User(req.body); // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isUserFormUrlEncoded(req); if (checkFormEncoding.isUserFormUrlEncoded) { usertobeInserted = checkFormEncoding.user; user.username = usertobeInserted.username; user.firstName = usertobeInserted.firstName; user.lastName = usertobeInserted.lastName; user.phone = usertobeInserted.phone; user.emails = usertobeInserted.emails; } username = user.username; userpass = req.body.password; // check if user exists in the system. If user exists , display message that // username is not available userExists = await User.findOne({ username: username }); if (userExists) { // return user exists message error = { armintatankdata: { error: true, code: 10001, message: "10001 - Username " + userExists.username + " is not available. please use a different username", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.validatePhoneFormat = async (req, reply) => { try { var user = new User(req.body); // check if user supplied phone is of the right format. // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isUserFormUrlEncoded(req); console.log(checkFormEncoding); if (checkFormEncoding.isUserFormUrlEncoded) { usertobeInserted = checkFormEncoding.user; user.username = usertobeInserted.username; user.firstName = usertobeInserted.firstName; user.lastName = usertobeInserted.lastName; user.phone = usertobeInserted.phone; user.emails = usertobeInserted.emails; } if (user) { const phoneNumber = libphonenumberjs.parsePhoneNumber(user.phone); if (phoneNumber) { // access returned collection if (!phoneNumber.isValid()) { error = { armintatankdata: { error: true, code: 10002, message: "10002 - Phone # " + user.phone + " is not a valid phone number", }, }; req.body.regError = error; reply.status(406).send(error); } } } } catch (err) { throw boom.boomify(err); } }; exports.validateEmailFormat = async (req, reply) => { try { var user = new User(req.body); // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isUserFormUrlEncoded(req); if (checkFormEncoding.isUserFormUrlEncoded) { usertobeInserted = checkFormEncoding.user; user.username = usertobeInserted.username; user.firstName = usertobeInserted.firstName; user.lastName = usertobeInserted.lastName; user.phone = usertobeInserted.phone; user.emails = usertobeInserted.emails; } useremail = await user.emails[0].email; // check if user supplied email is of the right format. if (user) { const isValidEmail = emailValidator.validate(useremail.trim()); if (!isValidEmail) { // Return email invalid format message error = { armintatankdata: { error: true, code: 10003, message: "10003 - Email " + user.emails[0].email + " is not a valid email", }, }; req.body.regError = error; reply.send(error); } } } catch (err) { throw boom.boomify(err); } }; // Check if all the required fields are supplied by the user exports.fieldCheck = async (req, reply) => { try { userData = { username: req.body.username, emails: req.body.emails, password: req.body.password, services: { password: {bcrypt: req.body.password} }, phone: req.body.phone, profile: { firstName: req.body.firstName, lastName: req.body.lastName, contactNumber: req.body.phone, country: req.body.country, state: req.body.state, city: req.body.city, address1: req.body.address1, address2: req.body.address2, zip: req.body.zip, notes: req.body.notes, }, }; var user = new User(userData); //password is not at the top level in the collection. password = req.body.password; // capture fields if data is sent via form instead of json encoding checkFormEncoding = isUserFormUrlEncoded(req); if (checkFormEncoding.isUserFormUrlEncoded) { usertobeInserted = checkFormEncoding.user; user.username = usertobeInserted.username; user.firstName = usertobeInserted.firstName; user.lastName = usertobeInserted.lastName; user.phone = usertobeInserted.phone; user.emails = usertobeInserted.emails; password = usertobeInserted.password; } console.log("User to be inserted is ", user.username,password,user.phone,user.profile); // check if all rerquired fields are passed. if ( !( user.username && password && user.phone && // user.profile.firstName && // user.profile.lastName && // user.profile.address1 && user.emails[0].email ) ) { console.log( user.username, password, user.phone, // user.profile.firstName, // user.profile.lastName, user.emails[0].email ); // Required Fields are missing suppliedvalues = user.username + " ," + password + " ," + user.phone + " ," + user.firstName + " ," + user.lastName + " ," + user.emails[0].email; error = { armintatankdata: { error: true, code: 10004, message: "10004 - username, password, phone , firstname , lastname email city country state address1 and zip are required fields. Supplied values are " + suppliedvalues, }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; // This methond supports verifying a users phone. exports.verifyPhone = async (req, reply) => { console.log("-------------------------------------------------"); try { phone = req.body.phone; phoneVerificationCode = req.body.phoneVerificationCode; // check if user exists in the system. If user exists , display message that // username is not available console.log( "this is the phone and verification code", phone, phoneVerificationCode ); userExists = await User.findOne({ phone: phone, phoneVerified: false, phoneVerificationCode: phoneVerificationCode, }); console.log(userExists); if (userExists) { // update the phoneVerified flag to true. const filter = { phone: phone, phoneVerificationCode: phoneVerificationCode, }; const update = { phoneVerified: true }; const doc = await User.findOneAndUpdate(filter, update); updatedUser = await User.findOne({ phone: phone }); if (updatedUser.phoneVerified) { reply.send('{"armintatankdata":{"error":false,"verified": true}}'); } else { error = { armintatankdata: { error: true, code: 10005, message: "10005 - Verification code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10005, message: "10005 - Verification code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.sendPhoneVerificationCode = async (req, reply) => { // Setting a regError in request so a user is not sent a verificaiton code if registration is not successful // checkign if regError property is available in the req body, if it exists , do not send the message // if it does not exisit , it impplies registraiton is succesful , as no pre handlers have set this reqError // during registraiton validations. if (!("regError" in req.body)) { // var user = new User(req.body); // // Handle if the user data is supplied via a url encoded form // // capture fields if data is sent via form instead of json encoding // checkFormEncoding = isUserFormUrlEncoded(req); // if (checkFormEncoding.isUserFormUrlEncoded) { // usertobeInserted = checkFormEncoding.user; // user.username = usertobeInserted.username; // user.firstName = usertobeInserted.firstName; // user.lastName = usertobeInserted.lastName; // user.phone = usertobeInserted.phone; // user.emails = usertobeInserted.emails; // } phone = req.body.phone; var accountSid = twilioAccountSid; // Your Account SID from www.twilio.com/console var authToken = twilioAuthToken; // Your Auth Token from www.twilio.com/console var client = new twilio(accountSid, authToken); var code = Math.floor(100000 + Math.random() * 900000); console.log("resending code", code); // Message format to allow app to autopickup sms message and // verify the code. // Be no longer than 140 bytes // Begin with the prefix <#> // Contain a one-time code that the client sends back to your server to complete the verification flow // End with an 11-character hash string that identifies your app - this is hardcoded as FA+9qCX9VSu as a reminder. var created = await client.messages.create({ // body: "<#> Your verification code is " + code + " - from armintatank " + "FA+9qCX9VSu", body: "Your verification code is " + code + " - from armintatank ", to: phone, // Text this number from: "+16193135693", // From a valid Twilio number }) // ; // if (created.errcode == null) { // try { // const msg = new Message(created); // msg.save(); // const filter = { phone: phone }; // const update = { $set: { phoneVerificationCode: code } }; // var u = await User.updateOne(filter, update); // if (u) { // reply.send({ // armintatankdata: { error: false, message: "Message Successfully sent" }, // }); // } // } catch (err) { // throw err; // } // } else { // reply.send({ armintatankda: { error: true, message: "Message not sent" } }); // } // .then(function (message) { // try { // const msg = new Message(message); // msg.save(); // } catch (err) { // throw err; // } // }) .then(async function () { // While testing ensure that there are no multiple users with the same phone number , // generally we are assumign each user will have a unique phone number. const filter = { phone: phone }; const update = {'$set':{ 'phoneVerificationCode': code }}; await User.updateOne(filter, update); }); } }; exports.forgotPassword = async (req, reply) => { try { var user = new User(req.body); // Handle if the user data is supplied via a url encoded form // capture fields if data is sent via form instead of json encoding checkFormEncoding = isUserFormUrlEncoded(req); if (checkFormEncoding.isUserFormUrlEncoded) { usertobeInserted = checkFormEncoding.user; user.username = usertobeInserted.username; user.firstName = usertobeInserted.firstName; user.lastName = usertobeInserted.lastName; user.phone = usertobeInserted.phone; user.emails = usertobeInserted.emails; } // check if user exists in the system. If user exists , display message that // username is not available userExists = await User.findOne({ phone: user.phone, }); if (userExists) { // Create a code and send it via sms so user can validate it and create a password // var accountSid = twilioAccountSid; // Your Account SID from www.twilio.com/console // var authToken = twilioAuthToken; // Your Auth Token from www.twilio.com/console // var client = new twilio(accountSid, authToken); const code = Math.floor(100000 + Math.random() * 900000); console.log(code, typeof code); // converting the code to a string type as hash generation was throwing error codestr = ""; codestr = code.toString(); hash = await bcryptPassword(codestr); // update the phoneVerified flag to true. const filter = { phone: userExists.phone, }; const update = { $set: { passwordResetCode: code, "services.password.bcrypt": hash, oneTimePasswordSetFlag: true, }, }; console.log(update); const doc = await User.updateOne(filter, update); console.log(doc); updatedUser = await User.findOne({ phone: userExists.phone }); if (updatedUser.oneTimePasswordSetFlag) { req.body.passwordResetCode = code; reply.send('{"armintatankdata":{"error":false,"forgotPassword": true}}'); } else { error = { armintatankdata: { error: true, code: 10007, message: "10007 - Unable to reset password", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10006, message: "10006 - Please check the phone number you entered..", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.sendPasswordResetCode = async (req) => { // Setting a regError in request so a user is not sent a verificaiton code if registration is not successful // checkign if regError property is available in the req body, if it exists , do not send the message // if it does not exisit , it impplies registraiton is succesful , as no pre handlers have set this reqError // during registraiton validations. if (!("regError" in req.body)) { var user = req.body; // // Handle if the user data is supplied via a url encoded form // checkFormEncoding = isUserFormUrlEncoded(req); // if (checkFormEncoding.isUserFormUrlEncoded) { // user = checkFormEncoding.user; // } phone = user.phone; console.log( "user for which password reset code needs to be sent is ", user ); // var accountSid = twilioAccountSid; // Your Account SID from www.twilio.com/console // var authToken = twilioAuthToken; // Your Auth Token from www.twilio.com/console var client = new twilio(twilioAccountSid, twilioAuthToken); // var code = Math.floor(100000 + Math.random() * 900000); code = user.passwordResetCode.toString(); // Message format to allow app to autopickup sms message and // verify the code. // Be no longer than 140 bytes // Begin with the prefix <#> // Contain a one-time code that the client sends back to your server to complete the verification flow // End with an 11-character hash string that identifies your app - this is hardcoded as FA+9qCX9VSu as a reminder. var created = client.messages .create({ // body: "<#> Your verification code from armintatank is " + code, body: "Your verification code is " + code + " - from armintatank ", to: phone, // Text this number from: "+16193135693", // From a valid Twilio number }) .then(function (message) { try { const msg = new Message(message); msg.save(); } catch (err) { throw err; } }) // .then(async function () { // // While testing ensure that there are no multiple users with the same phone number , // // generally we are assumign each user will have a unique phone number . Only updated while phone verficiation code is sent // const filter = { phone: phone }; // const update = { phoneVerificationCode: code }; // await User.findOneAndUpdate(filter, update); // }); } }; exports.resetPassword = async (req, reply) => { try { console.log(" in reset Password method"); var user = req.body; // Handle if the user data is supplied via a url encoded form checkFormEncoding = isUserFormUrlEncoded(req); if (checkFormEncoding.isUserFormUrlEncoded) { user = checkFormEncoding.user; } phone = user.phone; resetPasswordCode = user.resetPasswordCode; newPassword = user.newPassword; hash = await bcryptPassword(newPassword); console.log(user); // check if user exists in the system. If user exists , display message that // username is not available userExists = await User.findOne({ phone: phone, passwordResetCode: resetPasswordCode, }); console.log(userExists); if (userExists) { // update the phoneVerified flag to true. const filter = { phone: phone, passwordResetCode: resetPasswordCode, }; console.log(filter); const update = { $set: { "services.password.bcrypt": hash, oneTimePasswordSetFlag: false, }, }; console.log(update); const doc = await User.updateOne(filter, update); // updatedUser = await User.findOne({ phone: phone }); if (doc) { reply.send('{"armintatankdata":{"error":false,"passwordReset": true}}'); } else { error = { armintatankdata: { error: true, code: 10007, message: "10007 - Password Reset code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10008, message: "10008 - Either Phone or Temporary Code is Invalid.", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.resetPasswordFromAdmin = async (req, reply) => { try { var { userId } = req.body; // check if user exists in the system. If user exists , display message that // username is not available userExists = await User.findById(userId); if (userExists) { const newPassword = generator.generate({ length: 10, numbers: true, }); const hash = await bcryptPassword(newPassword); userExists.services.password.bcrypt = hash; userExists.oneTimePasswordSetFlag = true; const doc = await userExists.save(); await sendTextToPhone( userExists.phone, `Your new password is ${newPassword} - from armintatank ` ); if (doc) { reply.send('{"armintatankdata":{"error":false,"passwordReset": true}}'); } else { error = { armintatankdata: { error: true, code: 10007, message: "10007 - Can't set new password", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10008, message: "10008 - The user is not found", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.resetPhoneVerificationCode = async (req, reply) => { try { console.log(" in resetPhoneVerificationCode method"); var user = req.body; // Handle if the user data is supplied via a url encoded form checkFormEncoding = isUserFormUrlEncoded(req); if (checkFormEncoding.isUserFormUrlEncoded) { user = checkFormEncoding.user; } phone = user.phone; userExists = await User.findOne({ phone: phone, }); if (userExists) { // update the phoneVerified flag to true. const filter = { phone: phone, passwordResetCode: resetPasswordCode, }; console.log(filter); const update = { $set: { "services.password.bcrypt": hash, oneTimePasswordSetFlag: false, }, }; console.log(update); const doc = await User.updateOne(filter, update); // updatedUser = await User.findOne({ phone: phone }); if (doc) { reply.send('{"armintatankdata":{"error":false,"passwordReset": true}}'); } else { error = { armintatankdata: { error: true, code: 10007, message: "10007 - Password Reset code entered cannot be validated.", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10008, message: "10008 - Either Phone or Temporary Code is Invalid.", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } }; exports.sendMessageNotification = async (req, reply) => { try { var { userId } = req.body; userExists = await User.findById(userId); if (userExists) { const doc = await sendTextToPhone( userExists.phone, `${req.body.message} ` ); if (doc.errcode == null) { return reply.send({ armintatankdata: { error: false, message: "Message Successfully sent" }, }); } else { error = { armintatankdata: { error: true, code: 10007, message: "10007 - Can't set new password", }, }; req.body.regError = error; reply.send(error); } } else { error = { armintatankdata: { error: true, code: 10008, message: "10008 - The user is not found", }, }; req.body.regError = error; reply.send(error); } } catch (err) { throw boom.boomify(err); } };