Adding upstream version 0.8.9.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
3b2c48b5e4
commit
c0c4addb85
285 changed files with 25880 additions and 0 deletions
266
pkg/generators/xouath2/xoauth2.go
Normal file
266
pkg/generators/xouath2/xoauth2.go
Normal file
|
@ -0,0 +1,266 @@
|
|||
//go:generate stringer -type=URLPart -trimprefix URL
|
||||
|
||||
package xouath2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/services/smtp"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// SMTP port constants.
|
||||
const (
|
||||
DefaultSMTPPort uint16 = 25 // Standard SMTP port without encryption
|
||||
GmailSMTPPortStartTLS uint16 = 587 // Gmail SMTP port with STARTTLS
|
||||
)
|
||||
|
||||
const StateLength int = 16 // Length in bytes for OAuth 2.0 state randomness (128 bits)
|
||||
|
||||
// Errors.
|
||||
var (
|
||||
ErrReadFileFailed = errors.New("failed to read file")
|
||||
ErrUnmarshalFailed = errors.New("failed to unmarshal JSON")
|
||||
ErrScanFailed = errors.New("failed to scan input")
|
||||
ErrTokenExchangeFailed = errors.New("failed to exchange token")
|
||||
)
|
||||
|
||||
// Generator is the XOAuth2 Generator implementation.
|
||||
type Generator struct{}
|
||||
|
||||
// Generate generates a service URL from a set of user questions/answers.
|
||||
func (g *Generator) Generate(
|
||||
_ types.Service,
|
||||
props map[string]string,
|
||||
args []string,
|
||||
) (types.ServiceConfig, error) {
|
||||
if provider, found := props["provider"]; found {
|
||||
if provider == "gmail" {
|
||||
return oauth2GeneratorGmail(args[0])
|
||||
}
|
||||
}
|
||||
|
||||
if len(args) > 0 {
|
||||
return oauth2GeneratorFile(args[0])
|
||||
}
|
||||
|
||||
return oauth2Generator()
|
||||
}
|
||||
|
||||
func oauth2GeneratorFile(file string) (*smtp.Config, error) {
|
||||
jsonData, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", file, ErrReadFileFailed)
|
||||
}
|
||||
|
||||
var providerConfig struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
RedirectURL string `json:"redirect_url"`
|
||||
AuthURL string `json:"auth_url"`
|
||||
TokenURL string `json:"token_url"`
|
||||
Hostname string `json:"smtp_hostname"`
|
||||
Scopes []string `json:"scopes"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(jsonData, &providerConfig); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", file, ErrUnmarshalFailed)
|
||||
}
|
||||
|
||||
conf := oauth2.Config{
|
||||
ClientID: providerConfig.ClientID,
|
||||
ClientSecret: providerConfig.ClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: providerConfig.AuthURL,
|
||||
TokenURL: providerConfig.TokenURL,
|
||||
AuthStyle: oauth2.AuthStyleAutoDetect,
|
||||
},
|
||||
RedirectURL: providerConfig.RedirectURL,
|
||||
Scopes: providerConfig.Scopes,
|
||||
}
|
||||
|
||||
return generateOauth2Config(&conf, providerConfig.Hostname)
|
||||
}
|
||||
|
||||
func oauth2Generator() (*smtp.Config, error) {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
||||
var clientID string
|
||||
|
||||
fmt.Fprint(os.Stdout, "ClientID: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
clientID = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("clientID: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
var clientSecret string
|
||||
|
||||
fmt.Fprint(os.Stdout, "ClientSecret: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
clientSecret = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("clientSecret: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
var authURL string
|
||||
|
||||
fmt.Fprint(os.Stdout, "AuthURL: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
authURL = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("authURL: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
var tokenURL string
|
||||
|
||||
fmt.Fprint(os.Stdout, "TokenURL: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
tokenURL = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("tokenURL: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
var redirectURL string
|
||||
|
||||
fmt.Fprint(os.Stdout, "RedirectURL: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
redirectURL = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("redirectURL: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
var scopes string
|
||||
|
||||
fmt.Fprint(os.Stdout, "Scopes: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
scopes = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("scopes: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
var hostname string
|
||||
|
||||
fmt.Fprint(os.Stdout, "SMTP Hostname: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
hostname = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("hostname: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
conf := oauth2.Config{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: authURL,
|
||||
TokenURL: tokenURL,
|
||||
AuthStyle: oauth2.AuthStyleAutoDetect,
|
||||
},
|
||||
RedirectURL: redirectURL,
|
||||
Scopes: strings.Split(scopes, ","),
|
||||
}
|
||||
|
||||
return generateOauth2Config(&conf, hostname)
|
||||
}
|
||||
|
||||
func oauth2GeneratorGmail(credFile string) (*smtp.Config, error) {
|
||||
data, err := os.ReadFile(credFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", credFile, ErrReadFileFailed)
|
||||
}
|
||||
|
||||
conf, err := google.ConfigFromJSON(data, "https://mail.google.com/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"%s: %w",
|
||||
credFile,
|
||||
err,
|
||||
) // google.ConfigFromJSON error doesn't need custom wrapping
|
||||
}
|
||||
|
||||
return generateOauth2Config(conf, "smtp.gmail.com")
|
||||
}
|
||||
|
||||
func generateOauth2Config(conf *oauth2.Config, host string) (*smtp.Config, error) {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
||||
// Generate a random state value
|
||||
stateBytes := make([]byte, StateLength)
|
||||
if _, err := rand.Read(stateBytes); err != nil {
|
||||
return nil, fmt.Errorf("generating random state: %w", err)
|
||||
}
|
||||
|
||||
state := base64.URLEncoding.EncodeToString(stateBytes)
|
||||
|
||||
fmt.Fprintf(
|
||||
os.Stdout,
|
||||
"Visit the following URL to authenticate:\n%s\n\n",
|
||||
conf.AuthCodeURL(state),
|
||||
)
|
||||
|
||||
var verCode string
|
||||
|
||||
fmt.Fprint(os.Stdout, "Enter verification code: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
verCode = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("verification code: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
token, err := conf.Exchange(ctx, verCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", verCode, ErrTokenExchangeFailed)
|
||||
}
|
||||
|
||||
var sender string
|
||||
|
||||
fmt.Fprint(os.Stdout, "Enter sender e-mail: ")
|
||||
|
||||
if scanner.Scan() {
|
||||
sender = scanner.Text()
|
||||
} else {
|
||||
return nil, fmt.Errorf("sender email: %w", ErrScanFailed)
|
||||
}
|
||||
|
||||
// Determine the appropriate port based on the host
|
||||
port := DefaultSMTPPort
|
||||
if host == "smtp.gmail.com" {
|
||||
port = GmailSMTPPortStartTLS // Use 587 for Gmail with STARTTLS
|
||||
}
|
||||
|
||||
svcConf := &smtp.Config{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Username: sender,
|
||||
Password: token.AccessToken,
|
||||
FromAddress: sender,
|
||||
FromName: "Shoutrrr",
|
||||
ToAddresses: []string{sender},
|
||||
Auth: smtp.AuthTypes.OAuth2,
|
||||
UseStartTLS: true,
|
||||
UseHTML: true,
|
||||
}
|
||||
|
||||
return svcConf, nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue