fix(server/email): emails configuration is secure by default but can be overridden (#5417)

This commit is contained in:
Iain Sproat
2025-09-11 17:06:36 +01:00
committed by GitHub
parent 0afdaf5fc0
commit a0f23dcefe
7 changed files with 63 additions and 16 deletions
+1
View File
@@ -94,6 +94,7 @@ services:
S3_REGION: '' # optional, defaults to 'us-east-1'
FILE_SIZE_LIMIT_MB: 1000
EMAIL_FROM: 'no-reply@example.org'
EMAIL_SECURE: 'false' # If connecting to maildev server, do not use TLS
FRONTEND_ORIGIN: 'http://127.0.0.1'
ONBOARDING_STREAM_URL: 'https://latest.speckle.systems/projects/843d07eb10'
+1
View File
@@ -75,6 +75,7 @@ EMAIL=true
EMAIL_HOST="127.0.0.1"
EMAIL_FROM="no-reply@example.org"
EMAIL_PORT="1025"
EMAIL_SECURE="false"
# EMAIL_HOST="-> FILL IN <-"
# EMAIL_PORT="-> FILL IN <-"
@@ -1,6 +1,14 @@
import { emailLogger as logger } from '@/observability/logging'
import { MisconfiguredEnvironmentError } from '@/modules/shared/errors'
import { isEmailEnabled, isTestEnv } from '@/modules/shared/helpers/envHelper'
import {
getEmailHost,
getEmailPassword,
getEmailPort,
getEmailUsername,
isEmailEnabled,
isSecureEmailEnabled,
isTestEnv
} from '@/modules/shared/helpers/envHelper'
import type { Transporter } from 'nodemailer'
import { createTransport } from 'nodemailer'
@@ -11,18 +19,23 @@ const createJsonEchoTransporter = () => createTransport({ jsonTransport: true })
const initSmtpTransporter = async () => {
try {
const smtpTransporter = createTransport({
host: process.env.EMAIL_HOST || '127.0.0.1',
port: parseInt(process.env.EMAIL_PORT || '587'),
secure: process.env.EMAIL_SECURE === 'true',
host: getEmailHost(),
port: getEmailPort(),
secure: isSecureEmailEnabled(),
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD
user: getEmailUsername(),
pass: getEmailPassword()
},
pool: true,
maxConnections: 20,
maxMessages: Infinity
})
await smtpTransporter.verify()
const transporterVerified = await smtpTransporter.verify()
if (!transporterVerified) {
logger.error(
'📧 Email provider is likely misconfigured as validation failed, check config variables'
)
}
return smtpTransporter
} catch (e) {
logger.error(e, '📧 Email provider is misconfigured, check config variables.')
@@ -7,14 +7,16 @@ import { ensureError } from '@speckle/shared'
export function getStringFromEnv(
envVarKey: string,
options?: Partial<{
default?: string
/**
* If set to true, wont throw if the env var is not set
*/
unsafe: boolean
unsafe?: boolean
}>
): string {
const envVar = process.env[envVarKey]
if (!envVar) {
if (options?.default !== undefined) return options.default
if (options?.unsafe) return ''
throw new MisconfiguredEnvironmentError(`${envVarKey} env var not configured`)
}
@@ -334,10 +336,6 @@ export function getOnboardingStreamCacheBustNumber() {
return parseInt(val) || 1
}
export function getEmailFromAddress() {
return getStringFromEnv('EMAIL_FROM')
}
export function getMaximumProjectModelsPerPage() {
return getIntFromEnv('MAX_PROJECT_MODELS_PER_PAGE', '500')
}
@@ -373,6 +371,30 @@ export function isEmailEnabled() {
return getBooleanFromEnv('EMAIL')
}
export function getEmailFromAddress() {
return getStringFromEnv('EMAIL_FROM')
}
export function getEmailHost() {
return getStringFromEnv('EMAIL_HOST', { default: '127.0.0.1' })
}
export function getEmailPort() {
return getIntFromEnv('EMAIL_PORT', '587')
}
export function isSecureEmailEnabled() {
return getBooleanFromEnv('EMAIL_SECURE', true) // default to secure
}
export function getEmailUsername() {
return getStringFromEnv('EMAIL_USERNAME', { unsafe: true }) // can be empty
}
export function getEmailPassword() {
return getStringFromEnv('EMAIL_PASSWORD', { unsafe: true }) // can be empty
}
export const getFileImporterQueuePostgresUrl = () =>
process.env['FILEIMPORT_QUEUE_POSTGRES_URL'] ?? null
@@ -971,18 +971,20 @@ Generate the environment variables for Speckle server and Speckle objects deploy
- name: EMAIL
value: "true"
- name: EMAIL_HOST
value: "{{ .Values.server.email.host }}"
value: {{ .Values.server.email.host | quote }}
- name: EMAIL_PORT
value: "{{ .Values.server.email.port }}"
value: {{ .Values.server.email.port | quote }}
- name: EMAIL_USERNAME
value: "{{ .Values.server.email.username }}"
value: {{ .Values.server.email.username | quote }}
- name: EMAIL_PASSWORD
valueFrom:
secretKeyRef:
name: {{ default .Values.secretName .Values.server.email.password.secretName }}
key: {{ default "email_password" .Values.server.email.password.secretKey }}
- name: EMAIL_FROM
value: "{{ .Values.server.email.from }}"
value: {{ .Values.server.email.from | quote }}
- name: EMAIL_SECURE
value: {{ .Values.server.email.secure | quote }}
- name: EMAIL_VERIFICATION_TIMEOUT_MINUTES
value: {{ .Values.server.email.verificationTimeoutMinutes | quote }}
{{- end }}
@@ -1307,6 +1307,11 @@
}
}
},
"secure": {
"type": "boolean",
"description": "If true, will use TLS when connecting to the email server",
"default": true
},
"networkPolicy": {
"type": "object",
"properties": {
+3
View File
@@ -800,6 +800,9 @@ server:
## @param server.email.password.secretKey The key within the Kubernetes Secret holding the email password as its value.
##
secretKey: ''
## @param server.email.secure If true, will use TLS when connecting to the email server
##
secure: true
## @extra server.email.networkPolicy If networkPolicy is enabled for Speckle server, this provides the Network Policy with the necessary details to allow egress connections to the email server
##
networkPolicy: