diff options
author | Dean <gao.dean@hotmail.com> | 2023-01-11 09:37:48 +1100 |
---|---|---|
committer | Robin Jarry <robin@jarry.cc> | 2023-01-26 00:20:36 +0100 |
commit | 38d63cb7f78835e4f99573f09bea6d5441a15af3 (patch) | |
tree | 3494219f1466f8d5a802f551d61ef9543128cb2f /lib/xoauth2.go | |
parent | 5dd7ea5d5979808484e0f5ab8f1d7aaf9f65f2f2 (diff) | |
download | aerc-38d63cb7f78835e4f99573f09bea6d5441a15af3.tar.gz |
imap,smtp: cache and cycle XOAUTH2 refresh token
Normally for emails with xoauth2, the help page says to pass the refresh
token as the password. When the refresh token expires, aerc can't fetch
the access token, and you must get a new refresh token from the external
script.
This patch implements a cycle of refresh tokens so you only need
to use an external script to fetch the refresh token once.
Once you have fetched the initial refresh token (with an external script
like mutt_xoauth2.py or https://github.com/gaoDean/oauthRefreshToken),
that refresh token is inputted as the password to aerc (as normal) to
fetch the access token. Before this patch aerc used to only fetch the
access token, but now it fetches that and a new refresh token, which it
caches in $XDG_CONFIG_HOME/aerc/<account>-xoauth2.token. In the next
opening of aerc, aerc will pull the refresh token from the cache, and
use it instead of the inputted refresh token from the password. If it is
not present in the cache, the refresh token is taken from the password
as usual.
Signed-off-by: Dean <gao.dean@hotmail.com>
Acked-by: Robin Jarry <robin@jarry.cc>
Diffstat (limited to 'lib/xoauth2.go')
-rw-r--r-- | lib/xoauth2.go | 40 |
1 files changed, 39 insertions, 1 deletions
diff --git a/lib/xoauth2.go b/lib/xoauth2.go index 83f06309..c0f654b8 100644 --- a/lib/xoauth2.go +++ b/lib/xoauth2.go @@ -12,9 +12,12 @@ import ( "context" "encoding/json" "fmt" + "os" + "path" "github.com/emersion/go-imap/client" "github.com/emersion/go-sasl" + "github.com/kyoh86/xdg" "golang.org/x/oauth2" ) @@ -69,17 +72,52 @@ func (c *Xoauth2) ExchangeRefreshToken(refreshToken string) (*oauth2.Token, erro return c.OAuth2.TokenSource(context.TODO(), token).Token() } -func (c *Xoauth2) Authenticate(username string, password string, client *client.Client) error { +func SaveRefreshToken(refreshToken string, acct string) error { + p := path.Join(xdg.CacheHome(), "aerc", acct+"-xoauth2.token") + if _, err := os.Stat(p); os.IsNotExist(err) { + _ = os.MkdirAll(path.Join(xdg.CacheHome(), "aerc"), 0o700) + } + + return os.WriteFile( + p, + []byte(refreshToken), + 0o600, + ) +} + +func GetRefreshToken(acct string) ([]byte, error) { + p := path.Join(xdg.CacheHome(), "aerc", acct+"-xoauth2.token") + return os.ReadFile(p) +} + +func (c *Xoauth2) Authenticate( + username string, + password string, + account string, + client *client.Client, +) error { if ok, err := client.SupportAuth("XOAUTH2"); err != nil || !ok { return fmt.Errorf("Xoauth2 not supported %w", err) } if c.OAuth2.Endpoint.TokenURL != "" { + usedCache := false + if r, err := GetRefreshToken(account); err == nil && len(r) > 0 { + password = string(r) + usedCache = true + } + token, err := c.ExchangeRefreshToken(password) if err != nil { + if usedCache { + return fmt.Errorf("try removing cached refresh token. %w", err) + } return err } password = token.AccessToken + if err := SaveRefreshToken(token.RefreshToken, account); err != nil { + return err + } } saslClient := NewXoauth2Client(username, password) |