fix: support SMTP STARTTLS mode and NTLM auth (#5426)
* fix: support SMTP STARTTLS mode and NTLM auth Add explicit SMTP STARTTLS configuration for 587-style connections and keep SSL/TLS as the implicit TLS mode. Prefer PLAIN when advertised, keep LOGIN compatibility, and add NTLM as a fallback for Exchange SMTP servers that require it after STARTTLS. * fix: respect explicit SMTP encryption mode * fix: preserve SMTP TLS compatibility
This commit is contained in:
+101
-4
@@ -30,6 +30,8 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
@@ -57,6 +59,8 @@ const createEmailSchema = (t: (key: string) => string) =>
|
||||
}, t('Enter a valid email or leave blank')),
|
||||
SMTPToken: z.string(),
|
||||
SMTPSSLEnabled: z.boolean(),
|
||||
SMTPStartTLSEnabled: z.boolean(),
|
||||
SMTPInsecureSkipVerify: z.boolean(),
|
||||
SMTPForceAuthLogin: z.boolean(),
|
||||
})
|
||||
|
||||
@@ -66,6 +70,17 @@ type EmailSettingsSectionProps = {
|
||||
defaultValues: EmailFormValues
|
||||
}
|
||||
|
||||
type SmtpSecurityMode = 'none' | 'ssl_tls' | 'starttls'
|
||||
|
||||
function getSmtpSecurityMode(values: {
|
||||
SMTPSSLEnabled: boolean
|
||||
SMTPStartTLSEnabled: boolean
|
||||
}): SmtpSecurityMode {
|
||||
if (values.SMTPSSLEnabled) return 'ssl_tls'
|
||||
if (values.SMTPStartTLSEnabled) return 'starttls'
|
||||
return 'none'
|
||||
}
|
||||
|
||||
export function EmailSettingsSection({
|
||||
defaultValues,
|
||||
}: EmailSettingsSectionProps) {
|
||||
@@ -81,13 +96,16 @@ export function EmailSettingsSection({
|
||||
useResetForm(form, defaultValues)
|
||||
|
||||
const onSubmit = async (values: EmailFormValues) => {
|
||||
const securityMode = getSmtpSecurityMode(values)
|
||||
const sanitized = {
|
||||
SMTPServer: values.SMTPServer.trim(),
|
||||
SMTPPort: values.SMTPPort.trim(),
|
||||
SMTPAccount: values.SMTPAccount.trim(),
|
||||
SMTPFrom: values.SMTPFrom.trim(),
|
||||
SMTPToken: values.SMTPToken.trim(),
|
||||
SMTPSSLEnabled: values.SMTPSSLEnabled,
|
||||
SMTPSSLEnabled: securityMode === 'ssl_tls',
|
||||
SMTPStartTLSEnabled: securityMode === 'starttls',
|
||||
SMTPInsecureSkipVerify: values.SMTPInsecureSkipVerify,
|
||||
SMTPForceAuthLogin: values.SMTPForceAuthLogin,
|
||||
}
|
||||
|
||||
@@ -98,6 +116,8 @@ export function EmailSettingsSection({
|
||||
SMTPFrom: defaultValues.SMTPFrom.trim(),
|
||||
SMTPToken: defaultValues.SMTPToken.trim(),
|
||||
SMTPSSLEnabled: defaultValues.SMTPSSLEnabled,
|
||||
SMTPStartTLSEnabled: defaultValues.SMTPStartTLSEnabled,
|
||||
SMTPInsecureSkipVerify: defaultValues.SMTPInsecureSkipVerify,
|
||||
SMTPForceAuthLogin: defaultValues.SMTPForceAuthLogin,
|
||||
}
|
||||
|
||||
@@ -130,6 +150,20 @@ export function EmailSettingsSection({
|
||||
})
|
||||
}
|
||||
|
||||
if (sanitized.SMTPStartTLSEnabled !== initial.SMTPStartTLSEnabled) {
|
||||
updates.push({
|
||||
key: 'SMTPStartTLSEnabled',
|
||||
value: sanitized.SMTPStartTLSEnabled,
|
||||
})
|
||||
}
|
||||
|
||||
if (sanitized.SMTPInsecureSkipVerify !== initial.SMTPInsecureSkipVerify) {
|
||||
updates.push({
|
||||
key: 'SMTPInsecureSkipVerify',
|
||||
value: sanitized.SMTPInsecureSkipVerify,
|
||||
})
|
||||
}
|
||||
|
||||
if (sanitized.SMTPForceAuthLogin !== initial.SMTPForceAuthLogin) {
|
||||
updates.push({
|
||||
key: 'SMTPForceAuthLogin',
|
||||
@@ -197,15 +231,78 @@ export function EmailSettingsSection({
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormItem>
|
||||
<FormLabel>{t('SMTP encryption')}</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
value={getSmtpSecurityMode({
|
||||
SMTPSSLEnabled: form.watch('SMTPSSLEnabled'),
|
||||
SMTPStartTLSEnabled: form.watch('SMTPStartTLSEnabled'),
|
||||
})}
|
||||
onValueChange={(value) => {
|
||||
const mode = value as SmtpSecurityMode
|
||||
form.setValue('SMTPSSLEnabled', mode === 'ssl_tls', {
|
||||
shouldDirty: true,
|
||||
})
|
||||
form.setValue('SMTPStartTLSEnabled', mode === 'starttls', {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}}
|
||||
className='gap-3'
|
||||
>
|
||||
<div className='flex items-center gap-2'>
|
||||
<RadioGroupItem value='none' id='smtp-security-none' />
|
||||
<Label
|
||||
htmlFor='smtp-security-none'
|
||||
className='cursor-pointer font-normal'
|
||||
>
|
||||
{t('No encryption')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<RadioGroupItem
|
||||
value='ssl_tls'
|
||||
id='smtp-security-ssl-tls'
|
||||
/>
|
||||
<Label
|
||||
htmlFor='smtp-security-ssl-tls'
|
||||
className='cursor-pointer font-normal'
|
||||
>
|
||||
{t('SSL/TLS')}
|
||||
</Label>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<RadioGroupItem
|
||||
value='starttls'
|
||||
id='smtp-security-starttls'
|
||||
/>
|
||||
<Label
|
||||
htmlFor='smtp-security-starttls'
|
||||
className='cursor-pointer font-normal'
|
||||
>
|
||||
{t('STARTTLS')}
|
||||
</Label>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Choose one SMTP transport security mode')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='SMTPSSLEnabled'
|
||||
name='SMTPInsecureSkipVerify'
|
||||
render={({ field }) => (
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable SSL/TLS')}</FormLabel>
|
||||
<FormLabel>
|
||||
{t('Skip SMTP TLS certificate verification')}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Use secure connection when sending emails')}
|
||||
{t(
|
||||
'Allow self-signed or hostname-mismatched SMTP certificates'
|
||||
)}
|
||||
</FormDescription>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
|
||||
@@ -36,6 +36,8 @@ const defaultOperationsSettings: OperationsSettings = {
|
||||
SMTPFrom: '',
|
||||
SMTPToken: '',
|
||||
SMTPSSLEnabled: false,
|
||||
SMTPStartTLSEnabled: false,
|
||||
SMTPInsecureSkipVerify: false,
|
||||
SMTPForceAuthLogin: false,
|
||||
WorkerUrl: '',
|
||||
WorkerValidKey: '',
|
||||
|
||||
@@ -71,6 +71,8 @@ const OPERATIONS_SECTIONS = [
|
||||
SMTPFrom: settings.SMTPFrom,
|
||||
SMTPToken: settings.SMTPToken,
|
||||
SMTPSSLEnabled: settings.SMTPSSLEnabled,
|
||||
SMTPStartTLSEnabled: settings.SMTPStartTLSEnabled,
|
||||
SMTPInsecureSkipVerify: settings.SMTPInsecureSkipVerify,
|
||||
SMTPForceAuthLogin: settings.SMTPForceAuthLogin,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -334,6 +334,8 @@ export type OperationsSettings = {
|
||||
SMTPFrom: string
|
||||
SMTPToken: string
|
||||
SMTPSSLEnabled: boolean
|
||||
SMTPStartTLSEnabled: boolean
|
||||
SMTPInsecureSkipVerify: boolean
|
||||
SMTPForceAuthLogin: boolean
|
||||
WorkerUrl: string
|
||||
WorkerValidKey: string
|
||||
|
||||
Reference in New Issue
Block a user