From fd0fc1f52fa1736e94a1ba6a2d4f7d896a4c81df Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Mon, 18 May 2020 15:07:54 -0400 Subject: [PATCH] API: send emails with details of recovered panics --- api/go.mod | 2 ++ api/go.sum | 4 +++ api/server.go | 98 +++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 3 deletions(-) diff --git a/api/go.mod b/api/go.mod index 83b5d40..2fe176c 100644 --- a/api/go.mod +++ b/api/go.mod @@ -14,6 +14,7 @@ require ( github.com/gorilla/websocket v1.4.2 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/lib/pq v1.3.0 + github.com/martinlindhe/base36 v1.0.0 // indirect github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 // indirect github.com/mitchellh/mapstructure v1.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -24,5 +25,6 @@ require ( github.com/vektah/gqlparser/v2 v2.0.1 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 golang.org/x/tools v0.0.0-20200513201620-d5fe73897c97 // indirect + gopkg.in/mail.v2 v2.3.1 // indirect gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/api/go.sum b/api/go.sum index 2b9f647..0ee686a 100644 --- a/api/go.sum +++ b/api/go.sum @@ -76,6 +76,8 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A= +github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg= github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 h1:p8zN0Xu28xyEkPpqLbFXAnjdgBVvTJCpfOtoDf/+/RQ= @@ -173,6 +175,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/api/server.go b/api/server.go index e01fd3a..ac64946 100644 --- a/api/server.go +++ b/api/server.go @@ -1,20 +1,30 @@ package main import ( + "bytes" + "context" + "crypto/rand" "database/sql" + "encoding/binary" + "fmt" "log" "net/http" + "net/mail" "os" + "runtime" "strconv" "time" "git.sr.ht/~sircmpwn/getopt" - "github.com/99designs/gqlgen/handler" + "github.com/99designs/gqlgen/graphql" "github.com/99designs/gqlgen/graphql/playground" + "github.com/99designs/gqlgen/handler" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" - _ "github.com/lib/pq" + "github.com/martinlindhe/base36" "github.com/vaughan0/go-ini" + _ "github.com/lib/pq" + gomail "gopkg.in/mail.v2" "git.sr.ht/~sircmpwn/git.sr.ht/api/auth" "git.sr.ht/~sircmpwn/git.sr.ht/api/crypto" @@ -100,7 +110,69 @@ func main() { srv := handler.GraphQL( api.NewExecutableSchema(gqlConfig), - handler.ComplexityLimit(complexity)) + handler.ComplexityLimit(complexity), + handler.RecoverFunc(func(ctx context.Context, origErr interface{}) error { + if _, ok := origErr.(error); !ok { + log.Printf("Unexpected error in recover: %v\n", origErr) + return fmt.Errorf("internal system error") + } + stack := make([]byte, 32768) // 32 KiB + i := runtime.Stack(stack, false) + log.Println(string(stack[:i])) + if debug { + return fmt.Errorf("internal system error") + } + + to, ok := config.Get("mail", "error-to") + if !ok { + return fmt.Errorf("internal system error") + } + from, _ := config.Get("mail", "error-from") + portStr, ok := config.Get("mail", "smtp-port") + if !ok { + return fmt.Errorf("internal system error") + } + port, _ := strconv.Atoi(portStr) + host, _ := config.Get("mail", "smtp-host") + user, _ := config.Get("mail", "smtp-user") + pass, _ := config.Get("mail", "smtp-password") + + m := gomail.NewMessage() + sender, err := mail.ParseAddress(from) + if err != nil { + log.Fatalf("Failed to parse sender address") + } + m.SetAddressHeader("From", sender.Address, sender.Name) + recipient, err := mail.ParseAddress(to) + if err != nil { + log.Fatalf("Failed to parse recipient address") + } + m.SetAddressHeader("To", recipient.Address, recipient.Name) + m.SetHeader("Message-ID", GenerateMessageID()) + m.SetHeader("Subject", fmt.Sprintf( + "[git.sr.ht] GraphQL query error: %v", origErr)) + + quser := auth.ForContext(ctx) + octx := graphql.GetOperationContext(ctx) + + m.SetBody("text/plain", fmt.Sprintf(`Error occured processing GraphQL request: + +%v + +When running the following query on behalf of %s <%s>: + +%s + +The following stack trace was produced: + +%s`, origErr, quser.Username, quser.Email, octx.RawQuery, string(stack[:i]))) + + d := gomail.NewDialer(host, port, user, pass) + if err := d.DialAndSend(m); err != nil { + log.Printf("Error sending email: %v\n", err) + } + return fmt.Errorf("internal system error") + })) router.Handle("/query", srv) @@ -111,3 +183,23 @@ func main() { log.Printf("running on %s", addr) log.Fatal(http.ListenAndServe(addr, router)) } + +// Generates an RFC 2822-compliant Message-Id based on the informational draft +// "Recommendations for generating Message IDs", for lack of a better +// authoritative source. +func GenerateMessageID() string { + var ( + now bytes.Buffer + nonce []byte = make([]byte, 8) + ) + binary.Write(&now, binary.BigEndian, time.Now().UnixNano()) + rand.Read(nonce) + hostname, err := os.Hostname() + if err != nil { + hostname = "localhost" + } + return fmt.Sprintf("<%s.%s@%s>", + base36.EncodeBytes(now.Bytes()), + base36.EncodeBytes(nonce), + hostname) +} -- 2.38.4