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.

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