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.

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