Skip to content

Commit

Permalink
Merge pull request #223 from twitchdev/eventsub-websocket-ssl
Browse files Browse the repository at this point in the history
Added SSL support for the EventSub WebSocket server
  • Loading branch information
Xemdo authored Apr 5, 2023
2 parents dad323d + 1ebd213 commit f02f16c
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 34 deletions.
13 changes: 8 additions & 5 deletions cmd/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ var (
clientId string
version string
websocketClient string
websocketServerIP string
websocketServerPort int
)

// websocketCmd-specific flags
Expand All @@ -57,6 +55,9 @@ var (
wsSubscription string
wsStatus string
wsReason string
wsServerIP string
wsServerPort int
wsSSL bool
)

var eventCmd = &cobra.Command{
Expand Down Expand Up @@ -175,8 +176,9 @@ func init() {

// websocket flags
/// flags for start-server
websocketCmd.Flags().StringVar(&websocketServerIP, "ip", "127.0.0.1", "Defines the ip that the mock EventSub websocket server will bind to.")
websocketCmd.Flags().IntVarP(&websocketServerPort, "port", "p", 8080, "Defines the port that the mock EventSub websocket server will run on.")
websocketCmd.Flags().StringVar(&wsServerIP, "ip", "127.0.0.1", "Defines the ip that the mock EventSub websocket server will bind to.")
websocketCmd.Flags().IntVarP(&wsServerPort, "port", "p", 8080, "Defines the port that the mock EventSub websocket server will run on.")
websocketCmd.Flags().BoolVar(&wsSSL, "ssl", false, "Enables SSL for EventSub websocket server (wss) and EventSub mock subscription server (https).")
websocketCmd.Flags().BoolVar(&wsDebug, "debug", false, "Set on/off for debug messages for the EventSub WebSocket server.")
websocketCmd.Flags().BoolVarP(&wsStrict, "require-subscription", "S", false, "Requires subscriptions for all events, and activates 10 second subscription requirement.")

Expand Down Expand Up @@ -334,8 +336,9 @@ func websocketCmdRun(cmd *cobra.Command, args []string) {
}

if args[0] == "start-server" || args[0] == "start" {
log.Printf("Attempting to start WebSocket server on %v:%v", wsServerIP, wsServerPort)
log.Printf("`Ctrl + C` to exit mock WebSocket servers.")
mock_server.StartWebsocketServer(wsDebug, websocketServerIP, websocketServerPort, wsStrict)
mock_server.StartWebsocketServer(wsDebug, wsServerIP, wsServerPort, wsSSL, wsStrict)
} else {
// Forward all other commands via RPC
websocket.ForwardWebsocketCommand(args[0], websocket.WebsocketCommandParameters{
Expand Down
111 changes: 84 additions & 27 deletions internal/events/websocket/mock_server/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ package mock_server

import (
"encoding/json"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"strings"
"time"

Expand All @@ -28,11 +30,14 @@ type ServerManager struct {
port int
debugEnabled bool
strictMode bool
sslEnabled bool
protocolHttp string
protocolWs string
}

var serverManager *ServerManager

func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool) {
func StartWebsocketServer(enableDebug bool, ip string, port int, enableSSL bool, strictMode bool) {
serverManager = &ServerManager{
serverList: &util.List[WebSocketServer]{
Elements: make(map[string]*WebSocketServer),
Expand All @@ -41,6 +46,7 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
port: port,
reconnectTesting: false,
strictMode: strictMode,
sslEnabled: enableSSL,
}

serverManager.debugEnabled = enableDebug
Expand Down Expand Up @@ -83,39 +89,59 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
return
}

lightBlue := color.New(color.FgHiBlue).SprintFunc()
lightGreen := color.New(color.FgHiGreen).SprintFunc()
lightYellow := color.New(color.FgHiYellow).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()
lightRed := color.New(color.FgHiRed).SprintFunc()
brightWhite := color.New(color.FgHiWhite).SprintFunc()

log.Printf(lightBlue("Started WebSocket server on %v:%v"), ip, port)
if serverManager.strictMode {
log.Printf(lightBlue("--require-subscription enabled. Clients will have 10 seconds to subscribe before being disconnected."))
}

fmt.Println()

log.Printf(yellow("Simulate subscribing to events at: http://%v:%v/eventsub/subscriptions"), ip, port)
log.Printf(yellow("POST, GET, and DELETE are supported"))
log.Printf(yellow("For more info: https://dev.twitch.tv/docs/cli/websocket-event-command/#simulate-subscribing-to-mock-eventsub"))
// Serve HTTP server
if serverManager.sslEnabled {
serverManager.protocolHttp = "https"
serverManager.protocolWs = "wss"

home, err := util.GetApplicationDir()
if err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
}

fmt.Println()
crtFile := filepath.Join(home, "localhost.crt")
keyFile := filepath.Join(home, "localhost.key")
_, crtFileErr := os.Stat(crtFile)
_, keyFileErr := os.Stat(keyFile)
if errors.Is(crtFileErr, os.ErrNotExist) || errors.Is(keyFileErr, os.ErrNotExist) {
log.Fatalf(`%v
** Files must be placed in %v as %v and %v **
%v
** However, if you wish to generate the files using OpenSSL, run these commands: **
openssl genrsa -out "%v" 2048
openssl req -new -x509 -sha256 -key "%v" -out "%v" -days 365`,
lightRed("ERROR: Missing one of the required SSL crt/key files."),
brightWhite(home),
brightWhite("localhost.crt"),
brightWhite("localhost.key"),
lightYellow("** Testing with Twitch CLI using SSL is meant for users experienced with SSL already, as these files must be added to your systems keychain to work without errors. **"),
keyFile, keyFile, crtFile)
return
}

log.Printf(lightYellow("Events can be forwarded to this server from another terminal with --transport=websocket\nExample: \"twitch event trigger channel.ban --transport=websocket\""))
fmt.Println()
log.Printf(lightYellow("You can send to a specific client after its connected with --session\nExample: \"twitch event trigger channel.ban --transport=websocket --session=e411cc1e_a2613d4e\""))
printWelcomeMsg()

fmt.Println()
log.Printf(lightGreen("For further usage information, please see our official documentation:\nhttps://dev.twitch.tv/docs/cli/websocket-event-command/"))
fmt.Println()
if err := http.ServeTLS(listen, m, crtFile, keyFile); err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
}
} else {
serverManager.protocolHttp = "http"
serverManager.protocolWs = "ws"

log.Printf(lightBlue("Connect to the WebSocket server at: ")+"ws://%v:%v/ws", ip, port)
printWelcomeMsg()

// Serve HTTP server
if err := http.Serve(listen, m); err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
if err := http.Serve(listen, m); err != nil {
log.Fatalf("Cannot start HTTP server: %v", err)
return
}
}

}()

// Initalize RPC handler, to accept EventSub transports
Expand All @@ -135,10 +161,41 @@ func StartWebsocketServer(enableDebug bool, ip string, port int, strictMode bool
<-stop // Wait for Ctrl + C
}

func printWelcomeMsg() {
lightBlue := color.New(color.FgHiBlue).SprintFunc()
lightGreen := color.New(color.FgHiGreen).SprintFunc()
lightYellow := color.New(color.FgHiYellow).SprintFunc()
yellow := color.New(color.FgYellow).SprintFunc()

log.Printf(lightBlue("Started WebSocket server on %v:%v"), serverManager.ip, serverManager.port)
if serverManager.strictMode {
log.Printf(lightBlue("--require-subscription enabled. Clients will have 10 seconds to subscribe before being disconnected."))
}

fmt.Println()

log.Printf(yellow("Simulate subscribing to events at: %v://%v:%v/eventsub/subscriptions"), serverManager.protocolHttp, serverManager.ip, serverManager.port)
log.Printf(yellow("POST, GET, and DELETE are supported"))
log.Printf(yellow("For more info: https://dev.twitch.tv/docs/cli/websocket-event-command/#simulate-subscribing-to-mock-eventsub"))

fmt.Println()

log.Printf(lightYellow("Events can be forwarded to this server from another terminal with --transport=websocket\nExample: \"twitch event trigger channel.ban --transport=websocket\""))
fmt.Println()
log.Printf(lightYellow("You can send to a specific client after its connected with --session\nExample: \"twitch event trigger channel.ban --transport=websocket --session=e411cc1e_a2613d4e\""))

fmt.Println()
log.Printf(lightGreen("For further usage information, please see our official documentation:\nhttps://dev.twitch.tv/docs/cli/websocket-event-command/"))
fmt.Println()

log.Printf(lightBlue("Connect to the WebSocket server at: ")+"%v://%v:%v/ws", serverManager.protocolWs, serverManager.ip, serverManager.port)
}

func wsPageHandler(w http.ResponseWriter, r *http.Request) {
server, ok := serverManager.serverList.Get(serverManager.primaryServer)
if !ok {
log.Printf("Failed to find primary server [%v] when new client was accessing ws://%v:%v/ws -- Aborting...", serverManager.primaryServer, serverManager.ip, serverManager.port)
log.Printf("Failed to find primary server [%v] when new client was accessing %v://%v:%v/ws -- Aborting...",
serverManager.primaryServer, serverManager.protocolHttp, serverManager.ip, serverManager.port)
return
}

Expand Down
2 changes: 1 addition & 1 deletion internal/events/websocket/mock_server/rpc_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func RPCSubscriptionHandler(args rpc.RPCArgs) rpc.RPCResponse {
return rpc.RPCResponse{
ResponseCode: COMMAND_RESPONSE_MISSING_FLAG,
DetailedInfo: "Command \"subscription\" requires flags --status, --subscription, and --session" +
fmt.Sprintf("\nThe flag --subscription must be the ID of the subscription made at http://%v:%v/eventsub/subscriptions", serverManager.ip, serverManager.port) +
fmt.Sprintf("\nThe flag --subscription must be the ID of the subscription made at %v://%v:%v/eventsub/subscriptions", serverManager.protocolHttp, serverManager.ip, serverManager.port) +
"\nThe flag --status must be one of the non-webhook status options defined here:" +
"\nhttps://dev.twitch.tv/docs/api/reference/#get-eventsub-subscriptions" +
"\n\nExample: twitch event websocket subscription --status=user_removed --subscription=82a855-fae8-93bff0",
Expand Down
2 changes: 1 addition & 1 deletion internal/events/websocket/mock_server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func (ws *WebSocketServer) WsPageHandler(w http.ResponseWriter, r *http.Request)
clientName: util.RandomGUID()[:8],
conn: conn,
ConnectedAtTimestamp: connectedAtTimestamp,
connectionUrl: fmt.Sprintf("http://%v/ws", r.Host),
connectionUrl: fmt.Sprintf("%v://%v/ws", serverManager.protocolHttp, r.Host),
keepAliveChanOpen: false,
pingChanOpen: false,
}
Expand Down

0 comments on commit f02f16c

Please sign in to comment.