Преглед изворни кода

feat: 支持强制使用 AUTH LOGIN 以解决 outlook 等邮箱的发件问题 (#4112)

* feat: 支持强制使用 AUTH LOGIN 以解决 outlook 等邮箱的发件问题

* fix: 修复通过 SSL 发送邮件时绕过 AUTH LOGIN 的问题

* fix: remove redundant branch, delete test file, add i18n translations

- Remove redundant else-if branch in SendEmail since auth is already
  computed via getSMTPAuth()
- Delete option_smtp_auth_test.go as requested
- Add i18n translations for '强制使用 AUTH LOGIN' checkbox
星野梦月 пре 3 дана
родитељ
комит
a18ea3cc16

+ 1 - 0
common/constants.go

@@ -80,6 +80,7 @@ var InsecureTLSConfig = &tls.Config{InsecureSkipVerify: true}
 var SMTPServer = ""
 var SMTPPort = 587
 var SMTPSSLEnabled = false
+var SMTPForceAuthLogin = false
 var SMTPAccount = ""
 var SMTPFrom = ""
 var SMTPToken = ""

+ 15 - 4
common/email.go

@@ -19,6 +19,20 @@ func generateMessageID() (string, error) {
 	return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), GetRandomString(12), domain), nil
 }
 
+func shouldUseSMTPLoginAuth() bool {
+	if SMTPForceAuthLogin {
+		return true
+	}
+	return isOutlookServer(SMTPAccount) || slices.Contains(EmailLoginAuthServerList, SMTPServer)
+}
+
+func getSMTPAuth() smtp.Auth {
+	if shouldUseSMTPLoginAuth() {
+		return LoginAuth(SMTPAccount, SMTPToken)
+	}
+	return smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
+}
+
 func SendEmail(subject string, receiver string, content string) error {
 	if SMTPFrom == "" { // for compatibility
 		SMTPFrom = SMTPAccount
@@ -38,7 +52,7 @@ func SendEmail(subject string, receiver string, content string) error {
 		"Message-ID: %s\r\n"+ // 添加 Message-ID 头
 		"Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n",
 		receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), id, content))
-	auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
+	auth := getSMTPAuth()
 	addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort)
 	to := strings.Split(receiver, ";")
 	var err error
@@ -80,9 +94,6 @@ func SendEmail(subject string, receiver string, content string) error {
 		if err != nil {
 			return err
 		}
-	} else if isOutlookServer(SMTPAccount) || slices.Contains(EmailLoginAuthServerList, SMTPServer) {
-		auth = LoginAuth(SMTPAccount, SMTPToken)
-		err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
 	} else {
 		err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
 	}

+ 4 - 1
model/option.go

@@ -62,6 +62,7 @@ func InitOptionMap() {
 	common.OptionMap["SMTPAccount"] = ""
 	common.OptionMap["SMTPToken"] = ""
 	common.OptionMap["SMTPSSLEnabled"] = strconv.FormatBool(common.SMTPSSLEnabled)
+	common.OptionMap["SMTPForceAuthLogin"] = strconv.FormatBool(common.SMTPForceAuthLogin)
 	common.OptionMap["Notice"] = ""
 	common.OptionMap["About"] = ""
 	common.OptionMap["HomePageContent"] = ""
@@ -233,7 +234,7 @@ func updateOptionMap(key string, value string) (err error) {
 			common.ImageDownloadPermission = intValue
 		}
 	}
-	if strings.HasSuffix(key, "Enabled") || key == "DefaultCollapseSidebar" || key == "DefaultUseAutoGroup" {
+	if strings.HasSuffix(key, "Enabled") || key == "DefaultCollapseSidebar" || key == "DefaultUseAutoGroup" || key == "SMTPForceAuthLogin" {
 		boolValue := value == "true"
 		switch key {
 		case "PasswordRegisterEnabled":
@@ -308,6 +309,8 @@ func updateOptionMap(key string, value string) (err error) {
 			setting.StopOnSensitiveEnabled = boolValue
 		case "SMTPSSLEnabled":
 			common.SMTPSSLEnabled = boolValue
+		case "SMTPForceAuthLogin":
+			common.SMTPForceAuthLogin = boolValue
 		case "WorkerAllowHttpImageRequestEnabled":
 			system_setting.WorkerAllowHttpImageRequestEnabled = boolValue
 		case "DefaultUseAutoGroup":

+ 5 - 4
web/bun.lock

@@ -1,5 +1,6 @@
 {
   "lockfileVersion": 1,
+  "configVersion": 0,
   "workspaces": {
     "": {
       "name": "react-template",
@@ -10,7 +11,7 @@
         "@visactor/react-vchart": "~1.8.8",
         "@visactor/vchart": "~1.8.8",
         "@visactor/vchart-semi-theme": "~1.8.8",
-        "axios": "1.12.0",
+        "axios": "1.13.5",
         "clsx": "^2.1.1",
         "dayjs": "^1.11.11",
         "history": "^5.3.0",
@@ -776,7 +777,7 @@
 
     "autoprefixer": ["[email protected]", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="],
 
-    "axios": ["[email protected]2.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg=="],
+    "axios": ["[email protected]3.5", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q=="],
 
     "babel-plugin-macros": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", "resolve": "^1.19.0" } }, "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg=="],
 
@@ -1104,13 +1105,13 @@
 
     "flatted": ["[email protected]", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
 
-    "follow-redirects": ["[email protected].9", "", {}, "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="],
+    "follow-redirects": ["[email protected].11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
 
     "for-in": ["[email protected]", "", {}, "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ=="],
 
     "foreground-child": ["[email protected]", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
 
-    "form-data": ["[email protected].4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
+    "form-data": ["[email protected].5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
 
     "fraction.js": ["[email protected]", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
 

+ 11 - 0
web/src/components/settings/SystemSetting.jsx

@@ -91,6 +91,7 @@ const SystemSetting = () => {
     EmailDomainRestrictionEnabled: '',
     EmailAliasRestrictionEnabled: '',
     SMTPSSLEnabled: '',
+    SMTPForceAuthLogin: '',
     EmailDomainWhitelist: [],
     TelegramOAuthEnabled: '',
     TelegramBotToken: '',
@@ -182,6 +183,7 @@ const SystemSetting = () => {
           case 'EmailDomainRestrictionEnabled':
           case 'EmailAliasRestrictionEnabled':
           case 'SMTPSSLEnabled':
+          case 'SMTPForceAuthLogin':
           case 'LinuxDOOAuthEnabled':
           case 'discord.enabled':
           case 'oidc.enabled':
@@ -1335,6 +1337,15 @@ const SystemSetting = () => {
                       >
                         {t('启用SMTP SSL')}
                       </Form.Checkbox>
+                      <Form.Checkbox
+                        field='SMTPForceAuthLogin'
+                        noLabel
+                        onChange={(e) =>
+                          handleCheckboxChange('SMTPForceAuthLogin', e)
+                        }
+                      >
+                        {t('强制使用 AUTH LOGIN')}
+                      </Form.Checkbox>
                     </Col>
                   </Row>
                   <Button onClick={submitSMTP}>{t('保存 SMTP 设置')}</Button>

+ 1 - 0
web/src/i18n/locales/en.json

@@ -925,6 +925,7 @@
     "启用Gemini思考后缀适配": "Enable Gemini thinking suffix adaptation",
     "启用Ping间隔": "Enable Ping interval",
     "启用SMTP SSL": "Enable SMTP SSL",
+    "强制使用 AUTH LOGIN": "Force AUTH LOGIN",
     "启用SSRF防护(推荐开启以保护服务器安全)": "Enable SSRF Protection (Recommended for server security)",
     "启用供应商": "Enable Provider",
     "启用全部": "Enable all",

+ 1 - 0
web/src/i18n/locales/fr.json

@@ -920,6 +920,7 @@
     "启用Gemini思考后缀适配": "Activer l'adaptation du suffixe de la pensée Gemini",
     "启用Ping间隔": "Activer l'intervalle de ping",
     "启用SMTP SSL": "Activer SMTP SSL",
+    "强制使用 AUTH LOGIN": "Forcer AUTH LOGIN",
     "启用SSRF防护(推荐开启以保护服务器安全)": "Activer la protection SSRF (recommandé pour la sécurité du serveur)",
     "启用供应商": "Activer le fournisseur",
     "启用全部": "Activer tout",

+ 1 - 0
web/src/i18n/locales/ja.json

@@ -911,6 +911,7 @@
     "启用Gemini思考后缀适配": "Gemini思考サフィックスモードを有効にする",
     "启用Ping间隔": "Ping間隔を有効にする",
     "启用SMTP SSL": "SMTP SSLを有効にする",
+    "强制使用 AUTH LOGIN": "AUTH LOGINを強制する",
     "启用SSRF防护(推荐开启以保护服务器安全)": "SSRF保護を有効にする(サーバーを保護するため、有効化を推奨します)",
     "启用供应商": "プロバイダーを有効化",
     "启用全部": "すべてを有効にする",

+ 1 - 0
web/src/i18n/locales/ru.json

@@ -926,6 +926,7 @@
     "启用Gemini思考后缀适配": "Включить адаптацию суффикса мышления Gemini",
     "启用Ping间隔": "Включить интервал Ping",
     "启用SMTP SSL": "Включить SMTP SSL",
+    "强制使用 AUTH LOGIN": "Принудительно AUTH LOGIN",
     "启用SSRF防护(推荐开启以保护服务器安全)": "Включить защиту SSRF (рекомендуется включить для защиты безопасности сервера)",
     "启用供应商": "Включить поставщика",
     "启用全部": "Включить все",

+ 1 - 0
web/src/i18n/locales/vi.json

@@ -912,6 +912,7 @@
     "启用Gemini思考后缀适配": "Bật thích ứng hậu tố tư duy Gemini",
     "启用Ping间隔": "Bật khoảng thời gian Ping",
     "启用SMTP SSL": "Bật SMTP SSL",
+    "强制使用 AUTH LOGIN": "Buộc AUTH LOGIN",
     "启用SSRF防护(推荐开启以保护服务器安全)": "Bật bảo vệ SSRF (Khuyên dùng để bảo mật máy chủ)",
     "启用供应商": "Bật nhà cung cấp",
     "启用全部": "Bật tất cả",

+ 1 - 0
web/src/i18n/locales/zh-CN.json

@@ -680,6 +680,7 @@
     "启用Gemini思考后缀适配": "启用Gemini思考后缀适配",
     "启用Ping间隔": "启用Ping间隔",
     "启用SMTP SSL": "启用SMTP SSL",
+    "强制使用 AUTH LOGIN": "强制使用 AUTH LOGIN",
     "启用SSRF防护(推荐开启以保护服务器安全)": "启用SSRF防护(推荐开启以保护服务器安全)",
     "启用全部": "启用全部",
     "启用后可接入 io.net GPU 资源": "启用后可接入 io.net GPU 资源",

+ 1 - 0
web/src/i18n/locales/zh-TW.json

@@ -797,6 +797,7 @@
     "启用Gemini思考后缀适配": "啟用Gemini思考後綴相容",
     "启用Ping间隔": "啟用Ping間隔",
     "启用SMTP SSL": "啟用SMTP SSL",
+    "强制使用 AUTH LOGIN": "強制使用 AUTH LOGIN",
     "启用SSRF防护(推荐开启以保护服务器安全)": "啟用SSRF防護(推薦開啟以保護伺服器安全)",
     "启用全部": "啟用全部",
     "启用后可接入 io.net GPU 资源": "啟用後可接入 io.net GPU 資源",