diff --git a/go.mod b/go.mod index 1b399e01..75fb61e9 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/jdcloud-api/jdcloud-sdk-go v1.64.0 github.com/kong/go-kong v0.71.0 github.com/luthermonson/go-proxmox v0.3.2 + github.com/microcosm-cc/bluemonday v1.0.27 github.com/minio/minio-go/v7 v7.0.97 github.com/mohuatech/mohuacloud-go-sdk v0.0.0-20251115182757-6fba4d0a4c47 github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 @@ -192,6 +193,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect github.com/aws/smithy-go v1.24.0 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect @@ -209,6 +211,7 @@ require ( github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect diff --git a/go.sum b/go.sum index 45f464a1..dacffca7 100644 --- a/go.sum +++ b/go.sum @@ -238,6 +238,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfm github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/baidubce/bce-sdk-go v0.9.256 h1:/6UwBzDp+dRFpKRIb5WsvxfSiG4SLOIOghvagOK/q4Y= github.com/baidubce/bce-sdk-go v0.9.256/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -495,6 +497,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= @@ -648,6 +652,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= diff --git a/internal/notify/notifiers/sp_email.go b/internal/notify/notifiers/sp_email.go index 05e60d80..cb952d11 100644 --- a/internal/notify/notifiers/sp_email.go +++ b/internal/notify/notifiers/sp_email.go @@ -25,6 +25,7 @@ func init() { SenderAddress: credentials.SenderAddress, SenderName: credentials.SenderName, ReceiverAddress: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "receiverAddress", credentials.ReceiverAddress), + MessageFormat: xmaps.GetOrDefaultString(options.ProviderExtendedConfig, "format", email.MESSAGE_FORMAT_PLAIN), AllowInsecureConnections: credentials.AllowInsecureConnections, }) return provider, err diff --git a/pkg/core/notifier/providers/email/consts.go b/pkg/core/notifier/providers/email/consts.go new file mode 100644 index 00000000..cba86312 --- /dev/null +++ b/pkg/core/notifier/providers/email/consts.go @@ -0,0 +1,6 @@ +package email + +const ( + MESSAGE_FORMAT_PLAIN = "plain" + MESSAGE_FORMAT_HTML = "html" +) diff --git a/pkg/core/notifier/providers/email/email.go b/pkg/core/notifier/providers/email/email.go index 2ee60fc1..dde0ee48 100644 --- a/pkg/core/notifier/providers/email/email.go +++ b/pkg/core/notifier/providers/email/email.go @@ -6,6 +6,8 @@ import ( "fmt" "log/slog" + "github.com/microcosm-cc/bluemonday" + "github.com/certimate-go/certimate/internal/tools/smtp" "github.com/certimate-go/certimate/pkg/core/notifier" ) @@ -28,6 +30,10 @@ type NotifierConfig struct { SenderName string `json:"senderName,omitempty"` // 收件人邮箱。 ReceiverAddress string `json:"receiverAddress"` + // 消息格式。 + // 可取值 [MESSAGE_FORMAT_PLAIN]、[MESSAGE_FORMAT_HTML]。 + // 零值时默认值 [MESSAGE_FORMAT_PLAIN]。 + MessageFormat string `json:"messageFormat,omitempty"` // 是否允许不安全的连接。 AllowInsecureConnections bool `json:"allowInsecureConnections,omitempty"` } @@ -75,7 +81,16 @@ func (n *Notifier) Notify(ctx context.Context, subject string, message string) ( msg := smtp.NewMessage() msg.Subject(subject) - msg.SetBodyString(smtp.MIMETypeTextPlain, message) + switch n.config.MessageFormat { + case "", MESSAGE_FORMAT_PLAIN: + msg.SetBodyString(smtp.MIMETypeTextPlain, message) + case MESSAGE_FORMAT_HTML: + msg.SetBodyString(smtp.MIMETypeTextHTML, bluemonday.UGCPolicy().Sanitize(message)) + msg.AddAlternativeString(smtp.MIMETypeTextPlain, bluemonday.StrictPolicy().Sanitize(message)) + default: + return nil, fmt.Errorf("unsupported message format: '%s'", n.config.MessageFormat) + } + if n.config.SenderName == "" { msg.From(n.config.SenderAddress) } else { diff --git a/pkg/core/notifier/providers/email/email_test.go b/pkg/core/notifier/providers/email/email_test.go index 7e4028c8..11e4a69d 100644 --- a/pkg/core/notifier/providers/email/email_test.go +++ b/pkg/core/notifier/providers/email/email_test.go @@ -11,8 +11,9 @@ import ( ) const ( - mockSubject = "test_subject" - mockMessage = "test_message" + mockSubject = "test_subject" + mockMessage = "test_message" + mockHtmlMessage = "

Hello Certimate!

Google" ) var ( @@ -86,4 +87,40 @@ func TestNotify(t *testing.T) { t.Logf("ok: %v", res) }) + + t.Run("Notify_Html", func(t *testing.T) { + t.Log(strings.Join([]string{ + "args:", + fmt.Sprintf("SMTPHOST: %v", fSmtpHost), + fmt.Sprintf("SMTPPORT: %v", fSmtpPort), + fmt.Sprintf("SMTPTLS: %v", fSmtpTLS), + fmt.Sprintf("USERNAME: %v", fUsername), + fmt.Sprintf("PASSWORD: %v", fPassword), + fmt.Sprintf("SENDERADDRESS: %v", fSenderAddress), + fmt.Sprintf("RECEIVERADDRESS: %v", fReceiverAddress), + }, "\n")) + + provider, err := provider.NewNotifier(&provider.NotifierConfig{ + SmtpHost: fSmtpHost, + SmtpPort: int32(fSmtpPort), + SmtpTls: fSmtpTLS, + Username: fUsername, + Password: fPassword, + SenderAddress: fSenderAddress, + ReceiverAddress: fReceiverAddress, + MessageFormat: provider.MESSAGE_FORMAT_HTML, + }) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + res, err := provider.Notify(context.Background(), mockSubject, mockHtmlMessage) + if err != nil { + t.Errorf("err: %+v", err) + return + } + + t.Logf("ok: %v", res) + }) } diff --git a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderEmail.tsx b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderEmail.tsx index d1b5809e..7b343e71 100644 --- a/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderEmail.tsx +++ b/ui/src/components/workflow/designer/forms/BizNotifyNodeConfigFieldsProviderEmail.tsx @@ -1,5 +1,5 @@ import { getI18n, useTranslation } from "react-i18next"; -import { Form, Input } from "antd"; +import { Form, Input, Select } from "antd"; import { createSchemaFieldRule } from "antd-zod"; import { z } from "zod"; @@ -7,6 +7,9 @@ import { isEmail } from "@/utils/validator"; import { useFormNestedFieldsContext } from "./_context"; +const MESSAGE_FORMAT_PLAIN = "plain" as const; +const MESSAGE_FORMAT_HTML = "html" as const; + const BizNotifyNodeConfigFieldsProviderEmail = () => { const { i18n, t } = useTranslation(); @@ -19,6 +22,22 @@ const BizNotifyNodeConfigFieldsProviderEmail = () => { return ( <> + +