From da999107f31d4f42035be318ae9684aa09e0150c Mon Sep 17 00:00:00 2001 From: Reginald Bondoc Date: Sat, 10 Dec 2022 21:10:05 +0100 Subject: [PATCH 1/2] Add local SMTP server for development --- .env.example | 1 + backend/src/config/index.ts | 70 +++++++++++++------------ backend/src/helpers/nodemailer.ts | 87 ++++++++++++++++++------------- docker-compose.dev.yml | 15 +++++- docs/contributing/FAQ.mdx | 10 ++-- docs/contributing/developing.mdx | 23 ++++++-- 6 files changed, 127 insertions(+), 79 deletions(-) diff --git a/.env.example b/.env.example index 2025622dc..3871841c0 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,7 @@ SITE_URL=http://localhost:8080 # Required to send emails # By default, SMTP_HOST is set to smtp.gmail.com SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 SMTP_NAME=Team SMTP_USERNAME=team@infisical.com SMTP_PASSWORD= diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index fd8f9c55b..5575ceb44 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -13,12 +13,15 @@ const NODE_ENV = process.env.NODE_ENV! || 'production'; const OAUTH_CLIENT_SECRET_HEROKU = process.env.OAUTH_CLIENT_SECRET_HEROKU!; const OAUTH_TOKEN_URL_HEROKU = process.env.OAUTH_TOKEN_URL_HEROKU!; const POSTHOG_HOST = process.env.POSTHOG_HOST! || 'https://app.posthog.com'; -const POSTHOG_PROJECT_API_KEY = process.env.POSTHOG_PROJECT_API_KEY! || 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; +const POSTHOG_PROJECT_API_KEY = + process.env.POSTHOG_PROJECT_API_KEY! || + 'phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE'; const PRIVATE_KEY = process.env.PRIVATE_KEY!; const PUBLIC_KEY = process.env.PUBLIC_KEY!; const SENTRY_DSN = process.env.SENTRY_DSN!; const SITE_URL = process.env.SITE_URL!; const SMTP_HOST = process.env.SMTP_HOST! || 'smtp.gmail.com'; +const SMTP_PORT = process.env.SMTP_PORT! || 587; const SMTP_NAME = process.env.SMTP_NAME!; const SMTP_USERNAME = process.env.SMTP_USERNAME!; const SMTP_PASSWORD = process.env.SMTP_PASSWORD!; @@ -28,38 +31,39 @@ const STRIPE_PRODUCT_STARTER = process.env.STRIPE_PRODUCT_STARTER!; const STRIPE_PUBLISHABLE_KEY = process.env.STRIPE_PUBLISHABLE_KEY!; const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY!; const STRIPE_WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!; -const TELEMETRY_ENABLED = (process.env.TELEMETRY_ENABLED! !== 'false') && true; +const TELEMETRY_ENABLED = process.env.TELEMETRY_ENABLED! !== 'false' && true; export { - PORT, - EMAIL_TOKEN_LIFETIME, - ENCRYPTION_KEY, - JWT_AUTH_LIFETIME, - JWT_AUTH_SECRET, - JWT_REFRESH_LIFETIME, - JWT_REFRESH_SECRET, - JWT_SERVICE_SECRET, - JWT_SIGNUP_LIFETIME, - JWT_SIGNUP_SECRET, - MONGO_URL, - NODE_ENV, - OAUTH_CLIENT_SECRET_HEROKU, - OAUTH_TOKEN_URL_HEROKU, - POSTHOG_HOST, - POSTHOG_PROJECT_API_KEY, - PRIVATE_KEY, - PUBLIC_KEY, - SENTRY_DSN, - SITE_URL, - SMTP_HOST, - SMTP_NAME, - SMTP_USERNAME, - SMTP_PASSWORD, - STRIPE_PRODUCT_CARD_AUTH, - STRIPE_PRODUCT_PRO, - STRIPE_PRODUCT_STARTER, - STRIPE_PUBLISHABLE_KEY, - STRIPE_SECRET_KEY, - STRIPE_WEBHOOK_SECRET, - TELEMETRY_ENABLED + PORT, + EMAIL_TOKEN_LIFETIME, + ENCRYPTION_KEY, + JWT_AUTH_LIFETIME, + JWT_AUTH_SECRET, + JWT_REFRESH_LIFETIME, + JWT_REFRESH_SECRET, + JWT_SERVICE_SECRET, + JWT_SIGNUP_LIFETIME, + JWT_SIGNUP_SECRET, + MONGO_URL, + NODE_ENV, + OAUTH_CLIENT_SECRET_HEROKU, + OAUTH_TOKEN_URL_HEROKU, + POSTHOG_HOST, + POSTHOG_PROJECT_API_KEY, + PRIVATE_KEY, + PUBLIC_KEY, + SENTRY_DSN, + SITE_URL, + SMTP_HOST, + SMTP_PORT, + SMTP_NAME, + SMTP_USERNAME, + SMTP_PASSWORD, + STRIPE_PRODUCT_CARD_AUTH, + STRIPE_PRODUCT_PRO, + STRIPE_PRODUCT_STARTER, + STRIPE_PUBLISHABLE_KEY, + STRIPE_SECRET_KEY, + STRIPE_WEBHOOK_SECRET, + TELEMETRY_ENABLED }; diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 7e70f6ac1..93633e4fa 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -1,22 +1,37 @@ +/* eslint-disable no-console */ import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; import nodemailer from 'nodemailer'; -import { SMTP_HOST, SMTP_NAME, SMTP_USERNAME, SMTP_PASSWORD } from '../config'; +import { + SMTP_HOST, + SMTP_PORT, + SMTP_NAME, + SMTP_USERNAME, + SMTP_PASSWORD +} from '../config'; +import SMTPConnection from 'nodemailer/lib/smtp-connection'; +const mailOpts: SMTPConnection.Options = { + host: SMTP_HOST, + port: SMTP_PORT as number +}; +if (SMTP_USERNAME && SMTP_PASSWORD) { + mailOpts.auth = { + user: SMTP_USERNAME, + pass: SMTP_PASSWORD + }; +} // create nodemailer transporter -const transporter = nodemailer.createTransport({ - host: SMTP_HOST, - port: 587, - auth: { - user: SMTP_USERNAME, - pass: SMTP_PASSWORD - } -}); +const transporter = nodemailer.createTransport(mailOpts); transporter - .verify() - .then(() => console.log('SMTP - Successfully connected')) - .catch((err) => console.log('SMTP - Failed to connect')); + .verify() + .then(() => console.log('SMTP - Successfully connected')) + .catch((err) => + console.log( + `SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}` + ) + ); /** * @param {Object} obj @@ -26,33 +41,33 @@ transporter * @param {Object} obj.substitutions - object containing template substitutions */ const sendMail = async ({ - template, - subjectLine, - recipients, - substitutions + template, + subjectLine, + recipients, + substitutions }: { - template: string; - subjectLine: string; - recipients: string[]; - substitutions: any; + template: string; + subjectLine: string; + recipients: string[]; + substitutions: any; }) => { - try { - const html = fs.readFileSync( - path.resolve(__dirname, '../templates/' + template), - 'utf8' - ); - const temp = handlebars.compile(html); - const htmlToSend = temp(substitutions); + try { + const html = fs.readFileSync( + path.resolve(__dirname, '../templates/' + template), + 'utf8' + ); + const temp = handlebars.compile(html); + const htmlToSend = temp(substitutions); - await transporter.sendMail({ - from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`, - to: recipients.join(', '), - subject: subjectLine, - html: htmlToSend - }); - } catch (err) { - console.error(err); - } + await transporter.sendMail({ + from: `"${SMTP_NAME}" <${SMTP_USERNAME}>`, + to: recipients.join(', '), + subject: subjectLine, + html: htmlToSend + }); + } catch (err) { + console.error(err); + } }; export { sendMail }; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 949a96c7d..623462d5b 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -20,6 +20,7 @@ services: restart: unless-stopped depends_on: - mongo + - smtp-server build: context: ./backend dockerfile: Dockerfile @@ -33,7 +34,7 @@ services: - NODE_ENV=development networks: - infisical-dev - + frontend: container_name: infisical-dev-frontend restart: unless-stopped @@ -84,6 +85,18 @@ services: networks: - infisical-dev + smtp-server: + container_name: infisical-dev-smtp-server + image: mailhog/mailhog + restart: always + logging: + driver: 'none' # disable saving logs + ports: + - 1025:1025 # SMTP server + - 8025:8025 # Web UI + networks: + - infisical-dev + volumes: mongo-data: driver: local diff --git a/docs/contributing/FAQ.mdx b/docs/contributing/FAQ.mdx index be65f5ca7..9f4f7aee9 100644 --- a/docs/contributing/FAQ.mdx +++ b/docs/contributing/FAQ.mdx @@ -1,11 +1,13 @@ --- -title: "Frequently Asked Questions" -description: "Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g)." +title: 'Frequently Asked Questions' +description: 'Have any questions? [Join our Slack community](https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g).' --- ## Problem with SMTP -You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems. +If you opt for actual SMTP server (not the local MailHog), you have to have the right environment variables set. + +You can normally populate `SMTP_USERNAME` and `SMTP_PASSWORD` with your usual login and password (you could also create a 'burner' email). Sometimes, there still are problems. You can go to your Gmail account settings > security and enable “less secure apps”. This would allow Infisical to use your Gmail to send emails. @@ -13,4 +15,4 @@ If it still doesn't work, [this](https://stackoverflow.com/questions/72547853/un ## `MONGO_URL` issues -Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`. \ No newline at end of file +Your `MONGO_URL` should be something like `mongodb://root:example@mongo:27017/?authSource=admin`. If you want to change it (not recommended), you should make sure that you keep this URL in line with `MONGO_USERNAME=root` and `MONGO_PASSWORD=example`. diff --git a/docs/contributing/developing.mdx b/docs/contributing/developing.mdx index 64f75533f..3be1a1b6b 100644 --- a/docs/contributing/developing.mdx +++ b/docs/contributing/developing.mdx @@ -1,6 +1,6 @@ --- -title: "Developing" -description: "This guide will help you set up and run Infisical in development mode." +title: 'Developing' +description: 'This guide will help you set up and run Infisical in development mode.' --- ## Clone the repo @@ -48,14 +48,27 @@ Take into account that if you use your own `MONGO_USERNAME` and `MONGO_PASSWORD` ![image](https://user-images.githubusercontent.com/118568289/206792653-ba3211d1-1071-43f2-93a7-8b408bbd9e0e.png) -These variables are needed to use the Mail SMTP service that takes care of sending the verification code when you -register in the app. +If you want to receive actual emails (e.g. you want to test how the email message will look like), take note of the following. For the `SMTP_USERNAME` variable, you will need an email with 2-steps-verification. For the `SMTP_PASSWORD` variable, you will need to [generate an app password](https://support.google.com/mail/answer/185833?hl=en) with the email you used in the `SMTP_USERNAME` variable. -With this environment variables, you will be ready to run the docker-compose. +Otherwise, a local SMTP server (MailHog) is available for testing purposes. Set the following values to use this: + +``` +SMTP_HOST=smtp-server +SMTP_PORT=1025 +SMTP_NAME= +SMTP_USERNAME=team@infisical.com +SMTP_PASSWORD= +``` + +Make sure to leave the `SMTP_PASSWORD` blank so the backend will be able to connect to MailHog + +You can browse `http://localhost:8025/` to browse email messages sent by the backend. + +With these environment variables, you will be ready to run the docker-compose. ## Docker for development From 6b546034f4ef0790df0dc4ac54c6b42a412bc5d1 Mon Sep 17 00:00:00 2001 From: Reginald Bondoc Date: Sat, 10 Dec 2022 22:05:19 +0100 Subject: [PATCH 2/2] Use Sentry instead of console logs --- backend/src/helpers/nodemailer.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/src/helpers/nodemailer.ts b/backend/src/helpers/nodemailer.ts index 93633e4fa..1c92af93f 100644 --- a/backend/src/helpers/nodemailer.ts +++ b/backend/src/helpers/nodemailer.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import fs from 'fs'; import path from 'path'; import handlebars from 'handlebars'; @@ -11,6 +10,7 @@ import { SMTP_PASSWORD } from '../config'; import SMTPConnection from 'nodemailer/lib/smtp-connection'; +import * as Sentry from '@sentry/node'; const mailOpts: SMTPConnection.Options = { host: SMTP_HOST, @@ -26,12 +26,16 @@ if (SMTP_USERNAME && SMTP_PASSWORD) { const transporter = nodemailer.createTransport(mailOpts); transporter .verify() - .then(() => console.log('SMTP - Successfully connected')) - .catch((err) => - console.log( + .then(() => { + Sentry.setUser(null); + Sentry.captureMessage('SMTP - Successfully connected'); + }) + .catch((err) => { + Sentry.setUser(null); + Sentry.captureException( `SMTP - Failed to connect to ${SMTP_HOST}:${SMTP_PORT} \n\t${err}` - ) - ); + ); + }); /** * @param {Object} obj @@ -66,7 +70,8 @@ const sendMail = async ({ html: htmlToSend }); } catch (err) { - console.error(err); + Sentry.setUser(null); + Sentry.captureException(err); } };