Personal blog written from scratch using Node.js, Bootstrap, and MySQL. https://jrtechs.net
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
5.4 KiB

  1. /**
  2. * File which sends emails to me from
  3. * a captcha protected form on the contact
  4. * page.
  5. *
  6. * @author Jeffery Russell 8-19-18
  7. */
  8. //used for file IO
  9. const utils = require('../utils/utils.js');
  10. //used for static files
  11. const includes = require('../includes/includes');
  12. //for parsing post data
  13. const qs = require('querystring');
  14. //cleans form submission
  15. const sanitizer = require('sanitizer');
  16. //used to send post data for the captcha
  17. const Request = require('request');
  18. //sends the email using a throw away gmail account
  19. const nodemailer = require("nodemailer");
  20. //agent for sending the email
  21. const smtpTransport = require('nodemailer-smtp-transport');
  22. //captcha secret
  23. const CAPTCHA_SECRET = utils.getFileLine("../captcha_secret");
  24. //password to gmail account
  25. const EMAIL_PASSWORD = utils.getFileLine("../email_password");
  26. /**
  27. * Verifies if the captcha response recieved from the post data was
  28. * valid, or are bots trying to get around the captcha
  29. *
  30. * @param data captcha data from post request
  31. * @returns {Promise} resolves whether the captcha is valid
  32. */
  33. const verifyCapcha = function(data)
  34. {
  35. const recaptcha_url = "https://www.google.com/recaptcha/api/siteverify?" +
  36. "secret=" + CAPTCHA_SECRET + "&" +
  37. "response=" + data;
  38. return sync = new Promise(function(resolve, reject)
  39. {
  40. Request(recaptcha_url,
  41. function (error, response, body)
  42. {
  43. if (!error && response.statusCode == 200)
  44. {
  45. const googleAnswer = JSON.parse(body);
  46. if(googleAnswer.success == true)
  47. {
  48. resolve(true);
  49. }
  50. else
  51. {
  52. resolve(false);
  53. }
  54. }
  55. else
  56. {
  57. resolve(false);
  58. }
  59. }
  60. );
  61. });
  62. };
  63. /**
  64. * Sends a email to my personal emaail address using a throw away
  65. * gmail account
  66. *
  67. * @param name from contact form
  68. * @param email from contact form
  69. * @param message from contact form
  70. */
  71. const sendEmail = function(name, email, message)
  72. {
  73. const transporter = nodemailer.createTransport(smtpTransport({
  74. service: 'gmail',
  75. host: 'smtp.gmail.com',
  76. auth: {
  77. user: 'jrtechswebsite@gmail.com',
  78. pass: EMAIL_PASSWORD
  79. }
  80. }));
  81. const mailOptions =
  82. {
  83. to: "jeffery@jrtechs.net", // list of receivers
  84. subject: "Jrtechs.net form submission", // Subject line
  85. text: message, // plaintext body
  86. html: message
  87. };
  88. // send mail with defined transport object
  89. transporter.sendMail(mailOptions, function(error, response)
  90. {
  91. if(error)
  92. {
  93. console.log(error);
  94. }
  95. else
  96. {
  97. console.log("Message sent: " + response);
  98. }
  99. transporter.close(); // shut down the connection pool, no more messages
  100. });
  101. };
  102. /**
  103. * If there was post data on the contact page, it processes it to see
  104. * if it was a valid captcha request and sends an email. If no post data was sent,
  105. * the normal form is displayed
  106. *
  107. * @param request -- main express request
  108. * @returns {Promise} renders the html of the contact widget
  109. */
  110. const processContactPage = function(request)
  111. {
  112. return new Promise(function(resolve, reject)
  113. {
  114. utils.getPostData(request).then(function(postData)
  115. {
  116. const data = qs.parse(postData);
  117. if(data.name &&
  118. data.email &&
  119. data["g-recaptcha-response"] &&
  120. data.message)
  121. {
  122. verifyCapcha(sanitizer.sanitize(data["g-recaptcha-response"]))
  123. .then(function(valid)
  124. {
  125. if(valid)
  126. {
  127. resolve(utils.include("includes/html/messageSent.html"));
  128. sendEmail(data.name, data.email, data.message);
  129. }
  130. else
  131. {
  132. resolve(utils.include("includes/html/invalidCaptcha.html"));
  133. }
  134. });
  135. }
  136. else
  137. {
  138. resolve(utils.include("includes/html/contact.html"));
  139. }
  140. }).catch(function(err)
  141. {
  142. reject(err);
  143. })
  144. });
  145. };
  146. module.exports =
  147. {
  148. /**
  149. * Displays the contact page along with the header, sidebar, and footer.
  150. * This uses the admin header because it doesn't need any minified css
  151. * which has been purged of some css classes which are not used in any
  152. * of the blog posts.
  153. *
  154. * @param request -- main express request
  155. * @param result -- renders the html of the contact page
  156. */
  157. main: function(request, result)
  158. {
  159. result.writeHead(200, {'Content-Type': 'text/html'});
  160. Promise.all([includes.printAdminHeader(),
  161. processContactPage(request),
  162. require("../sidebar/sidebar.js").main(),
  163. includes.printFooter()]).then(function(content)
  164. {
  165. result.write(content.join(''));
  166. result.end();
  167. }).catch(function(err)
  168. {
  169. console.log(err);
  170. });
  171. }
  172. };