From a9b13a51adb28b0186fe579fe551b0dd19379902 Mon Sep 17 00:00:00 2001 From: Marc Szanto <11840265+Xemdo@users.noreply.github.com> Date: Thu, 27 Oct 2022 21:41:36 -0700 Subject: [PATCH 1/2] Fixed signature calculation. I broke the timestamp previously --- cmd/events.go | 18 ++++++++++++++++++ internal/events/trigger/forward_event.go | 6 +++--- internal/events/trigger/retrigger_event.go | 1 + internal/events/verify/subscription_verify.go | 2 ++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/cmd/events.go b/cmd/events.go index e4060636..eb764aa8 100644 --- a/cmd/events.go +++ b/cmd/events.go @@ -6,12 +6,14 @@ import ( "fmt" "log" "net/url" + "time" "github.com/spf13/cobra" "github.com/twitchdev/twitch-cli/internal/events" "github.com/twitchdev/twitch-cli/internal/events/mock_wss_server" "github.com/twitchdev/twitch-cli/internal/events/trigger" "github.com/twitchdev/twitch-cli/internal/events/verify" + "github.com/twitchdev/twitch-cli/internal/util" ) const websubDeprecationNotice = "Halt! It appears you are trying to use WebSub, which has been deprecated. For more information, see: https://discuss.dev.twitch.tv/t/deprecation-of-websub-based-webhooks/32152" @@ -206,6 +208,7 @@ func retriggerCmdRun(cmd *cobra.Command, args []string) { res, err := trigger.RefireEvent(eventID, trigger.TriggerParameters{ ForwardAddress: forwardAddress, Secret: secret, + Timestamp: util.GetTimestamp().Format(time.RFC3339Nano), }) if err != nil { fmt.Printf("Error refiring event: %s", err) @@ -240,11 +243,26 @@ func verifyCmdRun(cmd *cobra.Command, args []string) { } } + if timestamp == "" { + timestamp = util.GetTimestamp().Format(time.RFC3339Nano) + } else { + // Verify custom timestamp + _, err := time.Parse(time.RFC3339Nano, timestamp) + if err != nil { + fmt.Println( + `Discarding event: Invalid timestamp provided. +Please follow RFC3339Nano, which is used by Twitch as seen here: +https://dev.twitch.tv/docs/eventsub/handling-webhook-events#processing-an-event`) + return + } + } + _, err := verify.VerifyWebhookSubscription(verify.VerifyParameters{ Event: args[0], Transport: transport, ForwardAddress: forwardAddress, Secret: secret, + Timestamp: timestamp, }) if err != nil { diff --git a/internal/events/trigger/forward_event.go b/internal/events/trigger/forward_event.go index 9aafb431..dcf94c6e 100644 --- a/internal/events/trigger/forward_event.go +++ b/internal/events/trigger/forward_event.go @@ -12,7 +12,6 @@ import ( "github.com/twitchdev/twitch-cli/internal/models" "github.com/twitchdev/twitch-cli/internal/request" - "github.com/twitchdev/twitch-cli/internal/util" ) type ForwardParamters struct { @@ -99,11 +98,12 @@ func ForwardEvent(p ForwardParamters) (*http.Response, error) { func getSignatureHeader(req *http.Request, id string, secret string, transport string, timestamp string, payload []byte) { mac := hmac.New(sha256.New, []byte(secret)) - ts := util.GetTimestamp() + ts, _ := time.Parse(time.RFC3339Nano, timestamp) + switch transport { case models.TransportEventSub: req.Header.Set("Twitch-Eventsub-Message-Timestamp", timestamp) - prefix := ts.AppendFormat([]byte(id), timestamp) + prefix := ts.AppendFormat([]byte(id), time.RFC3339Nano) mac.Write(prefix) mac.Write(payload) req.Header.Set("Twitch-Eventsub-Message-Signature", fmt.Sprintf("sha256=%x", mac.Sum(nil))) diff --git a/internal/events/trigger/retrigger_event.go b/internal/events/trigger/retrigger_event.go index 00e2c17c..3f920dfe 100644 --- a/internal/events/trigger/retrigger_event.go +++ b/internal/events/trigger/retrigger_event.go @@ -35,6 +35,7 @@ func RefireEvent(id string, p TriggerParameters) (string, error) { resp, err := ForwardEvent(ForwardParamters{ ID: id, Transport: res.Transport, + Timestamp: p.Timestamp, ForwardAddress: p.ForwardAddress, Secret: p.Secret, JSON: []byte(res.JSON), diff --git a/internal/events/verify/subscription_verify.go b/internal/events/verify/subscription_verify.go index fb5798ae..bdd684ae 100644 --- a/internal/events/verify/subscription_verify.go +++ b/internal/events/verify/subscription_verify.go @@ -20,6 +20,7 @@ import ( type VerifyParameters struct { Transport string + Timestamp string Event string ForwardAddress string Secret string @@ -67,6 +68,7 @@ func VerifyWebhookSubscription(p VerifyParameters) (VerifyResponse, error) { Event: event.GetTopic(p.Transport, p.Event), JSON: body.JSON, Transport: p.Transport, + Timestamp: p.Timestamp, Secret: p.Secret, Method: requestMethod, ForwardAddress: u.String(), From ddce8ccdc331861af1d23f16be60a15e83be3d2a Mon Sep 17 00:00:00 2001 From: Marc Szanto <11840265+Xemdo@users.noreply.github.com> Date: Thu, 27 Oct 2022 21:46:08 -0700 Subject: [PATCH 2/2] Added --timestamp flag to 'twitch event verify'; Removed -Z shorthand for all --timestamp flags --- cmd/events.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmd/events.go b/cmd/events.go index eb764aa8..d13b8bf9 100644 --- a/cmd/events.go +++ b/cmd/events.go @@ -118,7 +118,7 @@ func init() { triggerCmd.Flags().Int64VarP(&cost, "cost", "C", 0, "Amount of bits or channel points redeemed/used in the event.") triggerCmd.Flags().StringVarP(&description, "description", "d", "", "Title the stream should be updated with.") triggerCmd.Flags().StringVarP(&gameID, "game-id", "G", "", "Sets the game/category ID for applicable events.") - triggerCmd.Flags().StringVarP(×tamp, "timestamp", "Z", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.") + triggerCmd.Flags().StringVar(×tamp, "timestamp", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.") // retrigger flags retriggerCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event.") @@ -130,6 +130,7 @@ func init() { verifyCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event.") verifyCmd.Flags().StringVarP(&transport, "transport", "T", "eventsub", fmt.Sprintf("Preferred transport method for event. Defaults to EventSub.\nSupported values: %s", events.ValidTransports())) verifyCmd.Flags().StringVarP(&secret, "secret", "s", "", "Webhook secret. If defined, signs all forwarded events with the SHA256 HMAC and must be 10-100 characters in length.") + verifyCmd.Flags().StringVar(×tamp, "timestamp", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.") verifyCmd.MarkFlagRequired("forward-address") // start-websocket-server flags @@ -250,7 +251,7 @@ func verifyCmdRun(cmd *cobra.Command, args []string) { _, err := time.Parse(time.RFC3339Nano, timestamp) if err != nil { fmt.Println( - `Discarding event: Invalid timestamp provided. + `Discarding verify: Invalid timestamp provided. Please follow RFC3339Nano, which is used by Twitch as seen here: https://dev.twitch.tv/docs/eventsub/handling-webhook-events#processing-an-event`) return