mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-13 02:41:11 +00:00
UBERF-9712: Improve mail TLS settings and logs for self hosters (#8399)
Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
parent
f9d6ef0621
commit
d994ed202d
@ -17,6 +17,12 @@ SMTP settings:
|
||||
- `SMTP_PORT`: Port number of the SMTP server.
|
||||
- `SMTP_USERNAME`: Username for authenticating with the SMTP server. Refer to your SMTP server documentation for the appropriate format.
|
||||
- `SMTP_PASSWORD`: Password for authenticating with the SMTP server. Refer to your SMTP server documentation for the appropriate format.
|
||||
- `SMTP_TLS_MODE` (Optional): TLS mode for SMTP connection. Default: 'upgrade'. Possible values:
|
||||
- `secure`: Always use TLS (implicit TLS)
|
||||
- `upgrade`: Start unencrypted, upgrade to TLS if supported (STARTTLS)
|
||||
- `ignore`: Do not use TLS (not recommended for production use)
|
||||
- `SMTP_DEBUG_LOG` (Optional): Enable debug logging for SMTP connection. Set to 'true' to enable. Default: false
|
||||
- `SMTP_ALLOW_SELF_SIGNED` (Optional): Allow self-signed certificates for TLS connections. Set to 'true' to enable (not recommended for production use). Default: false
|
||||
|
||||
SES settings:
|
||||
- `SES_ACCESS_KEY`: AWS SES access key for authentication.
|
||||
|
@ -45,7 +45,50 @@ describe('Config', () => {
|
||||
Host: 'smtp.example.com',
|
||||
Port: 587,
|
||||
Username: 'user',
|
||||
Password: undefined
|
||||
Password: undefined,
|
||||
TlsMode: 'upgrade',
|
||||
DebugLog: false,
|
||||
AllowSelfSigned: false
|
||||
})
|
||||
})
|
||||
|
||||
test('should properly configure TLS settings', () => {
|
||||
process.env.PORT = '1025'
|
||||
process.env.SMTP_HOST = 'smtp.example.com'
|
||||
process.env.SMTP_PORT = '587'
|
||||
process.env.SMTP_TLS_MODE = 'secure'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { default: config, getTlsSettings } = require('../config')
|
||||
const tlsSettings = getTlsSettings(config.smtpConfig)
|
||||
|
||||
expect(tlsSettings).toEqual({
|
||||
secure: true,
|
||||
ignoreTLS: false
|
||||
})
|
||||
})
|
||||
|
||||
test('should handle debug and self-signed certificate settings', () => {
|
||||
process.env.PORT = '1025'
|
||||
process.env.SMTP_HOST = 'smtp.example.com'
|
||||
process.env.SMTP_PORT = '587'
|
||||
process.env.SMTP_DEBUG_LOG = 'true'
|
||||
process.env.SMTP_ALLOW_SELF_SIGNED = 'true'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { default: config, getTlsSettings } = require('../config')
|
||||
|
||||
expect(config.smtpConfig).toMatchObject({
|
||||
DebugLog: true,
|
||||
AllowSelfSigned: true
|
||||
})
|
||||
const tlsSettings = getTlsSettings(config.smtpConfig)
|
||||
expect(tlsSettings).toEqual({
|
||||
secure: false,
|
||||
ignoreTLS: false,
|
||||
tls: {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -81,4 +124,13 @@ describe('Config', () => {
|
||||
|
||||
expect(() => require('../config')).toThrow('Both SMTP and SES configuration are specified, please specify only one')
|
||||
})
|
||||
|
||||
test('should throw an error if invalid TLS mode is provided', () => {
|
||||
process.env.PORT = '1025'
|
||||
process.env.SMTP_HOST = 'smtp.example.com'
|
||||
process.env.SMTP_PORT = '587'
|
||||
process.env.SMTP_TLS_MODE = 'invalid'
|
||||
|
||||
expect(() => require('../config')).toThrow('Invalid SMTP_TLS_MODE value. Must be one of: secure, upgrade, ignore')
|
||||
})
|
||||
})
|
||||
|
@ -29,11 +29,41 @@ export interface SesConfig {
|
||||
Region: string
|
||||
}
|
||||
|
||||
export enum TlsOptions {
|
||||
SECURE = 'secure', // Always use TLS (implicit TLS)
|
||||
UPGRADE = 'upgrade', // Start unencrypted, upgrade to TLS if supported (STARTTLS)
|
||||
IGNORE = 'ignore' // Do not use TLS (not recommended for production use)
|
||||
}
|
||||
|
||||
export interface SmtpConfig {
|
||||
Host: string
|
||||
Port: number
|
||||
Username: string | undefined
|
||||
Password: string | undefined
|
||||
TlsMode: TlsOptions
|
||||
DebugLog?: boolean
|
||||
AllowSelfSigned?: boolean
|
||||
}
|
||||
|
||||
export interface TlsSettings {
|
||||
secure: boolean
|
||||
ignoreTLS: boolean
|
||||
tls?: {
|
||||
rejectUnauthorized: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function getTlsSettings (config: SmtpConfig): TlsSettings {
|
||||
const tlsConfig: TlsSettings = {
|
||||
secure: config.TlsMode === TlsOptions.SECURE || config.Port === 465,
|
||||
ignoreTLS: config.TlsMode === TlsOptions.IGNORE
|
||||
}
|
||||
if (config.AllowSelfSigned === true) {
|
||||
tlsConfig.tls = {
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
}
|
||||
return tlsConfig
|
||||
}
|
||||
|
||||
const envMap = {
|
||||
@ -46,12 +76,25 @@ const envMap = {
|
||||
SmtpHost: 'SMTP_HOST',
|
||||
SmtpPort: 'SMTP_PORT',
|
||||
SmtpUsername: 'SMTP_USERNAME',
|
||||
SmtpPassword: 'SMTP_PASSWORD'
|
||||
SmtpPassword: 'SMTP_PASSWORD',
|
||||
SmtpTlsMode: 'SMTP_TLS_MODE', // TLS mode, see TlsOptions for possible values
|
||||
SmtpDebugLog: 'SMTP_DEBUG_LOG', // Enable debug logging for SMTP
|
||||
SmtpAllowSelfSigned: 'SMTP_ALLOW_SELF_SIGNED' // Allow self-signed certificates (not recommended for production use)
|
||||
}
|
||||
|
||||
const parseNumber = (str: string | undefined): number | undefined => (str !== undefined ? Number(str) : undefined)
|
||||
const isEmpty = (str: string | undefined): boolean => str === undefined || str.trim().length === 0
|
||||
|
||||
const normalizeTlsMode = (mode: string | undefined): TlsOptions | undefined => {
|
||||
if (mode === undefined || mode === '') return undefined
|
||||
const normalized = mode.toLowerCase()
|
||||
const value: TlsOptions | undefined = Object.values(TlsOptions).find((opt) => opt.toLowerCase() === normalized)
|
||||
if (value === undefined) {
|
||||
throw Error('Invalid SMTP_TLS_MODE value. Must be one of: secure, upgrade, ignore')
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const buildSesConfig = (): SesConfig => {
|
||||
const accessKey = process.env[envMap.SesAccessKey]
|
||||
const secretKey = process.env[envMap.SesSecretKey]
|
||||
@ -78,10 +121,12 @@ const buildSmtpConfig = (): SmtpConfig => {
|
||||
const port = parseNumber(process.env[envMap.SmtpPort])
|
||||
const username = process.env[envMap.SmtpUsername]
|
||||
const password = process.env[envMap.SmtpPassword]
|
||||
const tlsMode = normalizeTlsMode(process.env[envMap.SmtpTlsMode])
|
||||
const debugLog = process.env[envMap.SmtpDebugLog]?.toLowerCase() === 'true'
|
||||
const allowSelfSigned = process.env[envMap.SmtpAllowSelfSigned]?.toLowerCase() === 'true'
|
||||
|
||||
if (isEmpty(host) || port === undefined) {
|
||||
const missingKeys = [isEmpty(host) && 'SMTP_HOST', port === undefined && 'SMTP_PORT'].filter(Boolean)
|
||||
|
||||
throw Error(`Missing env variables for SMTP configuration: ${missingKeys.join(', ')}`)
|
||||
}
|
||||
|
||||
@ -89,7 +134,10 @@ const buildSmtpConfig = (): SmtpConfig => {
|
||||
Host: host as string,
|
||||
Port: port,
|
||||
Username: username,
|
||||
Password: password
|
||||
Password: password,
|
||||
TlsMode: tlsMode ?? TlsOptions.UPGRADE,
|
||||
DebugLog: debugLog,
|
||||
AllowSelfSigned: allowSelfSigned
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@
|
||||
import nodemailer, { type Transporter } from 'nodemailer'
|
||||
import aws from '@aws-sdk/client-ses'
|
||||
|
||||
import type { Config, SmtpConfig, SesConfig } from './config'
|
||||
import { type Config, type SmtpConfig, type SesConfig, getTlsSettings } from './config'
|
||||
|
||||
function smtp (config: SmtpConfig): Transporter {
|
||||
const auth =
|
||||
@ -25,10 +25,14 @@ function smtp (config: SmtpConfig): Transporter {
|
||||
pass: config.Password
|
||||
}
|
||||
: undefined
|
||||
const tlsSettings = getTlsSettings(config)
|
||||
return nodemailer.createTransport({
|
||||
host: config.Host,
|
||||
port: config.Port,
|
||||
auth
|
||||
auth,
|
||||
logger: true,
|
||||
debug: config.DebugLog,
|
||||
...tlsSettings
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user