Skip to content

Commit

Permalink
Merge pull request #299 from twitchdev/feat/eventsub-configure
Browse files Browse the repository at this point in the history
event configure command
  • Loading branch information
Xemdo authored Dec 13, 2023
2 parents 414972f + 5d50e42 commit bca3aec
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 10 deletions.
68 changes: 60 additions & 8 deletions cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/spf13/cobra"
"github.com/twitchdev/twitch-cli/internal/events"
configure_event "github.com/twitchdev/twitch-cli/internal/events/configure"
"github.com/twitchdev/twitch-cli/internal/events/trigger"
"github.com/twitchdev/twitch-cli/internal/events/types"
"github.com/twitchdev/twitch-cli/internal/events/verify"
Expand All @@ -25,6 +26,7 @@ var (
forwardAddress string
event string
transport string
noConfig bool
fromUser string
toUser string
giftUser string
Expand Down Expand Up @@ -127,16 +129,25 @@ var startWebsocketServerCmd = &cobra.Command{
Deprecated: `use "twitch event websocket start-server" instead.`,
}

var configureEventCmd = &cobra.Command{
Use: "configure",
Short: "Allows users to configure defaults for the twitch event subcommands.",
RunE: configureEventRun,
Example: `twitch event configure`,
}

func init() {
rootCmd.AddCommand(eventCmd)

eventCmd.AddCommand(triggerCmd, retriggerCmd, verifyCmd, websocketCmd, startWebsocketServerCmd)
eventCmd.AddCommand(triggerCmd, retriggerCmd, verifyCmd, websocketCmd, startWebsocketServerCmd, configureEventCmd)
eventCmd.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")

// trigger flags
//// flags for forwarding functionality/changing payloads
triggerCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
triggerCmd.Flags().StringVarP(&transport, "transport", "T", "webhook", fmt.Sprintf("Preferred transport method for event. Defaults to /EventSub.\nSupported values: %s", events.ValidTransports()))
triggerCmd.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.")
triggerCmd.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")

// trigger flags
//// per-topic flags
Expand Down Expand Up @@ -167,6 +178,7 @@ func init() {
retriggerCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
retriggerCmd.Flags().StringVarP(&eventID, "id", "i", "", "ID of the event to be refired.")
retriggerCmd.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.")
retriggerCmd.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")
retriggerCmd.MarkFlagRequired("id")

// verify-subscription flags
Expand All @@ -176,8 +188,8 @@ func init() {
verifyCmd.Flags().StringVar(&timestamp, "timestamp", "", "Sets the timestamp to be used in payloads and headers. Must be in RFC3339Nano format.")
verifyCmd.Flags().StringVarP(&eventID, "subscription-id", "u", "", "Manually set the subscription/event ID of the event itself.") // TODO: This description will need to change with https://github.com/twitchdev/twitch-cli/issues/184
verifyCmd.Flags().StringVarP(&version, "version", "v", "", "Chooses the EventSub version used for a specific event. Not required for most events.")
verifyCmd.Flags().BoolVarP(&noConfig, "no-config", "D", false, "Disables the use of the configuration, if it exists.")
verifyCmd.Flags().StringVarP(&toUser, "broadcaster", "b", "", "User ID of the broadcaster for the verification event.")
verifyCmd.MarkFlagRequired("forward-address")

// websocket flags
/// flags for start-server
Expand All @@ -193,6 +205,10 @@ func init() {
websocketCmd.Flags().StringVar(&wsSubscription, "subscription", "", `Subscription to target with your server command. Used with "websocket subscription".`)
websocketCmd.Flags().StringVar(&wsStatus, "status", "", `Changes the status of an existing subscription. Used with "websocket subscription".`)
websocketCmd.Flags().StringVar(&wsReason, "reason", "", `Sets the close reason when sending a Close message to the client. Used with "websocket close".`)

// configure flags
configureEventCmd.Flags().StringVarP(&forwardAddress, "forward-address", "F", "", "Forward address for mock event (webhook only).")
configureEventCmd.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.")
}

func triggerCmdRun(cmd *cobra.Command, args []string) error {
Expand All @@ -205,8 +221,14 @@ func triggerCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf(websubDeprecationNotice)
}

if secret != "" && (len(secret) < 10 || len(secret) > 100) {
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
defaults := configure_event.GetEventConfiguration(noConfig)

if secret != "" {
if len(secret) < 10 || len(secret) > 100 {
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
}
} else {
secret = defaults.Secret
}

// Validate that the forward address is actually a URL
Expand All @@ -215,6 +237,8 @@ func triggerCmdRun(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
} else {
forwardAddress = defaults.ForwardAddress
}

for i := 0; i < count; i++ {
Expand Down Expand Up @@ -261,8 +285,21 @@ func retriggerCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf(websubDeprecationNotice)
}

if secret != "" && (len(secret) < 10 || len(secret) > 100) {
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
defaults := configure_event.GetEventConfiguration(noConfig)

if secret != "" {
if len(secret) < 10 || len(secret) > 100 {
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
}
} else {
secret = defaults.Secret
}

if forwardAddress == "" {
if defaults.ForwardAddress == "" {
return fmt.Errorf("if a default configuration is not set, forward-address must be provided")
}
forwardAddress = defaults.ForwardAddress
}

res, err := trigger.RefireEvent(eventID, trigger.TriggerParameters{
Expand All @@ -288,8 +325,14 @@ func verifyCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf(websubDeprecationNotice)
}

if secret != "" && (len(secret) < 10 || len(secret) > 100) {
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
defaults := configure_event.GetEventConfiguration(noConfig)

if secret != "" {
if len(secret) < 10 || len(secret) > 100 {
return fmt.Errorf("Invalid secret provided. Secrets must be between 10-100 characters")
}
} else {
secret = defaults.Secret
}

// Validate that the forward address is actually a URL
Expand All @@ -298,6 +341,8 @@ func verifyCmdRun(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
} else {
forwardAddress = defaults.ForwardAddress
}

if timestamp == "" {
Expand Down Expand Up @@ -355,3 +400,10 @@ func websocketCmdRun(cmd *cobra.Command, args []string) error {

return nil
}

func configureEventRun(cmd *cobra.Command, args []string) error {
return configure_event.ConfigureEvents(configure_event.EventConfigurationParams{
ForwardAddress: forwardAddress,
Secret: secret,
})
}
22 changes: 20 additions & 2 deletions docs/event.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,31 @@

- [Events](#events)
- [Description](#description)
- [Configure](#configure)
- [Trigger](#trigger)
- [Retrigger](#retrigger)
- [Verify-Subscription](#verify-subscription)
- [WebSocket](#websocket)

## Description

The `event` product contains commands to trigger mock events for local webhook testing or migration.
The `event` command contains subcommands to trigger mock events for local webhook testing or migration.

All commands exit the program with a non-zero exit code when the command fails, including when an event does not exist, or when the mock EventSub WebSocket server does not start correctly.


## Configure

Used to configure the forwarding address and/or the secret used with the `trigger`, `verify-subscription`, and `retrigger` subcommands.

**Flags**

| Flag | Shorthand | Description | Example | Required? (Y/N) |
|---------------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|-----------------|
| `--forward-address` | `-F` | Web server address for where to send mock events. | `-F https://localhost:8080` | N |
| `--secret` | `-s` | Webhook secret. If defined, signs all forwarded events with the SHA256 HMAC and must be 10-100 characters in length. | `-s testsecret` | N |


## Trigger

Used to either create or send mock events for use with local webhooks testing.
Expand Down Expand Up @@ -92,6 +106,7 @@ This command can take either the Event or Alias listed as an argument. It is pre
| `--gift-user` | `-g` | Used only for subcription-based events, denotes the gifting user ID. | `-g 44635596` | N |
| `--item-id` | `-i` | Manually set the ID of the event payload item (for example the reward ID in redemption events or game in stream events). | `-i 032e4a6c-4aef-11eb-a9f5-1f703d1f0b92` | N |
| `--item-name` | `-n` | Manually set the name of the event payload item (for example the reward ID in redemption events or game name in stream events). | `-n "Science & Technology"` | N |
| `--no-config` | `-D` | Disables the use of the configuration values should they exist. | `-D` | N |
| `--secret` | `-s` | Webhook secret. If defined, signs all forwarded events with the SHA256 HMAC and must be 10-100 characters in length. | `-s testsecret` | N |
| `--session` | | WebSocket session to target. Only used when forwarding to WebSocket servers with --transport=websocket | `--session e411cc1e_a2613d4e` | N |
| `--subscription-id` | `-u` | Manually set the subscription/event ID of the event itself. | `-u 5d3aed06-d019-11ed-afa1-0242ac120002` | N |
Expand Down Expand Up @@ -134,8 +149,10 @@ None
|---------------------|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|-----------------|
| `--forward-address` | `-F` | Web server address for where to send mock events. | `-F https://localhost:8080` | N |
| `--id` | `-i` | The ID of the event to refire. | `-i <id>` | Y |
| `--no-config` | `-D` | Disables the use of the configuration values should they exist. | `-D` | N |
| `--secret` | `-s` | Webhook secret. If defined, signs all forwarded events with the SHA256 HMAC and must be 10-100 characters in length. | `-s testsecret` | N |


**Examples**

```sh
Expand All @@ -144,7 +161,7 @@ twitch event retrigger -i "713f3254-0178-9757-7439-d779400c0999" -F https://loca

## Verify-Subscription

Allows you to test if your webserver responds to subscription requests properly.
Allows you to test if your webserver responds to subscription requests properly. The `forward-address` flag is required *unless* you have configured a default forwarding address via `twitch event configure -F <address>`.

**Args**

Expand All @@ -156,6 +173,7 @@ This command takes the same arguments as [Trigger](#trigger).
|---------------------|-----------|----------------------------------------------------------------------------------------------------------------------|-----------------------------|-----------------|
| `--broadcaster` | `-b` | The broadcaster's user ID to be used for verification | `-b 1234` | N |
| `--forward-address` | `-F` | Web server address for where to send mock subscription. | `-F https://localhost:8080` | Y |
| `--no-config` | `-D` | Disables the use of the configuration values should they exist. | `-D` | N |
| `--secret` | `-s` | Webhook secret. If defined, signs all forwarded events with the SHA256 HMAC and must be 10-100 characters in length. | `-s testsecret` | N |
| `--transport` | `-T` | The method used to send events. Default is `eventsub`. | `-T eventsub` | N |

Expand Down
58 changes: 58 additions & 0 deletions internal/events/configure/configure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package configure_event

import (
"fmt"
"net/url"

"github.com/spf13/viper"
"github.com/twitchdev/twitch-cli/internal/util"
)

type EventConfigurationParams struct {
Secret string
ForwardAddress string
}

func ConfigureEvents(p EventConfigurationParams) error {
var err error
if p.ForwardAddress == "" && p.Secret == "" {
return fmt.Errorf("you must provide at least one of --secret or --forward-address")
}

// Validate that the forward address is actually a URL
if len(p.ForwardAddress) > 0 {
_, err := url.ParseRequestURI(p.ForwardAddress)
if err != nil {
return err
}
viper.Set("forwardAddress", p.ForwardAddress)
}
if p.Secret != "" {
if len(p.Secret) < 10 || len(p.Secret) > 100 {
return fmt.Errorf("invalid secret provided. Secrets must be between 10-100 characters")
}
viper.Set("eventSecret", p.Secret)
}

configPath, err := util.GetConfigPath()
if err != nil {
return err
}

if err := viper.WriteConfigAs(configPath); err != nil {
return fmt.Errorf("failed to write configuration: %v", err.Error())
}

fmt.Println("Updated configuration.")
return nil
}

func GetEventConfiguration(noConfig bool) EventConfigurationParams {
if noConfig {
return EventConfigurationParams{}
}
return EventConfigurationParams{
ForwardAddress: viper.GetString("forwardAddress"),
Secret: viper.GetString("eventSecret"),
}
}
36 changes: 36 additions & 0 deletions internal/events/configure/configure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package configure_event_test

import (
"testing"

"github.com/spf13/viper"
configure_event "github.com/twitchdev/twitch-cli/internal/events/configure"
"github.com/twitchdev/twitch-cli/test_setup"
)

func TestWriteEventConfig(t *testing.T) {
a := test_setup.SetupTestEnv(t)
defaultForwardAddress := "http://localhost:3000/"
defaultSecret := "12345678910"
test_config := configure_event.EventConfigurationParams{
ForwardAddress: defaultForwardAddress,
Secret: defaultSecret,
}

// test a good config writes correctly
a.NoError(configure_event.ConfigureEvents(test_config))

a.Equal(defaultForwardAddress, viper.Get("forwardAddress"))
a.Equal(defaultSecret, viper.Get("eventSecret"))

// test for secret length validation
test_config.Secret = "1"
a.Error(configure_event.ConfigureEvents(test_config))
a.NotEqual("1", viper.Get("eventSecret"))
test_config.Secret = defaultSecret

// test for forward address validation
test_config.ForwardAddress = "not a url"
a.Error(configure_event.ConfigureEvents(test_config))
a.NotEqual("not a url", viper.Get("forwardAddress"))
}
5 changes: 5 additions & 0 deletions internal/util/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,10 @@ func GetConfigPath() (string, error) {

configPath := filepath.Join(home, ".twitch-cli.env")

// purely for testing purposes- this allows us to run tests without overwriting the user's config
if os.Getenv("GOLANG_TESTING") == "true" {
configPath = filepath.Join(home, ".twitch-cli-test.env")
}

return configPath, nil
}
1 change: 1 addition & 0 deletions test_setup/test_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ func SetupTestEnv(t *testing.T) *assert.Assertions {
viper.SetConfigType("env")

viper.Set("DB_FILENAME", "test-eventCache.db")
t.Setenv("GOLANG_TESTING", "true")
return assert
}

0 comments on commit bca3aec

Please sign in to comment.