Skip to content

Commit

Permalink
token/cookie new auth flow
Browse files Browse the repository at this point in the history
  • Loading branch information
rusq committed Nov 11, 2024
1 parent 8b65cc7 commit 900991d
Show file tree
Hide file tree
Showing 13 changed files with 411 additions and 118 deletions.
70 changes: 49 additions & 21 deletions cmd/slackdump/internal/diag/eztest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"

"github.com/playwright-community/playwright-go"
Expand All @@ -29,34 +30,49 @@ You will see "OK" in the end if there were no issues, otherwise an error will
be printed and the test will be terminated.
`,
CustomFlags: true,
PrintFlags: true,
}

type ezResult struct {
Engine string `json:"engine,omitempty"`
HasToken bool `json:"has_token,omitempty"`
HasCookies bool `json:"has_cookies,omitempty"`
Err *string `json:"error,omitempty"`
Engine string `json:"engine,omitempty"`
HasToken bool `json:"has_token,omitempty"`
HasCookies bool `json:"has_cookies,omitempty"`
Err *string `json:"error,omitempty"`
Credentials *Credentials `json:"credentials,omitempty"`
}

type Credentials struct {
Token string `json:"token,omitempty"`
Cookies []*http.Cookie `json:"cookie,omitempty"`
}

type eztestOpts struct {
printCreds bool
wsp string
legacy bool
}

var eztestFlags eztestOpts

func init() {
CmdEzTest.Flag.Usage = func() {
fmt.Fprint(os.Stdout, "usage: slackdump tools eztest [flags]\n\nFlags:\n")
CmdEzTest.Flag.PrintDefaults()
}
CmdEzTest.Flag.BoolVar(&eztestFlags.printCreds, "p", false, "print credentials")
CmdEzTest.Flag.BoolVar(&eztestFlags.legacy, "legacy-browser", false, "run with playwright")
CmdEzTest.Flag.StringVar(&eztestFlags.wsp, "w", "", "Slack `workspace` to login to.")
}

func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error {
lg := logger.FromContext(ctx)

wsp := cmd.Flag.String("w", "", "Slack `workspace` to login to.")
legacy := cmd.Flag.Bool("legacy-browser", false, "run with playwright")

if err := cmd.Flag.Parse(args); err != nil {
base.SetExitStatus(base.SInvalidParameters)
return err
}

if *wsp == "" {
if eztestFlags.wsp == "" {
base.SetExitStatus(base.SInvalidParameters)
cmd.Flag.Usage()
return nil
Expand All @@ -66,10 +82,10 @@ func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error
res ezResult
)

if *legacy {
res = tryPlaywrightAuth(ctx, *wsp)
if eztestFlags.legacy {
res = tryPlaywrightAuth(ctx, eztestFlags.wsp, eztestFlags.printCreds)
} else {
res = tryRodAuth(ctx, *wsp)
res = tryRodAuth(ctx, eztestFlags.wsp, eztestFlags.printCreds)
}

enc := json.NewEncoder(os.Stdout)
Expand All @@ -88,28 +104,34 @@ func runEzLoginTest(ctx context.Context, cmd *base.Command, args []string) error
return nil
}

func tryPlaywrightAuth(ctx context.Context, wsp string) ezResult {
var res = ezResult{Engine: "playwright"}
func tryPlaywrightAuth(ctx context.Context, wsp string, populateCreds bool) ezResult {
var ret = ezResult{Engine: "playwright"}

if err := playwright.Install(&playwright.RunOptions{Browsers: []string{"firefox"}}); err != nil {
res.Err = ptr(fmt.Sprintf("playwright installation error: %s", err))
return res
ret.Err = ptr(fmt.Sprintf("playwright installation error: %s", err))
return ret
}

prov, err := auth.NewBrowserAuth(ctx, auth.BrowserWithWorkspace(wsp))
if err != nil {
res.Err = ptr(err.Error())
return res
ret.Err = ptr(err.Error())
return ret
}

res.HasToken = len(prov.SlackToken()) > 0
res.HasCookies = len(prov.Cookies()) > 0
return res
ret.HasToken = len(prov.SlackToken()) > 0
ret.HasCookies = len(prov.Cookies()) > 0
if populateCreds {
ret.Credentials = &Credentials{
Token: prov.SlackToken(),
Cookies: prov.Cookies(),
}
}
return ret
}

func ptr[T any](t T) *T { return &t }

func tryRodAuth(ctx context.Context, wsp string) ezResult {
func tryRodAuth(ctx context.Context, wsp string, populateCreds bool) ezResult {
ret := ezResult{Engine: "rod"}
prov, err := auth.NewRODAuth(ctx, auth.BrowserWithWorkspace(wsp))
if err != nil {
Expand All @@ -118,5 +140,11 @@ func tryRodAuth(ctx context.Context, wsp string) ezResult {
}
ret.HasCookies = len(prov.Cookies()) > 0
ret.HasToken = len(prov.SlackToken()) > 0
if populateCreds {
ret.Credentials = &Credentials{
Token: prov.SlackToken(),
Cookies: prov.Cookies(),
}
}
return ret
}
17 changes: 1 addition & 16 deletions cmd/slackdump/internal/export/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package export

import (
"context"
"errors"
"regexp"

"github.com/charmbracelet/huh"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
Expand Down Expand Up @@ -63,22 +61,9 @@ func (fl *exportFlags) configuration() cfgui.Configuration {
Value: fl.ExportToken,
Description: "File export token to append to each of the file URLs",
Inline: true,
Updater: updaters.NewString(&fl.ExportToken, "", false, validateToken),
Updater: updaters.NewString(&fl.ExportToken, "", false, structures.ValidateToken),
},
},
},
}
}

// tokenRe is a loose regular expression to match Slack API tokens.
// a - app, b - bot, c - client, e - export, p - legacy
var tokenRE = regexp.MustCompile(`xox[abcep]-[0-9]+-[0-9]+-[0-9]+-[0-9a-z]{64}`)

var errInvalidToken = errors.New("token must start with xoxa-, xoxb-, xoxc- or xoxe- and be followed by 4 numbers and 64 lowercase letters")

func validateToken(token string) error {
if !tokenRE.MatchString(token) {
return errInvalidToken
}
return nil
}
44 changes: 32 additions & 12 deletions cmd/slackdump/internal/ui/bubbles/menu/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"

"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/cfgui"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui/updaters"
Expand All @@ -29,9 +30,18 @@ type Model struct {
help help.Model

cursor int
last int
}

func New(title string, items []Item, preview bool) *Model {
var last = len(items) - 1
for i := last; i >= 0; i++ {
if !items[i].Separator {
break
}
last--
}

return &Model{
title: title,
items: items,
Expand All @@ -41,6 +51,8 @@ func New(title string, items []Item, preview bool) *Model {
focused: true,
preview: preview,
finishing: false,
cursor: 0,
last: last,
}
}

Expand Down Expand Up @@ -76,21 +88,29 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.Selected = m.items[m.cursor]
cmds = append(cmds, tea.Quit)
case key.Matches(msg, m.Keymap.Up):
for {
if m.cursor > 0 {
m.cursor--
}
if !m.items[m.cursor].Separator {
break
if m.cursor == 0 {
m.cursor = m.last
} else {
for {
if m.cursor > 0 {
m.cursor--
}
if !m.items[m.cursor].Separator {
break
}
}
}
case key.Matches(msg, m.Keymap.Down):
for {
if m.cursor < len(m.items)-1 {
m.cursor++
}
if !m.items[m.cursor].Separator {
break
if m.cursor == m.last {
m.cursor = 0
} else {
for {
if m.cursor < m.last {
m.cursor++
}
if !m.items[m.cursor].Separator {
break
}
}
}
case key.Matches(msg, m.Keymap.Select):
Expand Down
3 changes: 2 additions & 1 deletion cmd/slackdump/internal/ui/theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ func ThemeBase16Ext() *huh.Theme {
t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(black).Background(green)
t.Focused.SelectedPrefix = t.Focused.SelectedPrefix.Foreground(green)
t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(white)
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(white).Background(purple)
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(white).Background(green)
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(white).Background(black)
t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(cyan)

t.Focused.TextInput.Cursor.Foreground(purple)
t.Focused.TextInput.Placeholder.Foreground(gray)
Expand Down
34 changes: 34 additions & 0 deletions cmd/slackdump/internal/workspace/workspaceui/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package workspaceui

import (
"context"

"github.com/rusq/slackdump/v3/auth"
"github.com/rusq/slackdump/v3/auth/auth_ui"
)

type manager interface {
SaveProvider(workspace string, p auth.Provider) error
Select(workspace string) error
}

// createAndSelect creates a new workspace with the given provider and selects it.
// It returns the workspace name on success.
func createAndSelect(ctx context.Context, m manager, prov auth.Provider) (string, error) {
authInfo, err := prov.Test(ctx)
if err != nil {
return "", err
}

wsp, err := auth_ui.Sanitize(authInfo.URL)
if err != nil {
return "", err
}
if err := m.SaveProvider(wsp, prov); err != nil {
return "", err
}
if err := m.Select(wsp); err != nil {
return "", err
}
return wsp, nil
}
34 changes: 34 additions & 0 deletions cmd/slackdump/internal/workspace/workspaceui/dialogs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package workspaceui

import (
"context"
"fmt"

"github.com/charmbracelet/huh"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/ui"
)

func askRetry(ctx context.Context, name string, err error) (retry bool) {
var msg string = fmt.Sprintf("The following error occurred: %s", err)
if name != "" {
msg = fmt.Sprintf("Error creating workspace %q: %s", name, err)
}

if err := huh.NewForm(huh.NewGroup(
huh.NewConfirm().Title("Error Creating Workspace").
Description(msg).
Value(&retry).Affirmative("Retry").Negative("Cancel"),
)).WithTheme(ui.HuhTheme).RunWithContext(ctx); err != nil {
return false
}
return retry
}

func success(ctx context.Context, workspace string) error {
return huh.NewForm(huh.NewGroup(
huh.NewNote().Title("Great Success!").
Description(fmt.Sprintf("Workspace %q was added and selected.\n\n", workspace)).
Next(true).
NextLabel("Exit"),
)).WithTheme(ui.HuhTheme).RunWithContext(ctx)
}
Loading

0 comments on commit 900991d

Please sign in to comment.