Working oauth flow
This commit is contained in:
parent
f2cec168f7
commit
05f700bc93
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@
|
|||||||
|
|
||||||
slack-status
|
slack-status
|
||||||
slack-status-cli
|
slack-status-cli
|
||||||
|
dist/
|
||||||
|
32
Makefile
Normal file
32
Makefile
Normal 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
106
auth.go
Normal 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
|
||||||
|
}
|
71
main.go
71
main.go
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user