diff --git a/cli/cmd/auth_login.go b/cli/cmd/auth_login.go index 1f24521..9209865 100644 --- a/cli/cmd/auth_login.go +++ b/cli/cmd/auth_login.go @@ -2,45 +2,108 @@ package cmd import ( "bufio" - "errors" + "encoding/json" "fmt" "os" + "os/exec" + "path/filepath" + "runtime" "strings" ) -// TODO: What credentials are we taking? +// TODO: Update with real auth URL +const authUrl = "https://example.com/login" + type LoginCmd struct { } -func verifyUser(username, password string) error { - // Placeholder for actual authentication logic - if username == "admin" && password == "password" { - return nil +func openUrl() error { + + var cmd *exec.Cmd + url := authUrl + switch runtime.GOOS { + case "darwin": + cmd = exec.Command("open", url) + case "windows": + cmd = exec.Command("rundll32", "url.dll,FileProtocolHandler", url) + default: // Linux, BSD, etc. + cmd = exec.Command("xdg-open", url) + } + + return cmd.Start() +} + +func saveTokenToConfig(ctx *AppContext, token string) error { + configPath := defaultConfigPath() + if err := os.MkdirAll(filepath.Dir(configPath), 0o755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + if ctx.Config == nil { + ctx.Config = &Config{} + } + ctx.Config.AccessToken = token + + data, err := json.MarshalIndent(ctx.Config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) } - return errors.New("invalid credentials") + if err := os.WriteFile(configPath, data, 0o600); err != nil { + return fmt.Errorf("failed to write config file: %w", err) + } + fmt.Println("Token saved:", token) + return nil } -// TODO: Figure out how to handle password input without exposing it in the terminal historyn (go get golang.org/x/term) -// TODO: Where are we storing auth token? Are we getting JWT? +func getLongLivedToken(shortLivedToken string) (string, error) { + // Placeholder for actual implementation to exchange short-lived token for long-lived token + // In a real scenario, this would involve making an HTTP request to the auth server + return shortLivedToken + "_long_lived", nil +} -func (l *LoginCmd) Run() error { +func (l *LoginCmd) Run(ctx *AppContext) error { // mist auth login + if ctx.Config != nil && ctx.Config.AccessToken != "" { - fmt.Print("Username: ") + // Already logged in, ask if they want to re-login + fmt.Println("Already logged in with token:", ctx.Config.AccessToken) + fmt.Print("Re-enter token? (y/N): ") + reader := bufio.NewReader(os.Stdin) + answer, _ := reader.ReadString('\n') + answer = strings.TrimSpace(strings.ToLower(answer)) + if answer != "y" && answer != "yes" { + fmt.Println("Aborting login.") + return nil + } + } + + fmt.Println("Opening browser for authentication...") + fmt.Printf("If your browser didn't open, click here: \033]8;;%s\033\\%s\033]8;;\033\\\n", authUrl, authUrl) + + err := openUrl() + if err != nil { + fmt.Println("Error opening browser:", err) + return err + } + fmt.Print("token: ") reader := bufio.NewReader(os.Stdin) - username, _ := reader.ReadString('\n') - username = strings.TrimSpace(strings.ToLower(username)) + token, _ := reader.ReadString('\n') + token = strings.TrimSpace(token) + + token, err = getLongLivedToken(token) + if err != nil { + fmt.Println("Error obtaining long-lived token:", err) + return err + } - fmt.Print("Password: ") - password, _ := reader.ReadString('\n') - password = strings.TrimSpace(strings.ToLower(password)) - err := verifyUser(username, password) + err = saveTokenToConfig(ctx, token) if err != nil { - fmt.Println("Error during authentication:", err) + fmt.Println("Error during token saving") + return err } - fmt.Println("Logging in with username:", username) + fmt.Println("Saved token to config") return nil } diff --git a/cli/cmd/config.go b/cli/cmd/config.go index acb9ba8..5869954 100644 --- a/cli/cmd/config.go +++ b/cli/cmd/config.go @@ -2,28 +2,26 @@ package cmd import "fmt" - -// Config flags -type ConfigCmd struct{ +// Config flags +type ConfigCmd struct { DefaultCluster string `help:"Set the default compute cluster." optional: ""` - Show bool `help:"Show current configuration."` - + Show bool `help:"Show current configuration."` } -func (h *ConfigCmd) Run() error { - // Some dummy config; Call API or something +func (h *ConfigCmd) Run(ctx *AppContext) error { + // Some dummy config; Call API or something defaultConfig := map[string]string{ "defaultCluster": "AMD-cluster-1", - "region": "us-east", + "region": "us-east", } - if h.Show && h.DefaultCluster!= "" { + if h.Show && h.DefaultCluster != "" { fmt.Printf("Cannot use --show and --default-cluster together") return nil } if h.DefaultCluster != "" { - // This is not actually set. + // This is not actually set. fmt.Printf("Setting default cluster to: %s\n", h.DefaultCluster) return nil } @@ -36,7 +34,7 @@ func (h *ConfigCmd) Run() error { return nil } - fmt.Println("No config action specified. Use --help for options.") + fmt.Println("No config action specified. Use --help for options.") return nil } diff --git a/cli/cmd/job_cancel.go b/cli/cmd/job_cancel.go index 446787a..949a885 100644 --- a/cli/cmd/job_cancel.go +++ b/cli/cmd/job_cancel.go @@ -8,13 +8,12 @@ import ( "time" ) - type JobCancelCmd struct { - ID string `arg:"" help:"ID of job you want to cancel"` + ID string `arg:"" help:"ID of job you want to cancel"` } -func (c *JobCancelCmd) Run() error { - // Same Mock data from job list. +func (c *JobCancelCmd) Run(ctx *AppContext) error { + // Same Mock data from job list. jobs := []Job{ { ID: "ID:1", @@ -39,36 +38,35 @@ func (c *JobCancelCmd) Run() error { }, } - // Check if job exists + // Check if job exists if !jobExists(jobs, c.ID) { fmt.Printf("%s does not exist in your jobs.\n", c.ID) fmt.Printf("Use the command \"job list\" for your list of jobs.") - return nil + return nil } - fmt.Printf("Are you sure you want to cancel %s? (y/n): \n", c.ID) reader := bufio.NewReader(os.Stdin) input, _ := reader.ReadString('\n') input = strings.TrimSpace(strings.ToLower(input)) - if input == "y" || input == "yes"{ + if input == "y" || input == "yes" { fmt.Println("Confirmed, proceeding job cancellation....") - // Confirmed job cancellation logic + // Confirmed job cancellation logic fmt.Println("Cancelling job with ID:", c.ID) fmt.Printf("Job cancelled successfully with ID: %s\n", c.ID) return nil - } else if input == "n" || input == "no"{ + } else if input == "n" || input == "no" { fmt.Println("Cancelled.") return nil - } else{ + } else { fmt.Println("Invalid response.") return nil } - return nil + return nil -} \ No newline at end of file +} diff --git a/cli/cmd/job_list.go b/cli/cmd/job_list.go index 118df0e..9e4787d 100644 --- a/cli/cmd/job_list.go +++ b/cli/cmd/job_list.go @@ -19,7 +19,7 @@ type Job struct { CreatedAt time.Time } -func (l *ListCmd) Run() error { +func (l *ListCmd) Run(ctx *AppContext) error { // Mock data - pull from API in real implementation jobs := []Job{ { diff --git a/cli/cmd/job_status.go b/cli/cmd/job_status.go index cff9187..20634c8 100644 --- a/cli/cmd/job_status.go +++ b/cli/cmd/job_status.go @@ -11,7 +11,7 @@ type JobStatusCmd struct { ID string `arg:"" help:"The ID of the job to check the status for"` } -func (j *JobStatusCmd) Run() error { +func (j *JobStatusCmd) Run(ctx *AppContext) error { // Mock data - pull from API in real implementation jobs := []Job{{ ID: "ID:1", diff --git a/cli/cmd/job_submit.go b/cli/cmd/job_submit.go index d090934..22b793f 100644 --- a/cli/cmd/job_submit.go +++ b/cli/cmd/job_submit.go @@ -12,8 +12,7 @@ type JobSubmitCmd struct { Compute string `help:"Type of compute required for the job: AMD|TT|CPU" default:"AMD"` } - -func (j *JobSubmitCmd) Run() error { +func (j *JobSubmitCmd) Run(ctx *AppContext) error { // mist job submit