diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7bcdfb5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM node:18-alpine + +WORKDIR /app + +COPY package*.json ./ + +# Use --omit=dev instead of deprecated --production flag +RUN npm install --omit=dev + +COPY . . + +EXPOSE 7000 + +RUN addgroup -S contactform && adduser -S contactform -G contactform +RUN chown -R contactform:contactform /app +USER contactform + +CMD ["node", "ghost-contact-svc.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2a3d7fa --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,48 @@ +services: + contact-form: + build: . + container_name: contact-form + restart: unless-stopped + + # Only expose to localhost - nginx will proxy + ports: + - "127.0.0.1:7000:7000" + + env_file: + - .env + + # Security hardening + security_opt: + - no-new-privileges:true + read_only: true + tmpfs: + - /tmp + + networks: + contact-form-net: + mailcow-network: + ipv4_address: 172.22.1.243 + + # Resource limits + deploy: + resources: + limits: + cpus: '0.25' + memory: 128M + reservations: + memory: 64M + + healthcheck: + test: ["CMD", "wget", "--quiet", "--tries=1", + "--spider", "http://localhost:7000/v1/demo"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + +networks: + contact-form-net: + driver: bridge + mailcow-network: + external: true + name: mailcowdockerized_mailcow-network diff --git a/ghost-contact-svc.js b/ghost-contact-svc.js index 3f50dc2..f5d2057 100644 --- a/ghost-contact-svc.js +++ b/ghost-contact-svc.js @@ -8,7 +8,7 @@ var smtpTrans = require('nodemailer-smtp-transport'); var validator = require("email-validator"); var sanitize = require('sanitize-html'); -var smtp = { "auth": {}, "port": 465, "secure": true, "tls": {"rejectUnauthorized": false}, "debug": false}; +var smtp = { "auth": {}, "port": 587, "secure": false, "tls": {"rejectUnauthorized": false}, "debug": false}; smtp.host = process.env.SMTP_HOST; smtp.auth.user = process.env.SMTP_USER; smtp.auth.pass = process.env.SMTP_PASS; @@ -18,9 +18,9 @@ var app = express(); app.disable('x-powered-by'); app.use(bodyParser.urlencoded({limit: '1mb', extended: false})); app.use(bodyParser.json({limit: '1mb'})); -app.use(cors({origin: process.env.ALLOW_ORIGIN, - allowedHeaders: ['Content-Type', 'application/json; charset=utf-8', 'text/html; charset=utf-8']})); - +// CORS handled by nginx +// app.use(cors({origin: process.env.ALLOW_ORIGIN, +// allowedHeaders: ['Content-Type', 'application/json; charset=utf-8', 'text/html; charset=utf-8']})); app.use('/v1/assets', express.static(__dirname + '/assets')); app.post('/v1/contact', function(req, res) { @@ -37,22 +37,16 @@ app.listen(process.env.PORT || 7000, function(){ }); function sendEmail(data, res){ - var email = { "from": process.env.EMAIL_FROM, "to": process.env.EMAIL_TO}; - email.subject = 'MY BLOG - ' + (data.subject && data.subject.toUpperCase()); + var email = { + "from": process.env.EMAIL_FROM, + "to": process.env.EMAIL_TO, + "replyTo": `${data.name} <${data.email}>`, + "subject": `tobiaseigen.org contact form message from ${data.name}`, + "text": data.message + }; - const output = ` -

Hello,

-

You got a new contact request.

-

Contact Details

- -

Message:

-

${data.message}

- ` - email.html = sanitize(output, { - allowedTags: sanitize.defaults.allowedTags.concat([ 'img' ]) - }); transporter.sendMail(email, function(error, info){ - if(error) return res.json({"sendEmail": "failed"}); + if(error) { console.log("SMTP ERROR:", error); return res.json({"sendEmail": "failed"}); } res.json({"sendEmail": "ok"}); }); -}; \ No newline at end of file +};