1
0
Fork 0
mirror of https://git.sr.ht/~rjarry/aerc synced 2026-02-25 05:35:24 +01:00
aerc/lib/auth/sasl.go
Robin Jarry 018e6f81f4 lib: consolidate SASL authentication code
SASL and OAuth2 authentication logic was duplicated across the IMAP
worker and the SMTP sender code. Both implementations handled XOAUTH2,
OAUTHBEARER, and token caching independently, making maintenance
difficult and bug fixes error-prone.

Move this shared logic into lib/auth where it can be reused by all
backends. The IMAP worker no longer needs its own OAuth structs and
configuration parsing since it can now rely on the common
implementation also used by the SMTP sender.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Reviewed-by: Simon Martin <simon@nasilyan.com>
2026-02-09 14:46:27 +01:00

76 lines
1.7 KiB
Go

package auth
import (
"fmt"
"net/url"
"slices"
"strings"
"github.com/emersion/go-sasl"
"golang.org/x/oauth2"
)
func ParseScheme(uri *url.URL) (protocol string, mech string, err error) {
protocol = ""
mech = "plain"
if uri.Scheme != "" {
parts := strings.Split(uri.Scheme, "+")
if len(parts) == 0 {
return "", "", fmt.Errorf("Unknown scheme %s", uri.Scheme)
}
protocol = parts[0]
parts = slices.Delete(parts, 0, 1)
i := slices.Index(parts, "insecure")
if i != -1 {
protocol += "+insecure"
parts = slices.Delete(parts, i, i+1)
}
if len(parts) > 0 {
mech = strings.Join(parts, "+")
}
}
return protocol, mech, nil
}
func NewSaslClient(mech string, uri *url.URL, acct string) (sasl.Client, error) {
var saslClient sasl.Client
user := uri.User.Username()
password, _ := uri.User.Password()
switch mech {
case "", "none":
saslClient = nil
case "login":
saslClient = sasl.NewLoginClient(user, password)
case "plain":
saslClient = sasl.NewPlainClient("", user, password)
case "oauthbearer", "xoauth2":
q := uri.Query()
o := oauth2.Config{
ClientID: q.Get("client_id"),
ClientSecret: q.Get("client_secret"),
Scopes: strings.Split(q.Get("scope"), " "),
Endpoint: oauth2.Endpoint{
TokenURL: q.Get("token_endpoint"),
},
}
password, err := GetAccessToken(&o, acct, mech, password)
if err != nil {
return nil, err
}
if mech == "xoauth2" {
saslClient = NewXoauth2Client(user, password)
} else {
saslClient = sasl.NewOAuthBearerClient(
&sasl.OAuthBearerOptions{
Username: uri.User.Username(),
Token: password,
},
)
}
default:
return nil, fmt.Errorf("Unsupported auth mechanism %q", mech)
}
return saslClient, nil
}