mirror of
https://git.sr.ht/~rjarry/aerc
synced 2026-02-25 14:05:24 +01:00
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>
64 lines
1.5 KiB
Go
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
|
|
}
|