Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

event configure command #299

Merged
merged 2 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}