package cmd import ( "context" "encoding/json" "fmt" "gogs.dmsc.dev/arp/arp_cli/client" "gogs.dmsc.dev/arp/arp_cli/config" "github.com/AlecAivazis/survey/v2" "github.com/urfave/cli/v3" ) // LoginCommand returns the login command func LoginCommand() *cli.Command { return &cli.Command{ Name: "login", Usage: "Authenticate with the ARP server", Description: `Login to an ARP server using email and password. This command will store the authentication token in ~/.arp_cli/config.json for use by subsequent commands. Examples: # Login with URL and email specified arp_cli login --url http://localhost:8080/query --email user@example.com # Login interactively (will prompt for all fields) arp_cli login`, Flags: []cli.Flag{ &cli.StringFlag{ Name: "url", Aliases: []string{"u"}, Usage: "ARP server URL", Sources: cli.EnvVars("ARP_URL"), }, &cli.StringFlag{ Name: "email", Aliases: []string{"e"}, Usage: "User email address", }, &cli.StringFlag{ Name: "password", Aliases: []string{"p"}, Usage: "User password (will prompt if not provided)", }, }, Action: doLogin, } } func doLogin(ctx context.Context, cmd *cli.Command) error { // Get URL serverURL := cmd.String("url") if serverURL == "" { cfg, err := config.Load() if err != nil { return fmt.Errorf("failed to load config: %w", err) } serverURL = cfg.ServerURL } // Prompt for URL if still empty if serverURL == "" { prompt := &survey.Input{ Message: "Server URL", Default: "http://localhost:8080", Help: "The ARP server URL (e.g., http://localhost:8080)", } if err := survey.AskOne(prompt, &serverURL, survey.WithValidator(survey.Required)); err != nil { return err } } // Ensure URL has the /query endpoint serverURL = ensureQueryEndpoint(serverURL) // Get email email := cmd.String("email") if email == "" { prompt := &survey.Input{ Message: "Email", Help: "Your user account email address", } if err := survey.AskOne(prompt, &email, survey.WithValidator(survey.Required)); err != nil { return err } } // Get password password := cmd.String("password") if password == "" { prompt := &survey.Password{ Message: "Password", Help: "Your user account password", } if err := survey.AskOne(prompt, &password, survey.WithValidator(survey.Required)); err != nil { return err } } // Perform login c := client.New(serverURL) query := ` mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { token user { id email roles { id name } } } }` variables := map[string]interface{}{ "email": email, "password": password, } resp, err := c.Mutation(query, variables) if err != nil { return fmt.Errorf("login failed: %w", err) } // Parse response var loginResp struct { Login struct { Token string `json:"token"` User struct { ID string `json:"id"` Email string `json:"email"` Roles []struct { ID string `json:"id"` Name string `json:"name"` } `json:"roles"` } `json:"user"` } `json:"login"` } if err := json.Unmarshal(resp.Data, &loginResp); err != nil { return fmt.Errorf("failed to parse response: %w", err) } // Save config cfg, err := config.Load() if err != nil { return fmt.Errorf("failed to load config: %w", err) } cfg.ServerURL = serverURL cfg.Token = loginResp.Login.Token cfg.UserEmail = loginResp.Login.User.Email if err := config.Save(cfg); err != nil { return fmt.Errorf("failed to save config: %w", err) } fmt.Printf("Logged in as %s\n", loginResp.Login.User.Email) if len(loginResp.Login.User.Roles) > 0 { fmt.Print("Roles: ") for i, role := range loginResp.Login.User.Roles { if i > 0 { fmt.Print(", ") } fmt.Print(role.Name) } fmt.Println() } return nil } // ensureQueryEndpoint ensures the URL ends with /query func ensureQueryEndpoint(url string) string { // Remove trailing slash if present for len(url) > 0 && url[len(url)-1] == '/' { url = url[:len(url)-1] } // Add /query if not already present if len(url) < 6 || url[len(url)-6:] != "/query" { url = url + "/query" } return url }