feat: SMTP Compatibility Enhancement and SSL Protocol Support (#174)

This commit is contained in:
Deng Junhai 2024-06-23 03:04:52 +08:00
parent 9d4ff7285a
commit 6f3cdad1e7
8 changed files with 58 additions and 9 deletions

View File

@ -20,6 +20,7 @@ export type GeneralState = {
export type MailState = { export type MailState = {
host: string; host: string;
protocol: boolean;
port: number; port: number;
username: string; username: string;
password: string; password: string;
@ -137,6 +138,7 @@ export const initialSystemState: SystemProps = {
auth_footer: false, auth_footer: false,
}, },
mail: { mail: {
protocol: false,
host: "", host: "",
port: 465, port: 465,
username: "", username: "",

View File

@ -704,6 +704,7 @@
"debugMode": "调试模式", "debugMode": "调试模式",
"debugModeTip": "调试模式,开启后日志将输出详细的请求参数等的日志,用于排查问题", "debugModeTip": "调试模式,开启后日志将输出详细的请求参数等的日志,用于排查问题",
"mailHost": "发件域名", "mailHost": "发件域名",
"mailProtocol": "发件协议",
"mailPort": "SMTP 端口", "mailPort": "SMTP 端口",
"mailUser": "用户名", "mailUser": "用户名",
"mailPass": "密码", "mailPass": "密码",

View File

@ -513,6 +513,7 @@
"backend": "Backend Domain", "backend": "Backend Domain",
"backendTip": "Backend domain name (docker installation default path is/api), used to receive callbacks and storage, etc., default is empty\nExample: {{backend}}", "backendTip": "Backend domain name (docker installation default path is/api), used to receive callbacks and storage, etc., default is empty\nExample: {{backend}}",
"mailHost": "Mail Host", "mailHost": "Mail Host",
"mailProtocol": "Mail Protocol",
"mailPort": "SMTP Port", "mailPort": "SMTP Port",
"mailUser": "Username", "mailUser": "Username",
"mailPass": "Password", "mailPass": "Password",

View File

@ -513,6 +513,7 @@
"backend": "バックエンドドメイン", "backend": "バックエンドドメイン",
"backendTip": "バックエンドドメイン名( dockerインストールのデフォルトパスは/api )、コールバックやストレージなどを受信するために使用、デフォルトは空です\n例{{ backend}}", "backendTip": "バックエンドドメイン名( dockerインストールのデフォルトパスは/api )、コールバックやストレージなどを受信するために使用、デフォルトは空です\n例{{ backend}}",
"mailHost": "送信ドメイン名", "mailHost": "送信ドメイン名",
"mailProtocol": "送信プロトコル",
"mailPort": "SMTPポート", "mailPort": "SMTPポート",
"mailUser": "ユーザー名", "mailUser": "ユーザー名",
"mailPass": "パスワード", "mailPass": "パスワード",

View File

@ -513,6 +513,7 @@
"backend": "Домен бэкэнда", "backend": "Домен бэкэнда",
"backendTip": "Имя домена бэкенда (путь установки докера по умолчанию -/api), используется для получения обратных вызовов и хранения и т. д., значение по умолчанию пусто\nПример: {{backend}}", "backendTip": "Имя домена бэкенда (путь установки докера по умолчанию -/api), используется для получения обратных вызовов и хранения и т. д., значение по умолчанию пусто\nПример: {{backend}}",
"mailHost": "Почтовый хост", "mailHost": "Почтовый хост",
"mailProtocol": "Протокол отправки",
"mailPort": "Порт SMTP", "mailPort": "Порт SMTP",
"mailUser": "Имя пользователя", "mailUser": "Имя пользователя",
"mailPass": "Пароль", "mailPass": "Пароль",

View File

@ -12,6 +12,13 @@ import Paragraph, {
ParagraphSpace, ParagraphSpace,
} from "@/components/Paragraph.tsx"; } from "@/components/Paragraph.tsx";
import { Button } from "@/components/ui/button.tsx"; import { Button } from "@/components/ui/button.tsx";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select.tsx";
import { Label } from "@/components/ui/label.tsx"; import { Label } from "@/components/ui/label.tsx";
import { Input } from "@/components/ui/input.tsx"; import { Input } from "@/components/ui/input.tsx";
import { useMemo, useReducer, useState } from "react"; import { useMemo, useReducer, useState } from "react";
@ -283,8 +290,8 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
data.username.length > 0 && data.username.length > 0 &&
data.password.length > 0 && data.password.length > 0 &&
data.from.length > 0 && data.from.length > 0 &&
/\w+@\w+\.\w+/.test(data.from) && data.username.includes("@") &&
!data.username.includes("@") !/\w+@/.test(data.from)
); );
}, [data]); }, [data]);
@ -332,6 +339,30 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
placeholder={`smtp.qcloudmail.com`} placeholder={`smtp.qcloudmail.com`}
/> />
</ParagraphItem> </ParagraphItem>
<ParagraphItem>
<Label>
<Require /> {t("admin.system.mailProtocol")}
</Label>
<Select
value={data.protocol ? "true" : "false"}
onValueChange={(value: string) => {
dispatch({
type: "update:mail.protocol",
value: value === "true",
});
}}
>
<SelectTrigger className={`select`}>
<SelectValue
placeholder={data.protocol ? t("admin.system.mailProtocolTLS") : t("admin.system.mailProtocolSSL")}
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="true">TLS</SelectItem>
<SelectItem value="false">SSL</SelectItem>
</SelectContent>
</Select>
</ParagraphItem>
<ParagraphItem> <ParagraphItem>
<Label> <Label>
<Require /> {t("admin.system.mailPort")} <Require /> {t("admin.system.mailPort")}
@ -360,7 +391,7 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
} }
className={cn( className={cn(
"transition-all duration-300", "transition-all duration-300",
data.username.includes("@") && `border-red-700`, !data.username.includes("@") && `border-red-700`,
)} )}
placeholder={t("admin.system.mailUser")} placeholder={t("admin.system.mailUser")}
/> />
@ -396,7 +427,7 @@ function Mail({ data, dispatch, onChange }: CompProps<MailState>) {
className={cn( className={cn(
"transition-all duration-300", "transition-all duration-300",
data.from.length > 0 && data.from.length > 0 &&
!/\w+@\w+\.\w+/.test(data.from) && !/\w+@/.test(data.from) &&
`border-red-700`, `border-red-700`,
)} )}
/> />

View File

@ -4,8 +4,9 @@ import (
"chat/globals" "chat/globals"
"chat/utils" "chat/utils"
"fmt" "fmt"
"github.com/spf13/viper"
"strings" "strings"
"github.com/spf13/viper"
) )
type ApiInfo struct { type ApiInfo struct {
@ -54,6 +55,7 @@ type whiteList struct {
type mailState struct { type mailState struct {
Host string `json:"host" mapstructure:"host"` Host string `json:"host" mapstructure:"host"`
Protocol bool `json:"protocol" mapstructure:"protocol"`
Port int `json:"port" mapstructure:"port"` Port int `json:"port" mapstructure:"port"`
Username string `json:"username" mapstructure:"username"` Username string `json:"username" mapstructure:"username"`
Password string `json:"password" mapstructure:"password"` Password string `json:"password" mapstructure:"password"`
@ -162,6 +164,7 @@ func (c *SystemConfig) GetBackend() string {
func (c *SystemConfig) GetMail() *utils.SmtpPoster { func (c *SystemConfig) GetMail() *utils.SmtpPoster {
return utils.NewSmtpPoster( return utils.NewSmtpPoster(
c.Mail.Host, c.Mail.Host,
c.Mail.Protocol,
c.Mail.Port, c.Mail.Port,
c.Mail.Username, c.Mail.Username,
c.Mail.Password, c.Mail.Password,

View File

@ -3,22 +3,25 @@ package utils
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"gopkg.in/mail.v2"
"strings" "strings"
"text/template" "text/template"
"gopkg.in/mail.v2"
) )
type SmtpPoster struct { type SmtpPoster struct {
Host string Host string
Protocol bool
Port int Port int
Username string Username string
Password string Password string
From string From string
} }
func NewSmtpPoster(host string, port int, username string, password string, from string) *SmtpPoster { func NewSmtpPoster(host string, protocol bool, port int, username string, password string, from string) *SmtpPoster {
return &SmtpPoster{ return &SmtpPoster{
Host: host, Host: host,
Protocol: protocol,
Port: port, Port: port,
Username: username, Username: username,
Password: password, Password: password,
@ -35,14 +38,20 @@ func (s *SmtpPoster) SendMail(to string, subject string, body string) error {
return fmt.Errorf("smtp not configured properly") return fmt.Errorf("smtp not configured properly")
} }
dialer := mail.NewDialer(s.Host, s.Port, s.From, s.Password) dialer := mail.NewDialer(s.Host, s.Port, s.Username, s.Password)
message := mail.NewMessage() message := mail.NewMessage()
message.SetHeader("From", fmt.Sprintf("%s <%s>", s.Username, s.From)) message.SetHeader("From", s.From)
message.SetHeader("To", to) message.SetHeader("To", to)
message.SetHeader("Subject", subject) message.SetHeader("Subject", subject)
message.SetBody("text/html", body) message.SetBody("text/html", body)
if s.Protocol {
dialer.StartTLSPolicy = mail.MandatoryStartTLS
} else {
dialer.StartTLSPolicy = mail.NoStartTLS
}
// outlook STARTTLS policy adapter // outlook STARTTLS policy adapter
if strings.Contains(s.Host, "outlook") { if strings.Contains(s.Host, "outlook") {
dialer.StartTLSPolicy = mail.MandatoryStartTLS dialer.StartTLSPolicy = mail.MandatoryStartTLS