package auth import ( "context" "fmt" "net/http" "net/url" "strings" "sync" "github.com/charmbracelet/huh" "github.com/charmbracelet/huh/spinner" "github.com/spf13/cobra" "tangled.sh/rockorager.dev/knit" "tangled.sh/rockorager.dev/knit/config" ) func Login(cmd *cobra.Command, args []string) error { var ( handle string appPassword string ) host := knit.DefaultHost handleInput := huh.NewInput(). Title("Handle"). Value(&handle) if err := handleInput.Run(); err != nil { return fmt.Errorf("getting handle: %w", err) } appPasswordInput := huh.NewInput(). Title("App Password"). EchoMode(huh.EchoModePassword). Value(&appPassword) if err := appPasswordInput.Run(); err != nil { return fmt.Errorf("getting app-password: %w", err) } ctx, stop := context.WithCancel(cmd.Context()) defer stop() wg := &sync.WaitGroup{} wg.Add(1) go func(wg *sync.WaitGroup) { spinner.New(). Context(ctx). Title("Authenticating..."). Run() wg.Done() }(wg) cookies, err := login(host, handle, appPassword) if err != nil { return fmt.Errorf("authenticating: %w", err) } if err := SaveCookies(cookies, knit.DefaultHost, handle); err != nil { return err } if err := config.AddHandleToHost(host, handle); err != nil { return err } stop() wg.Wait() fmt.Printf("\x1b[32m✔\x1b[m %s logged in to %s\r\n", handle, host) return nil } func login(host string, handle string, appPassword string) ([]*http.Cookie, error) { form := url.Values{} form.Set("handle", handle) form.Set("app_password", appPassword) u := url.URL{ Scheme: "https", Host: host, Path: "/login", } resp, err := http.Post( u.String(), "application/x-www-form-urlencoded", strings.NewReader(form.Encode()), ) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) } if len(resp.Cookies()) == 0 { return nil, fmt.Errorf("session cookie not found") } return resp.Cookies(), nil }