refactor: Add Outlook authentication support for sending emails

This commit is contained in:
Marvin Zhang
2024-07-26 18:15:15 +08:00
parent 0e2170f644
commit 8d5a0b0c0e
4 changed files with 98 additions and 2 deletions

View File

@@ -14,4 +14,7 @@ type NotificationChannelV2 struct {
WebhookUrl string `json:"webhook_url,omitempty" bson:"webhook_url,omitempty"` WebhookUrl string `json:"webhook_url,omitempty" bson:"webhook_url,omitempty"`
TelegramBotToken string `json:"telegram_bot_token,omitempty" bson:"telegram_bot_token,omitempty"` TelegramBotToken string `json:"telegram_bot_token,omitempty" bson:"telegram_bot_token,omitempty"`
TelegramChatId string `json:"telegram_chat_id,omitempty" bson:"telegram_chat_id,omitempty"` TelegramChatId string `json:"telegram_chat_id,omitempty" bson:"telegram_chat_id,omitempty"`
OutlookTenantId string `json:"outlook_tenant_id,omitempty" bson:"outlook_tenant_id,omitempty"`
OutlookClientId string `json:"outlook_client_id,omitempty" bson:"outlook_client_id,omitempty"`
OutlookClientSecret string `json:"outlook_client_secret,omitempty" bson:"outlook_client_secret,omitempty"`
} }

View File

@@ -13,6 +13,20 @@ import (
) )
func SendMail(s *models.NotificationSettingV2, ch *models.NotificationChannelV2, to, cc, bcc, title, content string) error { func SendMail(s *models.NotificationSettingV2, ch *models.NotificationChannelV2, to, cc, bcc, title, content string) error {
// compatibility for different providers
var auth *XOAuth2Auth
if ch.Provider == ChannelMailProviderOutlook {
token, err := getOutlookToken(ch.OutlookTenantId, ch.OutlookClientId, ch.OutlookClientSecret)
if err != nil {
log.Errorf("failed to get outlook token: %v", err)
return err
}
auth = &XOAuth2Auth{
Username: ch.SMTPUsername,
Token: token,
}
}
// config // config
smtpConfig := smtpAuthentication{ smtpConfig := smtpAuthentication{
Server: ch.SMTPServer, Server: ch.SMTPServer,
@@ -42,7 +56,7 @@ func SendMail(s *models.NotificationSettingV2, ch *models.NotificationChannelV2,
} }
// send the email // send the email
if err := send(smtpConfig, options, content, text); err != nil { if err := sendMail(smtpConfig, options, content, text, auth); err != nil {
log.Errorf("failed to send email: %v", err) log.Errorf("failed to send email: %v", err)
trace.PrintError(err) trace.PrintError(err)
return err return err
@@ -84,7 +98,7 @@ type sendOptions struct {
} }
// send email // send email
func send(smtpConfig smtpAuthentication, options sendOptions, htmlBody string, txtBody string) error { func sendMail(smtpConfig smtpAuthentication, options sendOptions, htmlBody string, txtBody string, auth *XOAuth2Auth) error {
if smtpConfig.Server == "" { if smtpConfig.Server == "" {
return errors.New("SMTP server config is empty") return errors.New("SMTP server config is empty")
} }
@@ -132,6 +146,9 @@ func send(smtpConfig smtpAuthentication, options sendOptions, htmlBody string, t
m.AddAlternative("text/html", htmlBody) m.AddAlternative("text/html", htmlBody)
d := gomail.NewDialer(smtpConfig.Server, smtpConfig.Port, smtpConfig.SMTPUser, smtpConfig.SMTPPassword) d := gomail.NewDialer(smtpConfig.Server, smtpConfig.Port, smtpConfig.SMTPUser, smtpConfig.SMTPPassword)
if auth != nil {
d.Auth = auth
}
return d.DialAndSend(m) return d.DialAndSend(m)
} }

View File

@@ -0,0 +1,55 @@
package notification
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func getOutlookToken(tenantID, clientID, clientSecret string) (string, error) {
url := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantID)
data := map[string]string{
"grant_type": "client_credentials",
"client_id": clientID,
"client_secret": clientSecret,
"scope": "https://outlook.office365.com/.default",
}
formData := ""
for key, value := range data {
if formData != "" {
formData += "&"
}
formData += fmt.Sprintf("%s=%s", key, value)
}
req, err := http.NewRequest("POST", url, bytes.NewBufferString(formData))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return "", err
}
if token, ok := result["access_token"].(string); ok {
return token, nil
}
return "", fmt.Errorf("no access token found")
}

View File

@@ -0,0 +1,21 @@
package notification
import (
"fmt"
"net/smtp"
)
type XOAuth2Auth struct {
Username, Token string
}
func (a *XOAuth2Auth) Start(_ *smtp.ServerInfo) (string, []byte, error) {
return "XOAUTH2", []byte("user=" + a.Username + "\x01auth=Bearer " + a.Token + "\x01\x01"), nil
}
func (a *XOAuth2Auth) Next(fromServer []byte, more bool) ([]byte, error) {
if more {
return nil, fmt.Errorf("unexpected server challenge: %s", fromServer)
}
return nil, nil
}