Skip to content

Commit

Permalink
Merge pull request #347 from rusq/ui
Browse files Browse the repository at this point in the history
Auth UI
  • Loading branch information
rusq authored Nov 3, 2024
2 parents 2895662 + a66a25c commit 6aba025
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 278 deletions.
93 changes: 58 additions & 35 deletions auth/auth_ui/huh.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ func (h *Huh) RequestWorkspace(w io.Writer) (string, error) {
huh.NewInput().
Title("Enter Slack workspace name").
Value(&workspace).
Validate(valRequired).
Validate(valWorkspace).
Description("The workspace name is the part of the URL that comes before `.slack.com' in\nhttps://<workspace>.slack.com/. Both workspace name or URL are acceptable."),
)).Run()
if err != nil {
return "", err
}
return Sanitize(workspace)
return workspace, nil
}

func (*Huh) Stop() {}
Expand Down Expand Up @@ -61,7 +61,7 @@ func (m methodMenuItem) String() string {

var methods = []methodMenuItem{
{
"Manual",
"Interactive",
"Works with most authentication schemes, except Google.",
LInteractive,
},
Expand All @@ -71,18 +71,33 @@ var methods = []methodMenuItem{
LHeadless,
},
{
"User's Browser",
"User Browser",
"Loads your user profile, works with Google Auth",
LUserBrowser,
},
}

type LoginOpts struct {
Workspace string
Type LoginType
BrowserPath string
}

func (*Huh) RequestLoginType(w io.Writer) (LoginOpts, error) {
func valWorkspace(s string) error {
if err := valRequired(s); err != nil {
return err
}
_, err := Sanitize(s)
return err
}

func (*Huh) RequestLoginType(w io.Writer, workspace string) (LoginOpts, error) {
var ret = LoginOpts{
Workspace: workspace,
Type: LInteractive,
BrowserPath: "",
}

var opts = make([]huh.Option[LoginType], 0, len(methods))
for _, m := range methods {
opts = append(opts, huh.NewOption(m.String(), m.Type))
Expand All @@ -91,41 +106,49 @@ func (*Huh) RequestLoginType(w io.Writer) (LoginOpts, error) {
huh.NewOption("------", LoginType(-1)),
huh.NewOption("Cancel", LCancel),
)
var loginType LoginType
err := huh.NewForm(huh.NewGroup(
huh.NewSelect[LoginType]().Title("Select login type").
Options(opts...).
Value(&loginType).
Validate(valSepEaster()).
DescriptionFunc(func() string {
switch loginType {
case LInteractive:
return "Clean browser will open on a Slack Login page."
case LHeadless:
return "You will be prompted to enter your email and password, login is automated."
case LUserBrowser:
return "System browser will open on a Slack Login page."
case LCancel:
return "Cancel the login process."
default:
return ""
}
}, &loginType),
)).Run()
if err != nil {
return LoginOpts{Type: LCancel}, err
var fields []huh.Field
if workspace == "" {
fields = append(fields, huh.NewInput().
Title("Enter Slack workspace name").
Value(&ret.Workspace).
Validate(valWorkspace).
Description("The workspace name is the part of the URL that comes before `.slack.com' in\nhttps://<workspace>.slack.com/. Both workspace name or URL are acceptable."),
)
}

fields = append(fields, huh.NewSelect[LoginType]().
TitleFunc(func() string {
return fmt.Sprintf("Select login type for [%s]", ret.Workspace)
}, &ret.Workspace).
Options(opts...).
Value(&ret.Type).
Validate(valSepEaster()).
DescriptionFunc(func() string {
switch ret.Type {
case LInteractive:
return "Clean browser will open on a Slack Login page."
case LHeadless:
return "You will be prompted to enter your email and password, login is automated."
case LUserBrowser:
return "System browser will open on a Slack Login page."
case LCancel:
return "Cancel the login process."
default:
return ""
}
}, &ret.Type))
if err := huh.NewForm(huh.NewGroup(fields...)).Run(); err != nil {
return ret, err
}
if loginType == LUserBrowser {
if ret.Type == LUserBrowser {
path, err := chooseBrowser()
if err != nil {
return LoginOpts{Type: LCancel}, err
return ret, err
}
return LoginOpts{
Type: LUserBrowser,
BrowserPath: path,
}, err
ret.BrowserPath = path
return ret, err
}
return LoginOpts{Type: loginType}, nil
return ret, nil
}

func chooseBrowser() (string, error) {
Expand Down
33 changes: 16 additions & 17 deletions auth/rod.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"time"

"github.com/charmbracelet/huh/spinner"
"github.com/rusq/slackauth"

"github.com/rusq/slackdump/v3/auth/auth_ui"
Expand Down Expand Up @@ -60,11 +61,10 @@ func (ro rodOpts) slackauthOpts() []slackauth.Option {
}

type browserAuthUIExt interface {
BrowserAuthUI
// RequestLoginType should request the login type from the user and return
// one of the [auth_ui.LoginType] constants. The implementation should
// provide a way to cancel the login flow, returning [auth_ui.LoginCancel].
RequestLoginType(w io.Writer) (auth_ui.LoginOpts, error)
RequestLoginType(w io.Writer, workspace string) (auth_ui.LoginOpts, error)
// RequestCreds should request the user's email and password and return
// them.
RequestCreds(w io.Writer, workspace string) (email string, passwd string, err error)
Expand All @@ -89,23 +89,13 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) {
for _, opt := range opts {
opt(&r.opts)
}
if r.opts.workspace == "" {
var err error
r.opts.workspace, err = r.opts.ui.RequestWorkspace(os.Stdout)
if err != nil {
return r, err
}
if r.opts.workspace == "" {
return r, fmt.Errorf("workspace cannot be empty")
}
}
if wsp, err := auth_ui.Sanitize(r.opts.workspace); err != nil {
return r, err
} else {
r.opts.workspace = wsp
}

resp, err := r.opts.ui.RequestLoginType(os.Stdout)
resp, err := r.opts.ui.RequestLoginType(os.Stdout, r.opts.workspace)
if err != nil {
return r, err
}
Expand All @@ -117,7 +107,7 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) {
}

cl, err := slackauth.New(
r.opts.workspace,
resp.Workspace,
sopts...,
)
if err != nil {
Expand All @@ -137,14 +127,13 @@ func NewRODAuth(ctx context.Context, opts ...Option) (RodAuth, error) {
return r, err
}
case auth_ui.LHeadless:
sp, err = headlessFlow(ctx, cl, r.opts.workspace, r.opts.ui)
sp, err = headlessFlow(ctx, cl, resp.Workspace, r.opts.ui)
if err != nil {
return r, err
}
case auth_ui.LCancel:
return r, ErrCancelled
}

lg.Printf("✅ authenticated (time taken: %s)", time.Since(t))

return RodAuth{
Expand All @@ -163,12 +152,22 @@ func headlessFlow(ctx context.Context, cl *slackauth.Client, workspace string, u
if password == "" {
return sp, fmt.Errorf("password cannot be empty")
}
logger.FromContext(ctx).Println("⏳ Logging in to Slack, depending on your connection speed, it will take 25-40 seconds...")

sctx, cancel := context.WithCancel(ctx)
defer cancel()
go func() {
_ = spinner.New().
Type(spinner.Dots).
Title("Logging in to Slack, it will take 25-40 seconds").
Context(sctx).
Run()
}()

var loginErr error
sp.Token, sp.Cookie, loginErr = cl.Headless(ctx, username, password)
if loginErr != nil {
return sp, loginErr
}

return
}
9 changes: 6 additions & 3 deletions cmd/slackdump/internal/diag/uninstall.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"path/filepath"

"github.com/rusq/slackauth"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/diag/info"
"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
Expand Down Expand Up @@ -87,12 +88,14 @@ func uninstallPlaywright(ctx context.Context, si info.PwInfo) error {
return nil
}

func uninstallRod(ctx context.Context, si info.RodInfo) error {
func uninstallRod(_ context.Context, si info.RodInfo) error {
if si.Path == "" {
return errors.New("unable to determine rod browser path")
}
lg := logger.FromContext(ctx)
lg.Printf("Deleting %s", si.Path)
lg := cfg.Log
lg.Printf("Deleting Incogniton Browser...")
_ = slackauth.RemoveBrowser() // just to make sure.
lg.Printf("Deleting %s...", si.Path)
if err := os.RemoveAll(si.Path); err != nil {
return fmt.Errorf("failed to remove the rod browser: %w", err)
}
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.1.2
github.com/charmbracelet/huh v0.6.0
github.com/charmbracelet/huh/spinner v0.0.0-20241028115900-20a4d21717a8
github.com/charmbracelet/lipgloss v1.0.0
github.com/davecgh/go-spew v1.1.1
github.com/enescakir/emoji v1.0.0
Expand All @@ -28,6 +29,7 @@ require (
github.com/rusq/rbubbles v0.0.2
github.com/rusq/slack v0.9.6-0.20240712095442-5a0e2e405a99
github.com/rusq/slackauth v0.5.1
github.com/rusq/tagops v0.0.2
github.com/rusq/tracer v1.0.1
github.com/schollz/progressbar/v3 v3.17.0
github.com/stretchr/testify v1.9.0
Expand Down
Loading

0 comments on commit 6aba025

Please sign in to comment.