2021-01-06 18:44:29 +00:00
package main
import (
"context"
2021-02-16 22:16:45 +00:00
"crypto/tls"
_ "embed"
2021-01-06 18:44:29 +00:00
"errors"
"fmt"
"log"
"net/http"
"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-16 22:16:45 +00:00
//go:embed "certs/cert.pem"
certPem [ ] byte
//go:embed "certs/key.pem"
keyPem [ ] byte
2021-01-06 18:44:29 +00:00
)
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
2021-02-16 22:16:45 +00:00
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
2021-02-17 18:24:34 +00:00
// Empty out paths since they don't exist so embedded certs will be used
2021-02-16 22:16:45 +00:00
certPath = ""
keyPath = ""
}
2021-01-06 18:44:29 +00:00
// 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-02-16 22:16:45 +00:00
TLSConfig : tlsCfg ,
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-01-07 17:19:56 +00:00
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
}
func authenticate ( ) ( string , error ) {
app := slackApp {
2021-02-02 19:57:39 +00:00
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
}