From 0cf6a5f5de0cab08efef35c0235b8c7f1bc51f77 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Mon, 1 Aug 2022 10:56:08 +0000 Subject: [PATCH 1/6] Add http section to local usage config --- components/usage/config.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/usage/config.json b/components/usage/config.json index 3afe1c7c648de3..ff036bb180539d 100644 --- a/components/usage/config.json +++ b/components/usage/config.json @@ -8,6 +8,9 @@ "services": { "grpc": { "address": ":9001" + }, + "http": { + "address": ":9002" } } } From 14a160791ba700e2202b4a365c07374fce5bff98 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Mon, 1 Aug 2022 14:58:53 +0000 Subject: [PATCH 2/6] Add placeholder webhook handler Register the handler with the underlying baseserver. --- components/usage/pkg/server/server.go | 7 ++++ components/usage/pkg/stripe/webhook.go | 45 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 components/usage/pkg/stripe/webhook.go diff --git a/components/usage/pkg/server/server.go b/components/usage/pkg/server/server.go index 3d75723551c9b8..6c018be6565c33 100644 --- a/components/usage/pkg/server/server.go +++ b/components/usage/pkg/server/server.go @@ -111,6 +111,9 @@ func Start(cfg Config) error { return fmt.Errorf("failed to register gRPC services: %w", err) } + h := stripe.NewWebhookHandler() + registerHttpHandlers(srv, h) + err = controller.RegisterMetrics(srv.MetricsRegistry()) if err != nil { return fmt.Errorf("failed to register controller metrics: %w", err) @@ -133,3 +136,7 @@ func registerGRPCServices(srv *baseserver.Server, conn *gorm.DB, stripeClient *s } return nil } + +func registerHttpHandlers(srv *baseserver.Server, h *stripe.WebhookHandler) { + srv.HTTPMux().HandleFunc("/webhook", h.Handle) +} diff --git a/components/usage/pkg/stripe/webhook.go b/components/usage/pkg/stripe/webhook.go new file mode 100644 index 00000000000000..0402788b84e9ba --- /dev/null +++ b/components/usage/pkg/stripe/webhook.go @@ -0,0 +1,45 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package stripe + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gitpod-io/gitpod/common-go/log" + "github.com/stripe/stripe-go/v72" +) + +type WebhookHandler struct{} + +func NewWebhookHandler() *WebhookHandler { + return &WebhookHandler{} +} + +func (h *WebhookHandler) Handle(w http.ResponseWriter, req *http.Request) { + const maxBodyBytes = int64(65536) + + req.Body = http.MaxBytesReader(w, req.Body, maxBodyBytes) + payload, err := ioutil.ReadAll(req.Body) + if err != nil { + log.WithError(err).Error("Stripe webhook error when reading request body") + w.WriteHeader(http.StatusServiceUnavailable) + return + } + + event := stripe.Event{} + if err := json.Unmarshal(payload, &event); err != nil { + log.WithError(err).Error("Stripe webhook error while parsing event payload") + w.WriteHeader(http.StatusBadRequest) + return + } + + // TODO: verify webhook signature. + // Conditional on there being a secret configured. + + fmt.Fprintf(w, "event type: %s", event.Type) +} From b0d3d891b0472dd2cced1203ab2a316d2a9df80d Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Tue, 2 Aug 2022 05:50:38 +0000 Subject: [PATCH 3/6] Change webhook pattern Give it a more descriptive name. --- components/usage/pkg/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/usage/pkg/server/server.go b/components/usage/pkg/server/server.go index 6c018be6565c33..f2a14ef9bddcaa 100644 --- a/components/usage/pkg/server/server.go +++ b/components/usage/pkg/server/server.go @@ -138,5 +138,5 @@ func registerGRPCServices(srv *baseserver.Server, conn *gorm.DB, stripeClient *s } func registerHttpHandlers(srv *baseserver.Server, h *stripe.WebhookHandler) { - srv.HTTPMux().HandleFunc("/webhook", h.Handle) + srv.HTTPMux().HandleFunc("/stripe/invoices/webhook", h.Handle) } From 766430278b34836e4b15e7670688301e6d5c29c8 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Tue, 2 Aug 2022 06:00:54 +0000 Subject: [PATCH 4/6] Simplify json decoding of request body --- components/usage/pkg/stripe/webhook.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/components/usage/pkg/stripe/webhook.go b/components/usage/pkg/stripe/webhook.go index 0402788b84e9ba..0fb7ce7f454275 100644 --- a/components/usage/pkg/stripe/webhook.go +++ b/components/usage/pkg/stripe/webhook.go @@ -7,7 +7,6 @@ package stripe import ( "encoding/json" "fmt" - "io/ioutil" "net/http" "github.com/gitpod-io/gitpod/common-go/log" @@ -24,15 +23,10 @@ func (h *WebhookHandler) Handle(w http.ResponseWriter, req *http.Request) { const maxBodyBytes = int64(65536) req.Body = http.MaxBytesReader(w, req.Body, maxBodyBytes) - payload, err := ioutil.ReadAll(req.Body) - if err != nil { - log.WithError(err).Error("Stripe webhook error when reading request body") - w.WriteHeader(http.StatusServiceUnavailable) - return - } event := stripe.Event{} - if err := json.Unmarshal(payload, &event); err != nil { + err := json.NewDecoder(req.Body).Decode(&event) + if err != nil { log.WithError(err).Error("Stripe webhook error while parsing event payload") w.WriteHeader(http.StatusBadRequest) return From bd7927e5bcfabc1ea5b8fde29f9e29ca18abe09a Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Tue, 2 Aug 2022 06:32:24 +0000 Subject: [PATCH 5/6] go get github.com/gorilla/handlers --- components/usage/go.mod | 2 ++ components/usage/go.sum | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/components/usage/go.mod b/components/usage/go.mod index a7feff0f6c341d..67ffc3ff28901f 100644 --- a/components/usage/go.mod +++ b/components/usage/go.mod @@ -27,7 +27,9 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/felixge/httpsnoop v1.0.1 // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/gorilla/handlers v1.5.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/hashicorp/golang-lru v0.5.1 // indirect diff --git a/components/usage/go.sum b/components/usage/go.sum index f63492d75bb309..6fae7ef405d2d9 100644 --- a/components/usage/go.sum +++ b/components/usage/go.sum @@ -78,6 +78,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -157,6 +159,8 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= From 354cb835170e59e0f52cbf0c79fc4521c9338542 Mon Sep 17 00:00:00 2001 From: Andrew Farries Date: Tue, 2 Aug 2022 06:43:36 +0000 Subject: [PATCH 6/6] Use content type middleware To ensure requests have `application/json` `Content-Type` header set. --- components/usage/pkg/server/server.go | 3 ++- components/usage/pkg/stripe/webhook.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/usage/pkg/server/server.go b/components/usage/pkg/server/server.go index f2a14ef9bddcaa..0cad678a80473f 100644 --- a/components/usage/pkg/server/server.go +++ b/components/usage/pkg/server/server.go @@ -21,6 +21,7 @@ import ( "github.com/gitpod-io/gitpod/usage/pkg/controller" "github.com/gitpod-io/gitpod/usage/pkg/db" "github.com/gitpod-io/gitpod/usage/pkg/stripe" + "github.com/gorilla/handlers" "gorm.io/gorm" ) @@ -138,5 +139,5 @@ func registerGRPCServices(srv *baseserver.Server, conn *gorm.DB, stripeClient *s } func registerHttpHandlers(srv *baseserver.Server, h *stripe.WebhookHandler) { - srv.HTTPMux().HandleFunc("/stripe/invoices/webhook", h.Handle) + srv.HTTPMux().Handle("/stripe/invoices/webhook", handlers.ContentTypeHandler(h, "application/json")) } diff --git a/components/usage/pkg/stripe/webhook.go b/components/usage/pkg/stripe/webhook.go index 0fb7ce7f454275..065213da65a305 100644 --- a/components/usage/pkg/stripe/webhook.go +++ b/components/usage/pkg/stripe/webhook.go @@ -19,7 +19,7 @@ func NewWebhookHandler() *WebhookHandler { return &WebhookHandler{} } -func (h *WebhookHandler) Handle(w http.ResponseWriter, req *http.Request) { +func (h *WebhookHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { const maxBodyBytes = int64(65536) req.Body = http.MaxBytesReader(w, req.Body, maxBodyBytes)