Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 33 additions & 16 deletions cmd/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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)")
}
4 changes: 3 additions & 1 deletion internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 20 additions & 7 deletions internal/auth/shellauth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
Loading