Something went wrong. Try again.
An unofficial terminal client for Tangled, optimized for humans and agents. tgcli.wisp.place
cli atproto go tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190package cli
import ( "context" "fmt" "io" "net" "net/http" "os" "os/exec" "runtime" "strings"
"github.com/alyraffauf/tg/internal/app" "github.com/spf13/cobra")
func newAuthLoginCommand(service *app.Service) *cobra.Command { var passwordStdin bool var useInsecureFileStore bool
command := &cobra.Command{ Use: "login <handle> [app-password]", Short: "Log in with OAuth or an app password", Long: `Log in with OAuth. For login without a browser, pass an app password.`, Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { identifier := args[0] password, usePassword, err := loginPassword(args, passwordStdin, cmd.InOrStdin()) if err != nil { return err } if useInsecureFileStore && !usePassword { return fmt.Errorf("--insecure requires an app password; pass one as the second argument or use --password-stdin") } if usePassword { if err := service.LoginWithPassword(cmd.Context(), identifier, password, useInsecureFileStore); err != nil { return err } did, err := service.CurrentDID(cmd.Context()) if err != nil { fmt.Fprintln(cmd.ErrOrStderr(), "Login completed but session could not be confirmed.") return err } renderAuthLogin(cmd.OutOrStdout(), did) return nil }
server, resultChannel, err := runCallbackServer(service) if err != nil { return err } defer server.Shutdown(context.Background())
ctx := cmd.Context() loginURL, err := service.StartLogin(ctx, identifier) if err != nil { service.CancelLogin() return err } defer service.CancelLogin()
fmt.Fprintln(cmd.OutOrStdout(), "Opening browser to complete login...") if err := openBrowser(loginURL); err != nil { fmt.Fprintf(cmd.ErrOrStderr(), "Could not open browser. Open this URL manually:\n%s\n", loginURL) }
select { case err := <-resultChannel: if err != nil { return err } did, err := service.CurrentDID(ctx) if err != nil { fmt.Fprintln(cmd.ErrOrStderr(), "Login completed but session could not be confirmed.") return err } renderAuthLogin(cmd.OutOrStdout(), did) return nil case <-ctx.Done(): return ctx.Err() } }, } command.Flags().BoolVar(&passwordStdin, "password-stdin", false, "Read the app password from standard input") command.Flags().BoolVar(&useInsecureFileStore, "insecure", false, "Store credentials in ~/.local/share/tg/credentials.json instead of the system keyring. Requires an app password.") return command}
func renderAuthLogin(writer io.Writer, did string) { styles := newAuthOutputStyles(isTerminal(writer)) writeAuthHeading(writer, styles.active, "✓", "Logged in") writeAuthDetail(writer, styles, "DID", styles.dim.Render(did))}
func loginPassword(args []string, fromStdin bool, stdin io.Reader) (string, bool, error) { if !fromStdin { if len(args) < 2 { return "", false, nil } return args[1], true, nil } if len(args) == 2 { return "", false, fmt.Errorf("app password argument and --password-stdin cannot be used together") } data, err := io.ReadAll(stdin) if err != nil { return "", false, fmt.Errorf("read app password from stdin: %w", err) } password := strings.TrimSpace(string(data)) if password == "" { return "", false, fmt.Errorf("app password from stdin is empty") } return password, true, nil}
func runCallbackServer(service *app.Service) (*http.Server, <-chan error, error) { resultChannel := make(chan error, 1)
listener, err := net.Listen("tcp", oauthCallbackHost+":0") if err != nil { return nil, nil, fmt.Errorf("bind callback listener: %w", err) }
// Register the actual bound port as the OAuth redirect URI before starting // the login flow. RFC 8252 loopback redirects allow any port; binding an // ephemeral one avoids clashes with other local services. callbackURL := "http://" + listener.Addr().String() + oauthCallbackPath service.SetOAuthCallbackURL(callbackURL)
serveMux := http.NewServeMux() serveMux.HandleFunc(oauthCallbackPath, func(w http.ResponseWriter, r *http.Request) { if err := service.FinishLogin(r.Context(), r.URL.Query()); err != nil { resultChannel <- err http.Error(w, err.Error(), http.StatusBadRequest) return } resultChannel <- nil fmt.Fprintln(w, "Authenticated successfully. You can close this tab.") })
server := &http.Server{ Handler: serveMux, }
go func() { if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { resultChannel <- fmt.Errorf("callback server: %w", err) } }()
return server, resultChannel, nil}
func openBrowser(url string) error { var cmd string var args []string
switch runtime.GOOS { case "darwin": cmd = "open" args = []string{url} case "windows": cmd = "cmd" args = []string{"/c", "start", url} default: if isWSL() { cmd = "powershell.exe" args = []string{"-NoProfile", "-Command", "Start-Process -FilePath $args[0]", url} } else { cmd = "xdg-open" args = []string{url} } }
return exec.Command(cmd, args...).Start()}
func isWSL() bool { if os.Getenv("WSL_DISTRO_NAME") != "" { return true } data, err := os.ReadFile("/proc/sys/kernel/osrelease") if err != nil { return false } s := strings.ToLower(string(data)) return strings.Contains(s, "microsoft") || strings.Contains(s, "wsl")}