aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDean <gao.dean@hotmail.com>2023-01-11 09:37:48 +1100
committerRobin Jarry <robin@jarry.cc>2023-01-26 00:20:36 +0100
commit38d63cb7f78835e4f99573f09bea6d5441a15af3 (patch)
tree3494219f1466f8d5a802f551d61ef9543128cb2f
parent5dd7ea5d5979808484e0f5ab8f1d7aaf9f65f2f2 (diff)
downloadaerc-38d63cb7f78835e4f99573f09bea6d5441a15af3.tar.gz
aerc-38d63cb7f78835e4f99573f09bea6d5441a15af3.zip
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>
-rw-r--r--lib/xoauth2.go40
-rw-r--r--worker/imap/connect.go2
2 files changed, 40 insertions, 2 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)
diff --git a/worker/imap/connect.go b/worker/imap/connect.go
index 5be916e7..6f341753 100644
--- a/worker/imap/connect.go
+++ b/worker/imap/connect.go
@@ -82,7 +82,7 @@ func (w *IMAPWorker) connect() (*client.Client, error) {
}
} else if w.config.xoauth2.Enabled {
if err := w.config.xoauth2.Authenticate(
- username, password, c); err != nil {
+ username, password, w.worker.Name, c); err != nil {
return nil, err
}
} else if err := c.Login(username, password); err != nil {