diff --git a/cmd/login.go b/cmd/login.go index 3cec108..3ceba4e 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -91,7 +91,7 @@ func loginWithGit() error { // Step 2: Find SSH public key fmt.Print("Looking up SSH public key... ") - sshPubKey, err := auth.FindSSHPublicKey() + sshPubKey, _, err := auth.FindSSHPublicKey() if err != nil { fmt.Println("✗") return err diff --git a/cmd/signup.go b/cmd/signup.go index 916f663..1fd0fd3 100644 --- a/cmd/signup.go +++ b/cmd/signup.go @@ -11,24 +11,32 @@ import ( ) var ( - signupGit bool - signupOrg string + signupGit bool + signupOrg string + signupEmail string ) // signupWithGit authenticates using the Shell Auth flow with git email + SSH key. func signupWithGit() error { - // Step 1: Get git email - fmt.Print("Looking up git email... ") - email, err := auth.GetGitEmail() - if err != nil { - fmt.Println("✗") - return err + // Step 1: Get email + var email string + if signupEmail != "" { + email = signupEmail + fmt.Printf("Using email: %s\n", email) + } else { + fmt.Print("Looking up git email... ") + var err error + email, err = auth.GetGitEmail() + if err != nil { + fmt.Println("✗") + return err + } + fmt.Println(email) } - fmt.Println(email) // Step 2: Find SSH public key fmt.Print("Looking up SSH public key... ") - sshPubKey, err := auth.FindSSHPublicKey() + sshPubKey, sshKeyPath, err := auth.FindSSHPublicKey() if err != nil { fmt.Println("✗") return err @@ -131,8 +139,15 @@ func signupWithGit() error { return err } - if err := auth.SaveAPIKey(keyResp.APIKey); err != nil { - return fmt.Errorf("error saving API key: %w", err) + config, err := auth.LoadConfig() + if err != nil { + return fmt.Errorf("error loading config: %w", err) + } + config.APIKey = keyResp.APIKey + config.Email = email + config.SSHKeyPath = sshKeyPath + if err := auth.SaveConfig(config); err != nil { + return fmt.Errorf("error saving config: %w", err) } fmt.Printf("\n✓ Successfully authenticated with Vers (org: %s)\n", keyResp.OrgName) @@ -142,14 +157,15 @@ func signupWithGit() error { var signupCmd = &cobra.Command{ Use: "signup", Short: "Create a Vers account and authenticate", - Long: `Sign up for the Vers platform using your git email and SSH key. + Long: `Sign up for the Vers platform using your email and SSH key. By default, signup uses your git email and SSH public key to create an account. A verification email is sent — click the link and you're in. - vers signup Sign up with git email + SSH key (default) - vers signup --org myorg Pick org non-interactively (for scripts/agents) - vers signup --git=false Prompt for an API key instead + vers signup Sign up with git email + SSH key (default) + vers signup --email you@example.com Use a specific email instead of git config + vers signup --org myorg Pick org non-interactively (for scripts/agents) + vers signup --git=false Prompt for an API key instead If you already have an account, this will log you in.`, RunE: func(cmd *cobra.Command, args []string) error { @@ -183,4 +199,5 @@ func init() { rootCmd.AddCommand(signupCmd) signupCmd.Flags().BoolVar(&signupGit, "git", true, "Authenticate using your git email and SSH key (default: true)") signupCmd.Flags().StringVar(&signupOrg, "org", "", "Organization name (skips interactive selection)") + signupCmd.Flags().StringVar(&signupEmail, "email", "", "Email address (overrides git config user.email)") } diff --git a/internal/auth/auth.go b/internal/auth/auth.go index ecb9405..08268be 100644 --- a/internal/auth/auth.go +++ b/internal/auth/auth.go @@ -15,7 +15,9 @@ const DEFAULT_VERS_URL_STR = "https://api.vers.sh" // Config represents the structure of the .versrc file type Config struct { - APIKey string `json:"apiKey"` + APIKey string `json:"apiKey"` + Email string `json:"email,omitempty"` + SSHKeyPath string `json:"sshKeyPath,omitempty"` } // GetConfigPath returns the path to the .versrc file in the user's home directory diff --git a/internal/auth/shellauth.go b/internal/auth/shellauth.go index 1484d67..947c414 100644 --- a/internal/auth/shellauth.go +++ b/internal/auth/shellauth.go @@ -91,11 +91,11 @@ func GetGitEmail() (string, error) { } // FindSSHPublicKey finds the user's SSH public key, checking common locations. -// Returns the key contents (not the path). -func FindSSHPublicKey() (string, error) { +// Returns the key contents and the path where it was found. +func FindSSHPublicKey() (key string, keyPath string, err error) { home, err := os.UserHomeDir() if err != nil { - return "", fmt.Errorf("could not find home directory: %w", err) + return "", "", fmt.Errorf("could not find home directory: %w", err) } // Check common SSH key paths in preference order @@ -110,13 +110,26 @@ func FindSSHPublicKey() (string, error) { if err != nil { continue } - key := strings.TrimSpace(string(data)) - if key != "" { - return key, nil + k := strings.TrimSpace(string(data)) + if k != "" { + return k, path, nil } } - return "", fmt.Errorf("no SSH public key found — checked: %s\nGenerate one with: ssh-keygen -t ed25519", strings.Join(candidates, ", ")) + return "", "", fmt.Errorf("no SSH public key found — checked: %s\nGenerate one with: ssh-keygen -t ed25519", strings.Join(candidates, ", ")) +} + +// ReadSSHPublicKey reads an SSH public key from a specific path. +func ReadSSHPublicKey(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("could not read SSH public key at %s: %w", path, err) + } + key := strings.TrimSpace(string(data)) + if key == "" { + return "", fmt.Errorf("SSH public key at %s is empty", path) + } + return key, nil } // shellAuthBaseURL returns the base URL for shell auth endpoints.