1
0
Fork 0
mirror of https://git.sr.ht/~rjarry/aerc synced 2026-02-25 14:05:24 +01:00
aerc/lib/auth/token.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

64 lines
1.5 KiB
Go

package auth
import (
"context"
"fmt"
"os"
"path"
"git.sr.ht/~rjarry/aerc/lib/xdg"
"golang.org/x/oauth2"
)
func exchangeRefreshToken(o *oauth2.Config, refreshToken string) (*oauth2.Token, error) {
token := new(oauth2.Token)
token.RefreshToken = refreshToken
token.TokenType = "Bearer"
return o.TokenSource(context.TODO(), token).Token()
}
func tokenCachePath(account, mech string) string {
return xdg.CachePath("aerc", account+"-"+mech+".token")
}
func saveRefreshToken(refreshToken, account, mech string) error {
p := tokenCachePath(account, mech)
if err := os.MkdirAll(path.Dir(p), 0o700); err != nil {
return err
}
return os.WriteFile(p, []byte(refreshToken), 0o600)
}
func getRefreshToken(account, mech string) (string, error) {
buf, err := os.ReadFile(tokenCachePath(account, mech))
if err != nil {
return "", err
}
return string(buf), nil
}
func GetAccessToken(
o *oauth2.Config, account, mech, password string,
) (string, error) {
if o.Endpoint.TokenURL == "" {
return password, nil
}
usedCache := false
if r, err := getRefreshToken(account, mech); err == nil && len(r) > 0 {
password = string(r)
usedCache = true
}
token, err := exchangeRefreshToken(o, password)
if err != nil {
if usedCache {
return "", fmt.Errorf("%w: try deleting %s",
err, tokenCachePath(account, mech))
}
return "", err
}
if err := saveRefreshToken(token.RefreshToken, account, mech); err != nil {
return "", err
}
return token.AccessToken, nil
}