From 8a6a9e9380828ce85c3942945ea796891d6ac08a Mon Sep 17 00:00:00 2001 From: Jarno Rankinen Date: Wed, 27 Mar 2024 06:43:02 +0000 Subject: [PATCH] Login flow fixes - Validate push URL - Default values for push URL and topic if not set - setup-ntfy uses already set or default values if input is empty --- cmd/hydroxide-push/main.go | 60 +++++---------------- ntfy/ntfy.go | 105 +++++++++++++++++++++++++++++-------- 2 files changed, 97 insertions(+), 68 deletions(-) diff --git a/cmd/hydroxide-push/main.go b/cmd/hydroxide-push/main.go index c36ff73..2c4b81f 100644 --- a/cmd/hydroxide-push/main.go +++ b/cmd/hydroxide-push/main.go @@ -5,6 +5,10 @@ import ( "crypto/tls" "flag" "fmt" + "log" + "os" + "time" + "github.com/0ranki/hydroxide-push/auth" "github.com/0ranki/hydroxide-push/config" "github.com/0ranki/hydroxide-push/events" @@ -13,9 +17,6 @@ import ( "github.com/0ranki/hydroxide-push/protonmail" imapserver "github.com/emersion/go-imap/server" "golang.org/x/term" - "log" - "os" - "time" ) const ( @@ -57,7 +58,7 @@ func askPass(prompt string) ([]byte, error) { return b, err } -func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, eventsManager *events.Manager, tlsConfig *tls.Config) error { +func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, eventsManager *events.Manager, tlsConfig *tls.Config) { be := imapbackend.New(authManager, eventsManager) s := imapserver.New(be) s.Addr = addr @@ -71,39 +72,6 @@ func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, e for { time.Sleep(10 * time.Second) } - return nil -} - -func setupNtfy() { - err := cfg.Read() - if err != nil { - fmt.Println(err) - } - var n string - if cfg.URL != "" && cfg.Topic != "" { - fmt.Printf("Current push endpoint: %s\n", cfg.String()) - n = "new " - } - scanner := bufio.NewScanner(os.Stdin) - fmt.Printf("Input %spush server URL (e.g. 'http://ntfy.sh') : ", n) - scanner.Scan() - cfg.URL = scanner.Text() - scanner = bufio.NewScanner(os.Stdin) - fmt.Printf("Input push topic (e.g. my-proton-notifications): ") - scanner.Scan() - cfg.Topic = scanner.Text() - fmt.Printf("Using URL %s\n", cfg.String()) - err = cfg.Save() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - err = ntfy.LoginBridge(&cfg) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Println("Notification configuration saved") } const usage = `usage: hydroxide-push [options...] @@ -146,6 +114,11 @@ func main() { log.Fatal(err) } + err = cfg.Read() + if err != nil { + fmt.Println(err) + } + cmd := flag.Arg(0) switch cmd { case "auth": @@ -244,14 +217,7 @@ func main() { log.Fatal(err) } cfg.BridgePw = bridgePassword - reply, err := ntfy.AskToSaveBridgePw(&cfg) - if err != nil { - log.Fatal(err) - } - if reply != "yes" { - fmt.Println("Bridge password:", bridgePassword) - } - setupNtfy() + cfg.Setup() case "status": usernames, err := auth.ListUsernames() if err != nil { @@ -268,11 +234,11 @@ func main() { } case "setup-ntfy": - setupNtfy() + cfg.Setup() case "notify": authManager := auth.NewManager(newClient) eventsManager := events.NewManager() - log.Fatal(listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig)) + listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig) default: fmt.Print(usage) diff --git a/ntfy/ntfy.go b/ntfy/ntfy.go index 611ef12..6e6db76 100644 --- a/ntfy/ntfy.go +++ b/ntfy/ntfy.go @@ -2,12 +2,14 @@ package ntfy import ( "bufio" + "crypto/rand" + "encoding/base64" "encoding/json" - "errors" "fmt" "log" "net" "net/http" + "net/url" "os" "strings" @@ -23,7 +25,22 @@ type NtfyConfig struct { BridgePw string `json:"bridgePw"` } -func (cfg *NtfyConfig) String() string { +func (cfg *NtfyConfig) Init() { + if cfg.Topic == "" { + r := make([]byte, 12) + _, err := rand.Read(r) + if err != nil { + log.Fatal(err) + } + cfg.Topic = strings.Replace(base64.StdEncoding.EncodeToString(r), "/", "+", -1) + + } + if cfg.URL == "" { + cfg.URL = "http://ntfy.sh" + } +} + +func (cfg *NtfyConfig) URI() string { return fmt.Sprintf("%s/%s", cfg.URL, cfg.Topic) } @@ -49,39 +66,38 @@ func Notify() { log.Printf("error reading notification: %v", err) return } - req, _ := http.NewRequest("POST", cfg.String(), strings.NewReader("New message received")) + req, _ := http.NewRequest("POST", cfg.URI(), strings.NewReader("New message received")) req.Header.Set("Title", "ProtoMail") req.Header.Set("Click", "dismiss") req.Header.Set("Tags", "envelope") - http.DefaultClient.Do(req) + if _, err := http.DefaultClient.Do(req); err != nil { + log.Printf("failed to publish to push topic: %v", err) + return + } + log.Printf("Push event sent") + } +// Read reads the configuration from file. Creates the file +// if it does not exist func (cfg *NtfyConfig) Read() error { f, err := ntfyConfigFile() if err == nil { b, err := os.ReadFile(f) if err == nil { err = json.Unmarshal(b, &cfg) + } else if strings.HasSuffix(err.Error(), "no such file or directory") { + cfg.Init() + err = cfg.Save() } if err != nil { - return err + log.Fatal(err) } + cfg.Init() } return nil } -func AskToSaveBridgePw(cfg *NtfyConfig) (string, error) { - scanner := bufio.NewScanner(os.Stdin) - //fmt.Printf("Save bridge password to config?\nThe password is stored in plain text, but (yes/n): ") - //scanner.Scan() - //if scanner.Text() == "yes" { - if err := cfg.Save(); err != nil { - return "", errors.New("failed to save notification config") - } - //} - return scanner.Text(), nil -} - func LoginBridge(cfg *NtfyConfig) error { if cfg.BridgePw == "" { cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD") @@ -91,10 +107,7 @@ func LoginBridge(cfg *NtfyConfig) error { fmt.Printf("Bridge password: ") scanner.Scan() cfg.BridgePw = scanner.Text() - _, err := AskToSaveBridgePw(cfg) - if err != nil { - return err - } + } return nil } @@ -133,3 +146,53 @@ func Login(cfg *NtfyConfig, be backend.Backend) { log.Fatal(err) } } + +func (cfg *NtfyConfig) Setup() { + var n string + if cfg.URL != "" && cfg.Topic != "" { + fmt.Printf("Current push endpoint: %s\n", cfg.URI()) + n = "new " + } + // Read push base URL + notValid := true + scanner := bufio.NewScanner(os.Stdin) + for notValid { + tmpURL := cfg.URL + fmt.Printf("Input %spush server URL ('%s') : ", n, cfg.URL) + scanner.Scan() + if len(scanner.Text()) > 0 { + tmpURL = scanner.Text() + } + if _, err := url.ParseRequestURI(tmpURL); err != nil { + fmt.Printf("Not a valid URL: %s\n", tmpURL) + } else { + notValid = false + cfg.URL = tmpURL + } + } + scanner = bufio.NewScanner(os.Stdin) + // Read push topic + fmt.Printf("Input push topic ('%s'): ", cfg.Topic) + scanner.Scan() + if len(scanner.Text()) > 0 { + cfg.Topic = scanner.Text() + } + fmt.Printf("Using URL %s\n", cfg.URI()) + // Save bridge password + if len(cfg.BridgePw) == 0 { + err := LoginBridge(cfg) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + } else { + fmt.Println("Bridge password is set") + } + // Save configuration + err := cfg.Save() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println("Notification configuration saved") +}