mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-07-17 10:06:08 +00:00
fix(email): build an RFC 5322 message with a proper From address and name (#5941)
The notification/test email carried only From/To/Subject/MIME headers, and
the From header was the raw SMTP username. Two problems:
- When the SMTP login is not a bare email address (common with relays and
submission services), the From header has no valid address and strict
receivers reject the message — e.g. Gmail returns "550-5.7.1 ... Messages
missing a valid address in From: header".
- There was no Date (mandatory per RFC 5322 section 3.6) and no Message-ID,
which also raises spam score.
Add smtpFrom (sender address) and smtpFromName (display name) settings and
assemble the message with net/mail: a name-addr From ("Name" <addr>), a
Date, a Message-ID, and an RFC 2047 encoded Subject, in a deterministic
header order. From falls back to the username when smtpFrom is empty, so
existing setups keep working. Wire the settings through the model, the SMTP
send and test paths, the Email settings UI, and all 13 locale files;
regenerate the Zod/OpenAPI artifacts.
Validate smtpFrom in AllSetting.CheckValid (reject anything net/mail cannot
parse), which surfaces a bad address at configuration time and prevents CRLF
header injection; strip CR/LF in buildMessage as defense in depth. Add
buildMessage and CheckValid tests.
This commit is contained in:
committed by
GitHub
parent
ae0da4c51f
commit
1cfd7b49b0
@@ -35,6 +35,8 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"smtpEnable": false,
|
||||
"smtpEnabledEvents": "",
|
||||
"smtpEncryptionType": "",
|
||||
"smtpFrom": "",
|
||||
"smtpFromName": "",
|
||||
"smtpHost": "",
|
||||
"smtpMemory": 0,
|
||||
"smtpPassword": "",
|
||||
@@ -138,6 +140,8 @@ export const EXAMPLES: Record<string, unknown> = {
|
||||
"smtpEnable": false,
|
||||
"smtpEnabledEvents": "",
|
||||
"smtpEncryptionType": "",
|
||||
"smtpFrom": "",
|
||||
"smtpFromName": "",
|
||||
"smtpHost": "",
|
||||
"smtpMemory": 0,
|
||||
"smtpPassword": "",
|
||||
|
||||
@@ -116,6 +116,12 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"smtpEncryptionType": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtpFrom": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtpFromName": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtpHost": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -349,6 +355,8 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"smtpEnable",
|
||||
"smtpEnabledEvents",
|
||||
"smtpEncryptionType",
|
||||
"smtpFrom",
|
||||
"smtpFromName",
|
||||
"smtpHost",
|
||||
"smtpMemory",
|
||||
"smtpPassword",
|
||||
@@ -549,6 +557,12 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"smtpEncryptionType": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtpFrom": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtpFromName": {
|
||||
"type": "string"
|
||||
},
|
||||
"smtpHost": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -789,6 +803,8 @@ export const SCHEMAS: Record<string, unknown> = {
|
||||
"smtpEnable",
|
||||
"smtpEnabledEvents",
|
||||
"smtpEncryptionType",
|
||||
"smtpFrom",
|
||||
"smtpFromName",
|
||||
"smtpHost",
|
||||
"smtpMemory",
|
||||
"smtpPassword",
|
||||
|
||||
@@ -41,6 +41,8 @@ export interface AllSetting {
|
||||
smtpEnable: boolean;
|
||||
smtpEnabledEvents: string;
|
||||
smtpEncryptionType: string;
|
||||
smtpFrom: string;
|
||||
smtpFromName: string;
|
||||
smtpHost: string;
|
||||
smtpMemory: number;
|
||||
smtpPassword: string;
|
||||
@@ -145,6 +147,8 @@ export interface AllSettingView {
|
||||
smtpEnable: boolean;
|
||||
smtpEnabledEvents: string;
|
||||
smtpEncryptionType: string;
|
||||
smtpFrom: string;
|
||||
smtpFromName: string;
|
||||
smtpHost: string;
|
||||
smtpMemory: number;
|
||||
smtpPassword: string;
|
||||
|
||||
@@ -53,6 +53,8 @@ export const AllSettingSchema = z.object({
|
||||
smtpEnable: z.boolean(),
|
||||
smtpEnabledEvents: z.string(),
|
||||
smtpEncryptionType: z.string(),
|
||||
smtpFrom: z.string(),
|
||||
smtpFromName: z.string(),
|
||||
smtpHost: z.string(),
|
||||
smtpMemory: z.number().int().min(0).max(100),
|
||||
smtpPassword: z.string(),
|
||||
@@ -158,6 +160,8 @@ export const AllSettingViewSchema = z.object({
|
||||
smtpEnable: z.boolean(),
|
||||
smtpEnabledEvents: z.string(),
|
||||
smtpEncryptionType: z.string(),
|
||||
smtpFrom: z.string(),
|
||||
smtpFromName: z.string(),
|
||||
smtpHost: z.string(),
|
||||
smtpMemory: z.number().int().min(0).max(100),
|
||||
smtpPassword: z.string(),
|
||||
|
||||
@@ -91,6 +91,8 @@ export class AllSetting {
|
||||
smtpPort = 587;
|
||||
smtpUsername = '';
|
||||
smtpPassword = '';
|
||||
smtpFrom = '';
|
||||
smtpFromName = '';
|
||||
smtpTo = '';
|
||||
smtpEncryptionType = 'starttls';
|
||||
smtpEnabledEvents = '';
|
||||
|
||||
@@ -82,6 +82,16 @@ export default function EmailTab({ allSetting, updateSetting }: EmailTabProps) {
|
||||
onClearArmedChange={(armed) => updateSetting({ clearSmtpPassword: armed })} />
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem paddings="small" title={t('pages.settings.smtpFrom')} description={t('pages.settings.smtpFromDesc')}>
|
||||
<Input value={allSetting.smtpFrom} placeholder="user@gmail.com"
|
||||
onChange={(e) => updateSetting({ smtpFrom: e.target.value })} />
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem paddings="small" title={t('pages.settings.smtpFromName')} description={t('pages.settings.smtpFromNameDesc')}>
|
||||
<Input value={allSetting.smtpFromName} placeholder="3x-ui"
|
||||
onChange={(e) => updateSetting({ smtpFromName: e.target.value })} />
|
||||
</SettingListItem>
|
||||
|
||||
<SettingListItem paddings="small" title={t('pages.settings.smtpTo')} description={t('pages.settings.smtpToDesc')}>
|
||||
<Input value={allSetting.smtpTo} placeholder="admin@example.com, ops@example.com"
|
||||
onChange={(e) => updateSetting({ smtpTo: e.target.value })} />
|
||||
|
||||
Reference in New Issue
Block a user