You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
146 lines
3.7 KiB
146 lines
3.7 KiB
package main |
|
|
|
import ( |
|
"context" |
|
"crypto/tls" |
|
_ "embed" |
|
"errors" |
|
"fmt" |
|
"log" |
|
"net/http" |
|
"strings" |
|
"time" |
|
|
|
"github.com/slack-go/slack" |
|
) |
|
|
|
var ( |
|
// These are set via build flags but can be overridden via environment variables. |
|
defaultClientID = "" |
|
defaultClientSecret = "" |
|
|
|
//go:embed "certs/cert.pem" |
|
certPem []byte |
|
//go:embed "certs/key.pem" |
|
keyPem []byte |
|
) |
|
|
|
const ( |
|
httpReadTimeout = 5 * time.Second |
|
httpWriteTimeout = 10 * time.Second |
|
httpIdleTimeout = 120 * time.Second |
|
) |
|
|
|
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 |
|
|
|
certPath, err := getConfigFilePath("cert.pem") |
|
if err != nil { |
|
return "", fmt.Errorf("failed checking config path for cert: %w", err) |
|
} |
|
|
|
keyPath, err := getConfigFilePath("key.pem") |
|
if err != nil { |
|
return "", fmt.Errorf("failed checking config path for key: %w", err) |
|
} |
|
|
|
tlsCfg := &tls.Config{ |
|
MinVersion: tls.VersionTLS12, |
|
} |
|
|
|
// If config files don't exist, use embedded |
|
if !fileExists(certPath) && !fileExists(keyPath) { |
|
cert, err := tls.X509KeyPair(certPem, keyPem) |
|
if err != nil { |
|
return "", fmt.Errorf("failed loading embedded key pair: %w", err) |
|
} |
|
|
|
tlsCfg.Certificates = make([]tls.Certificate, 1) |
|
tlsCfg.Certificates[0] = cert |
|
|
|
// Empty out paths since they don't exist so embedded certs will be used |
|
certPath = "" |
|
keyPath = "" |
|
} |
|
|
|
// Also, should generate TLS certificate to use since https is a required scheme |
|
server := http.Server{ |
|
Addr: app.listenHost, |
|
ReadTimeout: httpReadTimeout, |
|
WriteTimeout: httpWriteTimeout, |
|
IdleTimeout: httpIdleTimeout, |
|
TLSConfig: tlsCfg, |
|
} |
|
|
|
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) |
|
} |
|
}() |
|
}) |
|
|
|
if err := server.ListenAndServeTLS(certPath, keyPath); err != nil && !errors.Is(err, http.ErrServerClosed) { |
|
return "", err |
|
} |
|
|
|
return code, nil |
|
} |
|
|
|
func authenticate() (string, error) { |
|
app := slackApp{ |
|
userScopes: []string{"dnd:write", "users.profile:write", "team:read"}, |
|
scopes: []string{"dnd:write", "users.profile:write", "team:read"}, |
|
clientID: getEnvOrDefault("CLIENT_ID", defaultClientID), |
|
clientSecret: getEnvOrDefault("CLIENT_SECRET", defaultClientSecret), |
|
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() |
|
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 |
|
}
|
|
|