mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-27 01:32:15 +00:00
# Which Problems Are Solved - Some mail providers (e.g.: MS Exchange) have deprecated plain auth # How the Problems Are Solved Adds XOauth2 auth option to SMPT - SMTP config now can contain XOAuth2 auth config - Proto files are updated to have a oneof for configuration selection. Old fields for configuring plain auth are still available not to create a breaking change. These can be removed in the future. - Columns are added to the database to persist the config # Additional Changes none # Additional Context Replace this example with links to related issues, discussions, discord threads, or other sources with more context. Use the Closing #issue syntax for issues that are resolved with this PR. - Closes #8042 --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: Tim Möhlmann <[email protected]>
97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
package smtp
|
|
|
|
import (
|
|
"context"
|
|
"hash/fnv"
|
|
"net/smtp"
|
|
|
|
lru "github.com/hashicorp/golang-lru/v2"
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/oauth2/clientcredentials"
|
|
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
var tokenSourceCache *lru.TwoQueueCache[string, oauth2.TokenSource]
|
|
|
|
func init() {
|
|
var err error
|
|
tokenSourceCache, err = lru.New2Q[string, oauth2.TokenSource](100)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
type XOAuth2AuthConfig struct {
|
|
User string
|
|
TokenEndpoint string
|
|
Scopes []string
|
|
ClientCredentialsAuth *OAuth2ClientCredentials
|
|
}
|
|
|
|
type OAuth2ClientCredentials struct {
|
|
ClientId string
|
|
ClientSecret string
|
|
}
|
|
|
|
func (cfg XOAuth2AuthConfig) Hash() string {
|
|
hash := fnv.New128()
|
|
hash.Write([]byte(cfg.User))
|
|
hash.Write([]byte(cfg.TokenEndpoint))
|
|
for _, s := range cfg.Scopes {
|
|
hash.Write([]byte(s))
|
|
}
|
|
if cfg.ClientCredentialsAuth != nil {
|
|
hash.Write([]byte(cfg.ClientCredentialsAuth.ClientId))
|
|
hash.Write([]byte(cfg.ClientCredentialsAuth.ClientSecret))
|
|
}
|
|
return string(hash.Sum(nil))
|
|
}
|
|
|
|
type xoauth2Auth struct {
|
|
host string
|
|
config XOAuth2AuthConfig
|
|
}
|
|
|
|
func XOAuth2Auth(config XOAuth2AuthConfig, host string) smtp.Auth {
|
|
return &xoauth2Auth{
|
|
host: host,
|
|
config: config,
|
|
}
|
|
}
|
|
func (a *xoauth2Auth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
|
if server.Name != a.host {
|
|
return "", nil, zerrors.ThrowInternal(nil, "SMTP-eRJLyi", "wrong host name")
|
|
}
|
|
|
|
hash := a.config.Hash()
|
|
tokenSource, ok := tokenSourceCache.Get(hash)
|
|
if !ok {
|
|
if a.config.ClientCredentialsAuth != nil {
|
|
config := &clientcredentials.Config{
|
|
ClientID: a.config.ClientCredentialsAuth.ClientId,
|
|
ClientSecret: a.config.ClientCredentialsAuth.ClientSecret,
|
|
Scopes: a.config.Scopes,
|
|
TokenURL: a.config.TokenEndpoint,
|
|
}
|
|
tokenSource = config.TokenSource(context.Background())
|
|
tokenSourceCache.Add(hash, tokenSource)
|
|
}
|
|
}
|
|
|
|
token, err := tokenSource.Token()
|
|
if err != nil {
|
|
return "", nil, zerrors.ThrowInternal(err, "SMTP-fjHcJu", "Failed to get token to connect with smtp server")
|
|
}
|
|
|
|
resp := []byte("user=" + a.config.User + "\x01" + "auth=Bearer " + token.AccessToken + "\x01\x01")
|
|
return "XOAUTH2", resp, nil
|
|
}
|
|
|
|
func (a *xoauth2Auth) Next(_ []byte, more bool) ([]byte, error) {
|
|
if !more {
|
|
return nil, nil
|
|
}
|
|
return nil, zerrors.ThrowInternal(nil, "SMTP-Pqsrj9", "unexpected server challenge for XOAUTH2 auth method")
|
|
}
|