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
This commit is contained in:
Jarno Rankinen 2024-03-27 06:43:02 +00:00
parent 65a6020623
commit 8a6a9e9380
2 changed files with 97 additions and 68 deletions

View File

@ -5,6 +5,10 @@ import (
"crypto/tls" "crypto/tls"
"flag" "flag"
"fmt" "fmt"
"log"
"os"
"time"
"github.com/0ranki/hydroxide-push/auth" "github.com/0ranki/hydroxide-push/auth"
"github.com/0ranki/hydroxide-push/config" "github.com/0ranki/hydroxide-push/config"
"github.com/0ranki/hydroxide-push/events" "github.com/0ranki/hydroxide-push/events"
@ -13,9 +17,6 @@ import (
"github.com/0ranki/hydroxide-push/protonmail" "github.com/0ranki/hydroxide-push/protonmail"
imapserver "github.com/emersion/go-imap/server" imapserver "github.com/emersion/go-imap/server"
"golang.org/x/term" "golang.org/x/term"
"log"
"os"
"time"
) )
const ( const (
@ -57,7 +58,7 @@ func askPass(prompt string) ([]byte, error) {
return b, err 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) be := imapbackend.New(authManager, eventsManager)
s := imapserver.New(be) s := imapserver.New(be)
s.Addr = addr s.Addr = addr
@ -71,39 +72,6 @@ func listenEventsAndNotify(addr string, debug bool, authManager *auth.Manager, e
for { for {
time.Sleep(10 * time.Second) 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...] <command> const usage = `usage: hydroxide-push [options...] <command>
@ -146,6 +114,11 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
err = cfg.Read()
if err != nil {
fmt.Println(err)
}
cmd := flag.Arg(0) cmd := flag.Arg(0)
switch cmd { switch cmd {
case "auth": case "auth":
@ -244,14 +217,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
cfg.BridgePw = bridgePassword cfg.BridgePw = bridgePassword
reply, err := ntfy.AskToSaveBridgePw(&cfg) cfg.Setup()
if err != nil {
log.Fatal(err)
}
if reply != "yes" {
fmt.Println("Bridge password:", bridgePassword)
}
setupNtfy()
case "status": case "status":
usernames, err := auth.ListUsernames() usernames, err := auth.ListUsernames()
if err != nil { if err != nil {
@ -268,11 +234,11 @@ func main() {
} }
case "setup-ntfy": case "setup-ntfy":
setupNtfy() cfg.Setup()
case "notify": case "notify":
authManager := auth.NewManager(newClient) authManager := auth.NewManager(newClient)
eventsManager := events.NewManager() eventsManager := events.NewManager()
log.Fatal(listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig)) listenEventsAndNotify("0", debug, authManager, eventsManager, tlsConfig)
default: default:
fmt.Print(usage) fmt.Print(usage)

View File

@ -2,12 +2,14 @@ package ntfy
import ( import (
"bufio" "bufio"
"crypto/rand"
"encoding/base64"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"log" "log"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"strings" "strings"
@ -23,7 +25,22 @@ type NtfyConfig struct {
BridgePw string `json:"bridgePw"` 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) return fmt.Sprintf("%s/%s", cfg.URL, cfg.Topic)
} }
@ -49,39 +66,38 @@ func Notify() {
log.Printf("error reading notification: %v", err) log.Printf("error reading notification: %v", err)
return 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("Title", "ProtoMail")
req.Header.Set("Click", "dismiss") req.Header.Set("Click", "dismiss")
req.Header.Set("Tags", "envelope") 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 { func (cfg *NtfyConfig) Read() error {
f, err := ntfyConfigFile() f, err := ntfyConfigFile()
if err == nil { if err == nil {
b, err := os.ReadFile(f) b, err := os.ReadFile(f)
if err == nil { if err == nil {
err = json.Unmarshal(b, &cfg) err = json.Unmarshal(b, &cfg)
} else if strings.HasSuffix(err.Error(), "no such file or directory") {
cfg.Init()
err = cfg.Save()
} }
if err != nil { if err != nil {
return err log.Fatal(err)
} }
cfg.Init()
} }
return nil 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 { func LoginBridge(cfg *NtfyConfig) error {
if cfg.BridgePw == "" { if cfg.BridgePw == "" {
cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD") cfg.BridgePw = os.Getenv("HYDROXIDE_BRIDGE_PASSWORD")
@ -91,10 +107,7 @@ func LoginBridge(cfg *NtfyConfig) error {
fmt.Printf("Bridge password: ") fmt.Printf("Bridge password: ")
scanner.Scan() scanner.Scan()
cfg.BridgePw = scanner.Text() cfg.BridgePw = scanner.Text()
_, err := AskToSaveBridgePw(cfg)
if err != nil {
return err
}
} }
return nil return nil
} }
@ -133,3 +146,53 @@ func Login(cfg *NtfyConfig, be backend.Backend) {
log.Fatal(err) 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")
}