M api/go.mod => api/go.mod +2 -2
@@ 14,7 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/martinlindhe/base36 v1.0.0
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
@@ 25,6 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/mail.v2 v2.3.1
gopkg.in/yaml.v2 v2.3.0 // indirect
)
A api/graph/recover.go => api/graph/recover.go +127 -0
@@ 0,0 1,127 @@
+package graph
+
+import (
+ "bytes"
+ "context"
+ "crypto/rand"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "log"
+ "net/mail"
+ "os"
+ "runtime"
+ "strconv"
+ "time"
+
+ "github.com/99designs/gqlgen/graphql"
+ "github.com/martinlindhe/base36"
+ "github.com/vaughan0/go-ini"
+ gomail "gopkg.in/mail.v2"
+
+ "git.sr.ht/~sircmpwn/git.sr.ht/api/auth"
+)
+
+// Provides a graphql.RecoverFunc which will print the stack trace, and if
+// debug mode is not enabled, email it to the administrator.
+func EmailRecover(config ini.File, debug bool) graphql.RecoverFunc {
+ return func (ctx context.Context, _origErr interface{}) error {
+ var (
+ ok bool
+ origErr error
+ )
+ if origErr, ok = _origErr.(error); !ok {
+ log.Printf("Unexpected error in recover: %v\n", origErr)
+ return fmt.Errorf("internal system error")
+ }
+
+ if errors.Is(origErr, context.Canceled) {
+ return origErr
+ }
+
+ if errors.Is(origErr, context.DeadlineExceeded) {
+ return origErr
+ }
+
+ if origErr.Error() == "pq: canceling statement due to user request" {
+ return origErr
+ }
+
+ 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")
+ }
+}
+
+// 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)
+}
M api/server.go => api/server.go +1 -107
@@ 1,31 1,20 @@
package main
import (
- "bytes"
- "context"
- "crypto/rand"
"database/sql"
- "encoding/binary"
- "errors"
- "fmt"
"log"
"net/http"
- "net/mail"
"os"
- "runtime"
"strconv"
"time"
"git.sr.ht/~sircmpwn/getopt"
- "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/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"
@@ 112,82 101,7 @@ func main() {
srv := handler.GraphQL(
api.NewExecutableSchema(gqlConfig),
handler.ComplexityLimit(complexity),
- handler.RecoverFunc(func(ctx context.Context, _origErr interface{}) error {
- var origErr error
- if origErr, ok = _origErr.(error); !ok {
- log.Printf("Unexpected error in recover: %v\n", origErr)
- return fmt.Errorf("internal system error")
- }
-
- if errors.Is(origErr, context.Canceled) {
- return origErr
- }
-
- if errors.Is(origErr, context.DeadlineExceeded) {
- return origErr
- }
-
- if origErr.Error() == "pq: canceling statement due to user request" {
- return origErr
- }
-
- 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")
- }))
+ handler.RecoverFunc(graph.EmailRecover(config, debug)))
router.Handle("/query", srv)
@@ 198,23 112,3 @@ The following stack trace was produced:
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)
-}