diff --git a/Makefile b/Makefile index 207fcaa..d09befb 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ DIST_TARGETS = $(addprefix dist/$(OUTPUT)-,$(DIST_ARCH)) .PHONY: default default: slack-status +.PHONY: run +run: slack-status + ./slack-status + .PHONY: all all: dist diff --git a/README.md b/README.md index b5f36ba..5cb619c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ Set your Slack status via the command line +## Requirements + +Rather than host a web server that you would need to trust, this command runs one on your local machine to retrieve the OAuth code. This page is hosted with an auto generated, self-signed certificate. + +This requires you to have `openssl` installed on your machine and, when the page loads for the first time, it will require you to trust the certificate or ignore the warning. + +Here's how to do that on [Firefox](https://support.mozilla.org/en-US/kb/error-codes-secure-websites?as=u&utm_source=inproduct#w_self-signed-certificate). On Chrome, you may have to enable `chrome://flags/#allow-insecure-localhost`. + ## Example usage Set auth token (it will store it in `~/.config/slack-status-cli` or your `$XDG_CONFIG_HOME` dir diff --git a/auth.go b/auth.go index dd83d9b..d8d2e22 100644 --- a/auth.go +++ b/auth.go @@ -7,7 +7,9 @@ import ( "log" "net/http" "os" + "os/exec" "strings" + "time" "github.com/slack-go/slack" ) @@ -51,7 +53,12 @@ func (app slackApp) listenForCode() (string, error) { var code string // Also, should generate TLS certificate to use since https is a required scheme - server := http.Server{Addr: app.listenHost} + server := http.Server{ + Addr: app.listenHost, + ReadTimeout: 5 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 120 * time.Second, + } http.HandleFunc(app.listenPath, func(w http.ResponseWriter, r *http.Request) { codes := r.URL.Query()["code"] @@ -71,25 +78,64 @@ func (app slackApp) listenForCode() (string, error) { }() }) - if err := server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + certPath := getConfigFilePath("cert.pem") + keyPath := getConfigFilePath("key.pem") + + if !fileExists(certPath) || !fileExists(keyPath) { + if err := generateSelfSignedCertificates(certPath, keyPath); err != nil { + return "", err + } + } + + if err := server.ListenAndServeTLS(certPath, keyPath); err != nil && !errors.Is(err, http.ErrServerClosed) { return "", err } return code, nil } +func fileExists(path string) bool { + if _, err := os.Stat(path); os.IsNotExist(err) { + return false + } + + return true +} + +func generateSelfSignedCertificates(certPath, keyPath string) error { + command := exec.Command( + "openssl", + "req", + "-x509", + "-subj", + "/C=US/O=Slack Status CLI/CN=localhost:8888", + "-nodes", + "-days", + "365", + "-newkey", + "rsa:2048", + "-keyout", + keyPath, + "-out", + certPath, + ) + + return command.Run() +} + func authenticate() (string, error) { app := slackApp{ userScopes: []string{"dnd:write", "users.profile:write"}, scopes: []string{"dnd:write", "users.profile:write"}, clientID: getEnvOrDefault("CLIENT_ID", defaultClientID), clientSecret: getEnvOrDefault("CLIENT_SECRET", defaultClientSecret), - redirectURI: "http://localhost:8888/auth", + redirectURI: "https://localhost:8888/auth", listenHost: "localhost:8888", listenPath: "/auth", } fmt.Println("To authenticate, go to the following URL:") + fmt.Println("NOTE: After you authenticate with Slack, it will redirect you to a server running on your local computer. Your browser will present a security error because it cann't verify the server. You will need to manually add an exception or tell your browser to proceed anyway.") fmt.Println(app.getAuthURL()) code, err := app.listenForCode()