Working oauth flow

This commit is contained in:
IamTheFij 2021-01-06 13:44:29 -05:00
parent f2cec168f7
commit 05f700bc93
4 changed files with 180 additions and 32 deletions

1
.gitignore vendored
View File

@ -17,3 +17,4 @@
slack-status slack-status
slack-status-cli slack-status-cli
dist/

32
Makefile Normal file
View File

@ -0,0 +1,32 @@
OUTPUT = slack-status
GOFILES = *.go go.mod go.sum
DIST_ARCH = darwin-amd64 linux-amd64
DIST_TARGETS = $(addprefix dist/$(OUTPUT)-,$(DIST_ARCH))
.PHONY: default
default: slack-status
.PHONY: all
all: dist
.PHONY: test
test:
go test
slack-status: $(GOFILES)
go build -o $(OUTPUT)
.PHONY: dist
dist: $(DIST_TARGETS)
$(DIST_TARGETS): $(GOFILES)
@mkdir -p ./dist
GOOS=$(word 3, $(subst -, ,$(@))) GOARCH=$(word 4, $(subst -, ,$(@))) \
go build \
-ldflags '-X "main.defaultClientID=$(CLIENT_ID)" -X "main.defaultClientSecret=$(CLIENT_SECRET)"' \
-o $@
.PHONY: clean
clean:
rm ./slack-status
rm -fr ./dist

106
auth.go Normal file
View File

@ -0,0 +1,106 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"os"
"strings"
"github.com/slack-go/slack"
)
var (
// These are set via build flags but can be overriden via environment variables.
defaultClientID = ""
defaultClientSecret = ""
)
func getEnvOrDefault(name, defaultValue string) string {
val, ok := os.LookupEnv(name)
if ok {
return val
}
return defaultValue
}
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
server := http.Server{Addr: app.listenHost}
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.ListenAndServe(); 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"},
scopes: []string{"dnd:write", "users.profile:write"},
clientID: getEnvOrDefault("CLIENT_ID", defaultClientID),
clientSecret: getEnvOrDefault("CLIENT_SECRET", defaultClientSecret),
redirectURI: "http://localhost:8888/auth",
listenHost: "localhost:8888",
listenPath: "/auth",
}
fmt.Println("To authenticate, go to the following URL:")
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
}

69
main.go
View File

@ -4,6 +4,7 @@ import (
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -43,46 +44,29 @@ func getConfigFilePath(filename string) string {
return filepath.Join(configDir, filename) return filepath.Join(configDir, filename)
} }
// readWriteAccessToken will store and retrieve access tokens for future use. // writeAccessToken writes the access token to a file for future use.
func readWriteAccessToken(accessToken string) (string, error) { func writeAccessToken(accessToken string) error {
tokenFile := getConfigFilePath("token") tokenFile := getConfigFilePath("token")
if accessToken != "" { if err := ioutil.WriteFile(tokenFile, []byte(accessToken), 0600); err != nil {
err := ioutil.WriteFile(tokenFile, []byte(accessToken), 0600) return fmt.Errorf("error writing access token %w", err)
if err != nil {
fmt.Println("Error writing access token")
} }
return accessToken, err return nil
} }
// readAccessToken retreive access token from a file
func readAccessToken() (string, error) {
tokenFile := getConfigFilePath("token")
content, err := ioutil.ReadFile(tokenFile) content, err := ioutil.ReadFile(tokenFile)
if err != nil { if err != nil {
fmt.Println("No token provided on command line or in file") return "", fmt.Errorf("error reading access token from file %w", err)
return "", err
} }
return string(content), nil return string(content), nil
} }
// createClient will return a Slack client with the provided access token.
// If that token is empty, it will try to get one from the config folder.
func createClient(accessToken string) (*slack.Client, error) {
var err error
accessToken, err = readWriteAccessToken(accessToken)
if err != nil {
fmt.Println("error reading access token")
return nil, err
}
client := slack.New(accessToken)
return client, nil
}
// readDurationArgs will attempt to find a duration within command line args rather than flags. // readDurationArgs will attempt to find a duration within command line args rather than flags.
// It will look for a prefixed duration. eg. "5m :cowboy: Howdy y'all" and a postfix duration // It will look for a prefixed duration. eg. "5m :cowboy: Howdy y'all" and a postfix duration
// following the word "for". eg. ":dancing: Dancing for 1h". // following the word "for". eg. ":dancing: Dancing for 1h".
@ -158,15 +142,40 @@ func readFlags() statusInfo {
} }
} }
func getAccessToken(accessToken string) (string, error) {
// If provided, save and return
if accessToken != "" {
return accessToken, writeAccessToken(accessToken)
}
// Try to get from stored file
accessToken, err := readAccessToken()
if accessToken != "" && err == nil {
// Successfully read from file
return accessToken, nil
}
// Begin auth process to fetch a new token
accessToken, err = authenticate()
if err == nil {
// Successful authentication, save the token
err = writeAccessToken(accessToken)
}
return accessToken, err
}
func main() { func main() {
args := readFlags() args := readFlags()
client, err := createClient(args.accessToken) accessToken, err := getAccessToken(args.accessToken)
if err != nil { if err != nil {
fmt.Println("error getting client") fmt.Println("error getting access token")
panic(err) log.Fatal(err)
} }
client := slack.New(accessToken)
err = client.SetUserCustomStatus(args.statusText, args.emoji, args.getExpirationTime()) err = client.SetUserCustomStatus(args.statusText, args.emoji, args.getExpirationTime())
if err != nil { if err != nil {
fmt.Println("error setting status") fmt.Println("error setting status")