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 @@ -188,7 +188,7 @@ func loginWithGit() error {
return err
}

if err := auth.SaveAPIKey(keyResp.APIKey); err != nil {
if err := auth.SaveAuth(keyResp.APIKey, keyResp.OrgName, keyResp.OrgID); err != nil {
return fmt.Errorf("error saving API key: %w", err)
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/signup.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ func signupWithGit() error {
config.APIKey = keyResp.APIKey
config.Email = email
config.SSHKeyPath = sshKeyPath
config.OrgName = keyResp.OrgName
config.OrgID = keyResp.OrgID
if err := auth.SaveConfig(config); err != nil {
return fmt.Errorf("error saving config: %w", err)
}
Expand Down
39 changes: 39 additions & 0 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ type Config struct {
APIKey string `json:"apiKey"`
Email string `json:"email,omitempty"`
SSHKeyPath string `json:"sshKeyPath,omitempty"`
// OrgName is the user's organization name (namespace).
// Persisted from the login/signup API response so that callers can
// compose canonical references (e.g. <org>/<repo>:<tag>) without
// an extra API round-trip.
OrgName string `json:"orgName,omitempty"`
// OrgID is the user's organization UUID, persisted alongside OrgName.
OrgID string `json:"orgID,omitempty"`
}

// GetConfigPath returns the path to the .versrc file in the user's home directory
Expand Down Expand Up @@ -105,6 +112,38 @@ func SaveAPIKey(apiKey string) error {
return SaveConfig(config)
}

// SaveAuth persists the API key plus org identity to the config file in a
// single write. Use this from the login/signup paths so that subsequent
// commands can read the user's org name without an extra API call.
func SaveAuth(apiKey, orgName, orgID string) error {
config, err := LoadConfig()
if err != nil {
return err
}

config.APIKey = apiKey
if orgName != "" {
config.OrgName = orgName
}
if orgID != "" {
config.OrgID = orgID
}
return SaveConfig(config)
}

// GetOrgName returns the user's org name, preferring the VERS_ORG env var
// over the persisted config value. Empty string if neither is set.
func GetOrgName() (string, error) {
if orgName := os.Getenv("VERS_ORG"); orgName != "" {
return orgName, nil
}
config, err := LoadConfig()
if err != nil {
return "", err
}
return config.OrgName, nil
}

// HasAPIKey checks if an API key is present in environment variable or config file
func HasAPIKey() (bool, error) {
// First check environment variable
Expand Down
68 changes: 68 additions & 0 deletions internal/auth/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,71 @@ func TestGetVMDomain(t *testing.T) {
})
}
}

func TestGetOrgName(t *testing.T) {
// Save and restore HOME / VERS_ORG so this test is hermetic.
origHome := os.Getenv("HOME")
origOrg := os.Getenv("VERS_ORG")
t.Cleanup(func() {
os.Setenv("HOME", origHome)
if origOrg == "" {
os.Unsetenv("VERS_ORG")
} else {
os.Setenv("VERS_ORG", origOrg)
}
})

tmp := t.TempDir()
os.Setenv("HOME", tmp)
os.Unsetenv("VERS_ORG")

// 1. No config file, no env var → empty.
got, err := GetOrgName()
if err != nil {
t.Fatalf("GetOrgName with no config: %v", err)
}
if got != "" {
t.Errorf("expected empty, got %q", got)
}

// 2. Persist via SaveAuth, read back.
if err := SaveAuth("test-key", "acme", "org-uuid-1"); err != nil {
t.Fatalf("SaveAuth: %v", err)
}
got, err = GetOrgName()
if err != nil {
t.Fatalf("GetOrgName after SaveAuth: %v", err)
}
if got != "acme" {
t.Errorf("expected acme, got %q", got)
}

// 3. Env var wins over persisted value.
os.Setenv("VERS_ORG", "override-org")
got, err = GetOrgName()
if err != nil {
t.Fatalf("GetOrgName with env override: %v", err)
}
if got != "override-org" {
t.Errorf("expected override-org, got %q", got)
}

// 4. Empty SaveAuth values don't clobber persisted ones.
os.Unsetenv("VERS_ORG")
if err := SaveAuth("new-key", "", ""); err != nil {
t.Fatalf("SaveAuth empty org: %v", err)
}
cfg, err := LoadConfig()
if err != nil {
t.Fatalf("LoadConfig: %v", err)
}
if cfg.APIKey != "new-key" {
t.Errorf("expected new-key, got %q", cfg.APIKey)
}
if cfg.OrgName != "acme" {
t.Errorf("expected acme preserved, got %q", cfg.OrgName)
}
if cfg.OrgID != "org-uuid-1" {
t.Errorf("expected org-uuid-1 preserved, got %q", cfg.OrgID)
}
}
Loading