diff --git a/cmd/recv/go.mod b/cmd/recv/go.mod index 0c40df7..a35f7b3 100644 --- a/cmd/recv/go.mod +++ b/cmd/recv/go.mod @@ -1,6 +1,6 @@ module gitea.inhome.blapointe.com/contact/cmd/recv -go 1.20 +go 1.24.0 replace gitea.inhome.blapointe.com/local-sandbox/contact => ../../ @@ -10,9 +10,12 @@ require ( ) require ( + cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e // indirect github.com/emersion/go-imap v1.2.0 // indirect - github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect + github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect + github.com/google/uuid v1.6.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/cmd/recv/go.sum b/cmd/recv/go.sum index e064062..e8089d7 100644 --- a/cmd/recv/go.sum +++ b/cmd/recv/go.sum @@ -1,3 +1,5 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= gitea.inhome.blapointe.com/local/args v0.0.0-20231109145953-eb2e1c1b8d56 h1:zTGGZ77KLFagqUvDSgTOnm0qF+iSLwQWiEtGjb2jjlY= gitea.inhome.blapointe.com/local/args v0.0.0-20231109145953-eb2e1c1b8d56/go.mod h1:SqCOE3bE3wvrztVIQGHuyxHKfDjRKU9EWhBdkmkiwyc= github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo= @@ -7,7 +9,13 @@ github.com/emersion/go-imap v1.2.0/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5N github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= +github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 h1:oP4q0fw+fOSWn3DfFi4EXdT+B+gTtzx8GC9xsc26Znk= +github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/cmd/recv/main.go b/cmd/recv/main.go index 84eb13b..1bea196 100755 --- a/cmd/recv/main.go +++ b/cmd/recv/main.go @@ -24,6 +24,7 @@ func main() { as.Append(args.STRING, "p", "password", emailer.Password) as.Append(args.INT, "b", "dont read more than this many characters", 4096) as.Append(args.BOOL, "json", "output as json", false) + as.Append(args.STRING, "oauth", "", emailer.OAuth) if err := as.Parse(); err != nil { panic(err) } @@ -33,6 +34,7 @@ func main() { emailer.Limit = as.Get("n").GetInt() emailer.From = as.GetString("u") emailer.Password = as.GetString("p") + emailer.OAuth = as.GetString("oauth") var msgs <-chan *mail.Message var err error diff --git a/email.go b/email.go index f6e119b..8e25f91 100755 --- a/email.go +++ b/email.go @@ -1,17 +1,26 @@ package contact import ( + "context" + "crypto/sha256" "crypto/tls" + "encoding/base64" + "encoding/json" "fmt" "log" "net/mail" "net/smtp" + "net/url" "os" "strings" "github.com/bytbox/go-pop3" "github.com/emersion/go-imap" "github.com/emersion/go-imap/client" + "github.com/emersion/go-sasl" + "github.com/google/uuid" + "golang.org/x/oauth2/authhandler" + "golang.org/x/oauth2/google" ) type Emailer struct { @@ -21,6 +30,7 @@ type Emailer struct { IMAP string Password string Limit int + OAuth string } func NewEmailer() *Emailer { @@ -34,6 +44,7 @@ func NewEmailer() *Emailer { From: envOr("FROM", "breellocaldev@gmail.com"), SMTP: envOr("SMTP", "smtp.gmail.com:465"), Password: envOr("PASSWORD", "lhnjijrvqaesiufp"), + OAuth: envOr("OAUTH", ""), } } @@ -42,12 +53,82 @@ func (e *Emailer) Read() (chan *mail.Message, error) { } func (e *Emailer) ReadIMAP() (chan *mail.Message, error) { + if e.OAuth != "" { + return e.oauthThenReadIMAP() + } + return e.readIMAP(e.From, e.Password) +} + +func (e *Emailer) oauthThenReadIMAP() (chan *mail.Message, error) { + configJSON, err := e.configJSON() + if err != nil { + return nil, err + } + + if token, _ := os.ReadFile(e.OAuth + ".token"); len(token) == 0 { + state := uuid.NewString() + + verifier := uuid.NewString() + s256 := sha256.Sum256([]byte(verifier)) + challenge := base64.RawURLEncoding.EncodeToString(s256[:]) + pkceParams := authhandler.PKCEParams{Challenge: challenge, ChallengeMethod: "S256", Verifier: verifier} + config, err := google.ConfigFromJSON([]byte(configJSON), "https://mail.google.com/") + if err != nil { + return nil, err + } + credentialsParams := google.CredentialsParams{Scopes: config.Scopes, State: state, AuthHandler: func(authCodeURL string) (string, string, error) { + fmt.Println(authCodeURL + "&access_type=offline&prompt=consent") + var callback string + _, err := fmt.Scan(&callback) + u, _ := url.Parse(callback) + fmt.Println() + return u.Query().Get("code"), state, err + }, PKCE: &pkceParams} + credentials, err := google.CredentialsFromJSONWithParams(context.Background(), []byte(configJSON), credentialsParams) + if err != nil { + return nil, err + } + + token, err := credentials.TokenSource.Token() + if err != nil { + return nil, err + } + tokenFileJSON, _ := json.Marshal(token) + os.WriteFile(e.OAuth+".token", tokenFileJSON, os.ModePerm) + log.Printf("%s", tokenFileJSON) + } + token, _ := os.ReadFile(e.OAuth + ".token") + var tokenStruct struct { + AccessToken string `json:"access_token"` + } + json.Unmarshal(token, &tokenStruct) + log.Println(e.From, tokenStruct.AccessToken) + return e.readIMAP(e.From, tokenStruct.AccessToken) +} + +func (e *Emailer) configJSON() (string, error) { + b, err := os.ReadFile(e.OAuth) + return string(b), err +} + +func (e *Emailer) readIMAP(authU, authP string) (chan *mail.Message, error) { c, err := client.DialTLS(e.IMAP, nil) if err != nil { return nil, err } - if err := c.Login(e.From, e.Password); err != nil { - return nil, err + if e.OAuth == "" { + if err := c.Login(authU, authP); err != nil { + return nil, err + } + } else { + if err := c.Authenticate(sasl.NewOAuthBearerClient(&sasl.OAuthBearerOptions{ + Username: authU, + Token: authP, + Host: "", + Port: 0, + })); err != nil { + return nil, err + } } mbox, err := c.Select("INBOX", true) //readonly if err != nil { diff --git a/go.mod b/go.mod index 35ac5e9..f686827 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,16 @@ module gitea.inhome.blapointe.com/local-sandbox/contact -go 1.17 +go 1.24.0 require ( github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e github.com/emersion/go-imap v1.2.0 + github.com/google/uuid v1.6.0 + golang.org/x/oauth2 v0.35.0 ) require ( + cloud.google.com/go/compute/metadata v0.3.0 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect golang.org/x/text v0.3.7 // indirect ) diff --git a/go.sum b/go.sum index d774484..11c877f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e h1:mQTN05gz0rDZSABqKMzAPMb5ATWcvvdMljRzEh0LjBo= github.com/bytbox/go-pop3 v0.0.0-20120201222208-3046caf0763e/go.mod h1:alXX+s7a4cKaIprgjeEboqi4Tm7XR/HXEwUTxUV/ywU= github.com/emersion/go-imap v1.2.0 h1:lyUQ3+EVM21/qbWE/4Ya5UG9r5+usDxlg4yfp3TgHFA= @@ -6,6 +8,10 @@ github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwd github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=