slack-status-cli/auth.go

150 lines
3.5 KiB
Go
Raw Normal View History

2021-01-06 18:44:29 +00:00
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
2021-01-07 17:19:56 +00:00
"os/exec"
2021-01-06 18:44:29 +00:00
"strings"
2021-01-07 17:19:56 +00:00
"time"
2021-01-06 18:44:29 +00:00
"github.com/slack-go/slack"
)
var (
2021-02-09 18:20:38 +00:00
// These are set via build flags but can be overridden via environment variables.
2021-01-06 18:44:29 +00:00
defaultClientID = ""
defaultClientSecret = ""
)
2021-02-09 18:20:38 +00:00
const (
httpReadTimeout = 5 * time.Second
httpWriteTimeout = 10 * time.Second
httpIdleTimeout = 120 * time.Second
)
2021-01-06 18:44:29 +00:00
type slackApp struct {
clientID, clientSecret, redirectURI string
scopes, userScopes []string
listenHost, listenPath string
}
func (app slackApp) getAuthURL() string {
scopes := strings.Join(app.scopes, ",")
userScopes := strings.Join(app.userScopes, ",")
return fmt.Sprintf(
"https://slack.com/oauth/authorize?scope=%s&user_scope=%s&client_id=%s&redirect_uri=%s",
scopes,
userScopes,
app.clientID,
app.redirectURI,
)
}
func (app slackApp) listenForCode() (string, error) {
// start an http listener and listen for the redirect and return the code from params
var code string
// Also, should generate TLS certificate to use since https is a required scheme
2021-01-07 17:19:56 +00:00
server := http.Server{
Addr: app.listenHost,
2021-02-09 18:20:38 +00:00
ReadTimeout: httpReadTimeout,
WriteTimeout: httpWriteTimeout,
IdleTimeout: httpIdleTimeout,
2021-01-07 17:19:56 +00:00
}
2021-01-06 18:44:29 +00:00
http.HandleFunc(app.listenPath, func(w http.ResponseWriter, r *http.Request) {
codes := r.URL.Query()["code"]
if len(codes) == 0 {
log.Fatal("no oauth code found in response")
}
code = codes[0]
fmt.Fprintf(w, "Got code %s", code)
// Shutdown after response
go func() {
if err := server.Shutdown(context.Background()); err != nil {
fmt.Println("Fatal?")
log.Fatal(err)
}
}()
})
2021-02-05 18:22:17 +00:00
certPath, err := getConfigFilePath("cert.pem")
if err != nil {
return "", err
}
keyPath, err := getConfigFilePath("key.pem")
if err != nil {
return "", err
}
2021-01-07 17:19:56 +00:00
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) {
2021-01-06 18:44:29 +00:00
return "", err
}
return code, nil
}
2021-01-07 17:19:56 +00:00
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",
2021-02-03 01:08:09 +00:00
"-addext",
"subjectAltName=DNS:localhost:8888",
2021-01-07 17:19:56 +00:00
"-keyout",
keyPath,
"-out",
certPath,
)
return command.Run()
}
2021-01-06 18:44:29 +00:00
func authenticate() (string, error) {
app := slackApp{
userScopes: []string{"dnd:write", "users.profile:write", "team:read"},
scopes: []string{"dnd:write", "users.profile:write", "team:read"},
2021-01-06 18:44:29 +00:00
clientID: getEnvOrDefault("CLIENT_ID", defaultClientID),
clientSecret: getEnvOrDefault("CLIENT_SECRET", defaultClientSecret),
2021-01-07 17:19:56 +00:00
redirectURI: "https://localhost:8888/auth",
2021-01-06 18:44:29 +00:00
listenHost: "localhost:8888",
listenPath: "/auth",
}
fmt.Println("To authenticate, go to the following URL:")
2021-01-07 17:19:56 +00:00
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.")
2021-01-06 18:44:29 +00:00
fmt.Println(app.getAuthURL())
code, err := app.listenForCode()
if err != nil {
return "", err
}
accessToken, _, err := slack.GetOAuthToken(&http.Client{}, app.clientID, app.clientSecret, code, app.redirectURI)
if err != nil {
return "", err
}
return accessToken, nil
}