Skip to content

Commit

Permalink
Merge pull request #196 from rusq/defork
Browse files Browse the repository at this point in the history
Use slack library directly, remove replace
  • Loading branch information
rusq authored Mar 4, 2023
2 parents 81ecf25 + 73deffc commit b92c821
Show file tree
Hide file tree
Showing 25 changed files with 811 additions and 346 deletions.
59 changes: 41 additions & 18 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,46 @@
package auth

import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"runtime/trace"
"strings"

"github.com/rusq/chttp"
"github.com/slack-go/slack"
)

// Type is the auth type.
//
//go:generate stringer -type Type -linecomment
type Type uint8

// All supported auth types.
const (
TypeInvalid Type = iota
TypeValue
TypeCookieFile
TypeBrowser
TypeInvalid Type = iota // Invalid
TypeValue // Value
TypeCookieFile // Cookie File
TypeBrowser // EZ-Login 3000
)

// Provider is the Slack Authentication provider.
//
//go:generate mockgen -destination ../internal/mocks/mock_auth/mock_auth.go github.com/rusq/slackdump/v2/auth Provider
type Provider interface {
// SlackToken should return the Slack Token value.
SlackToken() string
// Cookies should returns a set of Slack Session cookies.
Cookies() []http.Cookie
// Cookies should return a set of Slack Session cookies.
Cookies() []*http.Cookie
// Type returns the auth type.
Type() Type
// Validate should return error, in case the token or cookies cannot be
// retrieved.
Validate() error
// Test tests if credentials are valid.
Test(ctx context.Context) error
}

var (
Expand All @@ -39,7 +50,7 @@ var (

type simpleProvider struct {
Token string
Cookie []http.Cookie
Cookie []*http.Cookie
}

func (c simpleProvider) Validate() error {
Expand All @@ -56,19 +67,10 @@ func (c simpleProvider) SlackToken() string {
return c.Token
}

func (c simpleProvider) Cookies() []http.Cookie {
func (c simpleProvider) Cookies() []*http.Cookie {
return c.Cookie
}

// deref dereferences []*T to []T.
func deref[T any](cc []*T) []T {
var ret = make([]T, len(cc))
for i := range cc {
ret[i] = *cc[i]
}
return ret
}

// Load deserialises JSON data from reader and returns a ValueAuth, that can
// be used to authenticate Slackdump. It will return ErrNoToken or
// ErrNoCookie if the authentication information is missing.
Expand All @@ -88,7 +90,7 @@ func Save(w io.Writer, p Provider) error {
return err
}

var s = simpleProvider{
s := simpleProvider{
Token: p.SlackToken(),
Cookie: p.Cookies(),
}
Expand All @@ -101,6 +103,27 @@ func Save(w io.Writer, p Provider) error {
return nil
}

// IsClientToken returns true if the tok is a web-client token.
func IsClientToken(tok string) bool {
return strings.HasPrefix(tok, "xoxc-")
}

// TestAuth attempts to authenticate with the given provider. It will return
// AuthError if failed.
func (s simpleProvider) Test(ctx context.Context) error {
ctx, task := trace.NewTask(ctx, "TestAuth")
defer task.End()

httpCl, err := chttp.New("https://slack.com", s.Cookies())
if err != nil {
return err
}
cl := slack.New(s.Token, slack.OptionHTTPClient(httpCl))

region := trace.StartRegion(ctx, "AuthTestContext")
defer region.End()
if _, err := cl.AuthTestContext(ctx); err != nil {
return &Error{Err: err}
}
return nil
}
21 changes: 21 additions & 0 deletions auth/auth_error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package auth

import "fmt"

// Error is the error returned by New, the underlying Err contains
// an API error returned by slack.AuthTest call.
type Error struct {
Err error
}

func (ae *Error) Error() string {
return fmt.Sprintf("failed to authenticate: %s", ae.Err)
}

func (ae *Error) Unwrap() error {
return ae.Err
}

func (ae *Error) Is(target error) bool {
return target == ae.Err
}
79 changes: 79 additions & 0 deletions auth/auth_error_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package auth

import (
"errors"
"fmt"
"testing"
)

var errSample = errors.New("test error")

func TestAuthError_Unwrap(t *testing.T) {
type fields struct {
Err error
}
tests := []struct {
name string
fields fields
wantErr error
}{
{
"unwrap unwraps properly",
fields{Err: errSample},
errSample,
},
{
"multilevel wrap",
fields{Err: fmt.Errorf("blah: %w", errSample)},
errSample,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ae := &Error{
Err: tt.fields.Err,
}
if err := ae.Unwrap(); (err != nil) && !errors.Is(err, tt.wantErr) {
t.Errorf("AuthError.Unwrap() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestAuthError_Is(t *testing.T) {
type fields struct {
Err error
}
type args struct {
target error
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
"is correctly compares underlying error",
fields{Err: errSample},
args{errSample},
true,
},
{
"not matching error returns false",
fields{Err: errors.New("not me bro")},
args{errSample},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ae := &Error{
Err: tt.fields.Err,
}
if got := ae.Is(tt.args.target); got != tt.want {
t.Errorf("AuthError.Is() = %v, want %v", got, tt.want)
}
})
}
}
10 changes: 5 additions & 5 deletions auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestLoad(t *testing.T) {
{
"loads valid data",
args{strings.NewReader(`{"Token":"token_value","Cookie":[{"Name":"d","Value":"abc","Path":"","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":false,"SameSite":0,"Raw":"","Unparsed":null}]}`)},
ValueAuth{simpleProvider{Token: "token_value", Cookie: []http.Cookie{
ValueAuth{simpleProvider{Token: "token_value", Cookie: []*http.Cookie{
{Name: "d", Value: "abc"},
}}},
false,
Expand Down Expand Up @@ -66,29 +66,29 @@ func TestSave(t *testing.T) {
}{
{
"all info present",
args{ValueAuth{simpleProvider{Token: "token_value", Cookie: []http.Cookie{
args{ValueAuth{simpleProvider{Token: "token_value", Cookie: []*http.Cookie{
{Name: "d", Value: "abc"},
}}}},
`{"Token":"token_value","Cookie":[{"Name":"d","Value":"abc","Path":"","Domain":"","Expires":"0001-01-01T00:00:00Z","RawExpires":"","MaxAge":0,"Secure":false,"HttpOnly":false,"SameSite":0,"Raw":"","Unparsed":null}]}` + "\n",
false,
},
{
"token missing",
args{ValueAuth{simpleProvider{Token: "", Cookie: []http.Cookie{
args{ValueAuth{simpleProvider{Token: "", Cookie: []*http.Cookie{
{Name: "d", Value: "abc"},
}}}},
"",
true,
},
{
"cookies missing on client token",
args{ValueAuth{simpleProvider{Token: "xoxc-blah", Cookie: []http.Cookie{}}}},
args{ValueAuth{simpleProvider{Token: "xoxc-blah", Cookie: []*http.Cookie{}}}},
"",
true,
},
{
"cookies missing on non-client token",
args{ValueAuth{simpleProvider{Token: "xoxp-blah", Cookie: []http.Cookie{}}}},
args{ValueAuth{simpleProvider{Token: "xoxp-blah", Cookie: []*http.Cookie{}}}},
`{"Token":"xoxp-blah","Cookie":[]}
`,
false,
Expand Down
45 changes: 18 additions & 27 deletions auth/browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,46 @@ var defaultFlow = &auth_ui.Survey{}

type BrowserAuth struct {
simpleProvider
flow BrowserAuthUI
opts browserOpts
}

type browserOpts struct {
workspace string
browser browser.Browser
flow BrowserAuthUI
}

type BrowserAuthUI interface {
RequestWorkspace(w io.Writer) (string, error)
Stop()
}

type BrowserOption func(*BrowserAuth)

func BrowserWithAuthFlow(flow BrowserAuthUI) BrowserOption {
return func(ba *BrowserAuth) {
if flow == nil {
return
}
ba.flow = flow
}
}

func BrowserWithWorkspace(name string) BrowserOption {
return func(ba *BrowserAuth) {
ba.workspace = name
}
}

func NewBrowserAuth(ctx context.Context, opts ...BrowserOption) (BrowserAuth, error) {
func NewBrowserAuth(ctx context.Context, opts ...Option) (BrowserAuth, error) {
var br = BrowserAuth{
flow: defaultFlow,
opts: browserOpts{
flow: defaultFlow,
browser: browser.Bfirefox,
},
}
for _, opt := range opts {
opt(&br)
opt(&options{browserOpts: &br.opts})
}

if br.workspace == "" {
if br.opts.workspace == "" {
var err error
br.workspace, err = br.flow.RequestWorkspace(os.Stdout)
br.opts.workspace, err = br.opts.flow.RequestWorkspace(os.Stdout)
if err != nil {
return br, err
}
defer br.flow.Stop()
defer br.opts.flow.Stop()
}
if wsp, err := sanitize(br.workspace); err != nil {
if wsp, err := sanitize(br.opts.workspace); err != nil {
return br, err
} else {
br.workspace = wsp
br.opts.workspace = wsp
}

auther, err := browser.New(br.workspace)
auther, err := browser.New(br.opts.workspace, browser.OptBrowser(br.opts.browser))
if err != nil {
return br, err
}
Expand Down
Loading

0 comments on commit b92c821

Please sign in to comment.