diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b90444 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +./node_modules diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..374ff89 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM node:10.6-alpine +RUN mkdir /app +RUN chown node:node /app +WORKDIR /app +run apk add --no-cache python2 g++ make git curl +RUN yarn global add Haraka express +USER node +RUN haraka -i /app +RUN echo webmailer >> /app/config/plugins +RUN echo "listen=[::0]:8080" >> /app/config/http.ini +RUN echo "listen=[::0]:2525" >> /app/config/smtp.ini +COPY testing/testTemplate.js /app/plugins/webmailer/templates/testTemplate.js +CMD haraka -c /app diff --git a/index.js b/index.js index 71ccf97..372cdbb 100644 --- a/index.js +++ b/index.js @@ -3,58 +3,67 @@ let fs = require('fs'); let bodyParser = require('body-parser'); let outbound; -let getTemplates = require('./templater'); +let getTemplates = require('./templates'); let errorSender = require('./errorSender'); exports.register = function() { outbound = this.haraka_require('./outbound'); - let plugin = this; - this.register_hook('hook_init_http', function(next, server) { + this.register_hook('init_http', 'initExpress'); +} + +exports.initExpress = function(next, server) { server.http.app.use('/webSend', setupRoutes(server.http.express.Router())); server.http.app.get('/test', (req, res) => res.send("HELLO!")); - plugin.loginfo(server.http.app._router.stack); - next(); - }); -} + next(); +}; function setupRoutes(app) { app.use(bodyParser.json()); app.use(errorSender); app.post('/template', function(req, res) { - if(!req.body) { - return res.sendError(400, "No body provided."); - } + try { + if(!req.body) { + return res.sendError(400, "No body provided."); + } - let {to, from, template, headers} = req.body; - if(!template) { - return res.sendError(400, "No template name provided."); - } - return getTemplates.then(function(templates) { - let emailText = `From: ${from}\nTo: ${to}`; - if(headers) { - headers.forEach(function(header) { - emailText += `\n${header}`; + let {to, from, template, headers} = req.body; + if(!template) { + console.error("Template not found in: ", req.body); + return res.sendError(400, "No template name provided."); + } + return getTemplates().then(function(templates) { + if(!templates[template]) { + return res.sendError(400, `Template not found in ${JSON.stringify(templates)}`); + } + let emailText = `From: ${from}\nTo: ${to}`; + if(headers) { + headers.forEach(function(header) { + emailText += `\n${header}`; + }); + } + try { + let {subject, message} = templates[template](req.body); + emailText += `\nSubject: ${subject}\n\n${message}`; + } catch (err) { + console.error("Error building email from template: ", err); + res.sendError(err.httpStatus || 400, err.userMessage || "Error parsing template"); + } + outbound.send_email(from, to, emailText, function(code, message) { + console.log(`Queue mail result code: ${code} msg: ${message}`); + res.type("json").send(JSON.stringify({ + code, + message + })); }); - } - try { - let [subject, body] = `\n\n${templates[template](req.body)}\n`; - emailText += `\nSubject: ${subject}\n\n${body}`; - } catch (err) { - console.error("Error building email from template: ", err); - res.sendError(err.httpStatus || 400, err.userMessage || "Error parsing template"); - } - - outbound.send_email(from, to, emailText, function(code, message) { - console.log(`Queue mail result code: ${code} msg: ${msg}`); - res.type("json").send(JSON.stringigy({ - code, - message - })); + }).catch(function(err) { + console.error("Error loading templates:", err); + res.sendError(500, "Server Error"); + throw(e); }); - }).catch(function(err) { - console.error("Error loading templates:", err); + } catch(e) { res.sendError(500, "Server Error"); - }); + throw(e); + } }); return app; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..bf5fbd2 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,164 @@ +{ + "name": "haraka-plugin-webmailer", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + } + } +} diff --git a/templater.js b/templater.js index a37c348..bfce422 100644 --- a/templater.js +++ b/templater.js @@ -2,10 +2,10 @@ let escape = require("lodash.escape"); module.exports = function(strings) { //first element is the strings, don't need it - let args = arguments.slice(1); + let args = Array.prototype.slice.call(arguments,1); //first element will be the initial value - return strings.slice(1).reduce(function(result, string, i) { - return result + escape(args[i]) + string; - }, strings[0]); + return strings.reduce(function(result, string, i) { + return result + escape(args[i-1]) + string; + }); }; diff --git a/templates/index.js b/templates/index.js new file mode 100644 index 0000000..927b97d --- /dev/null +++ b/templates/index.js @@ -0,0 +1,11 @@ +let fs = require('fs'); +let templateFiles = fs.readdirSync(__dirname, "utf8").filter( + x => x[0] !== "." && x !== "index.js" +); + +let templates = templateFiles.reduce((obj, file) => { obj[file.split(".")[0]] = require(`./${file}`); return obj;}, {}); + +module.exports = function() { + //I might want to make this asynchronous very soon + return Promise.resolve(templates); +}; diff --git a/testing/testTemplate.js b/testing/testTemplate.js new file mode 100644 index 0000000..bc155db --- /dev/null +++ b/testing/testTemplate.js @@ -0,0 +1,16 @@ +let templater = require('../templater'); + +module.exports = function({params}) { + let message = templater`Hello ${params.name}, + +I am writing to inform you that I am testing a templated email. I have tested it ${params.times} times. + +${params.valediction}, + +${params.senderName} +`; + + let subject = "New message from webmailer program!"; + return {subject, message}; +} +