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
7 changes: 4 additions & 3 deletions internal/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import (
)

const (
// DefaultCallbackPort is the port for the OAuth callback server
DefaultCallbackPort = 8080
// CallbackURL is the OAuth redirect URL that must match the Connected App configuration.
// No server listens on this — the browser shows an error and the user copies the URL.
CallbackURL = "http://localhost:8080/callback"

// ProductionLoginURL is the Salesforce production login endpoint
ProductionLoginURL = "https://login.salesforce.com"
Expand Down Expand Up @@ -42,7 +43,7 @@ func GetOAuthConfig(instanceURL, clientID string) *oauth2.Config {
AuthURL: instanceURL + "/services/oauth2/authorize",
TokenURL: instanceURL + "/services/oauth2/token",
},
RedirectURL: fmt.Sprintf("http://localhost:%d/callback", DefaultCallbackPort),
RedirectURL: CallbackURL,
Scopes: Scopes,
}
}
Expand Down
108 changes: 0 additions & 108 deletions internal/auth/callback.go

This file was deleted.

90 changes: 12 additions & 78 deletions internal/cmd/initcmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@ import (
"net/http"
"net/url"
"os"
"os/exec"
"runtime"
"strings"
"time"

"github.com/charmbracelet/huh"
"github.com/spf13/cobra"
Expand All @@ -25,7 +22,6 @@ var (
instanceURL string
clientID string
noVerify bool
noBrowser bool
)

// Register registers the init command with the parent command.
Expand Down Expand Up @@ -57,7 +53,6 @@ Prerequisites:
cmd.Flags().StringVar(&instanceURL, "instance-url", "", "Salesforce instance URL (e.g., login.salesforce.com)")
cmd.Flags().StringVar(&clientID, "client-id", "", "Connected App Consumer Key")
cmd.Flags().BoolVar(&noVerify, "no-verify", false, "Skip connectivity verification after setup")
cmd.Flags().BoolVar(&noBrowser, "no-browser", false, "Don't auto-open browser, just print URL")

return cmd
}
Expand Down Expand Up @@ -165,71 +160,32 @@ func runInit(cmd *cobra.Command, args []string) error {
authURL := auth.GetAuthURL(oauthConfig)

fmt.Println()
if noBrowser {
fmt.Println("Open this URL in your browser:")
} else {
fmt.Println("Opening browser for Salesforce login...")
fmt.Println()
fmt.Println("If browser doesn't open, visit:")
}
fmt.Println("Open this URL in your browser:")
fmt.Println()
fmt.Println(authURL)
fmt.Println()
fmt.Println("After clicking 'Allow', your browser will redirect to a localhost URL.")
fmt.Println("This will show an error - that's expected!")
fmt.Println()
fmt.Println("Copy the ENTIRE URL from your browser's address bar and paste it here,")
fmt.Println("or just paste the 'code' parameter value:")
fmt.Println()
fmt.Print("> ")

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
defer cancel()

resultChan, err := auth.StartCallbackServer(ctx, auth.DefaultCallbackPort)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Printf("Warning: Could not start callback server: %v\n", err)
fmt.Println("You'll need to manually copy the authorization code.")
}

if !noBrowser {
if err := openBrowser(authURL); err != nil {
fmt.Printf("Could not open browser: %v\n", err)
}
}

var code string
if resultChan != nil {
fmt.Println("Waiting for authorization...")
fmt.Println("(Or paste the authorization code or full redirect URL below)")
fmt.Println()

inputChan := make(chan string, 1)
go func() {
fmt.Print("> ")
input, _ := reader.ReadString('\n')
inputChan <- strings.TrimSpace(input)
}()

select {
case result := <-resultChan:
if result.Error != "" {
return fmt.Errorf("authorization failed: %s", result.Error)
}
code = result.Code
fmt.Println("Authorization received from callback.")
case input := <-inputChan:
code = extractAuthCode(input)
case <-ctx.Done():
return fmt.Errorf("authorization timed out")
}
} else {
fmt.Println("After authorizing, paste the authorization code or full redirect URL:")
fmt.Print("> ")
input, _ := reader.ReadString('\n')
code = extractAuthCode(strings.TrimSpace(input))
return fmt.Errorf("failed to read input: %w", err)
}

code := extractAuthCode(strings.TrimSpace(input))
if code == "" {
return fmt.Errorf("no authorization code received")
}

fmt.Println()
fmt.Println("Exchanging authorization code for tokens...")

ctx := context.Background()
token, err := auth.ExchangeAuthCode(ctx, oauthConfig, code)
if err != nil {
return fmt.Errorf("failed to exchange authorization code: %w", err)
Expand Down Expand Up @@ -295,25 +251,3 @@ func verifyConnectivity(instanceURL string) error {

return nil
}

// openBrowser opens the default browser to the given URL.
func openBrowser(url string) error {
var cmd string
var args []string

switch runtime.GOOS {
case "darwin":
cmd = "open"
args = []string{url}
case "linux":
cmd = "xdg-open"
args = []string{url}
case "windows":
cmd = "cmd"
args = []string{"/c", "start", url}
default:
return fmt.Errorf("unsupported platform")
}

return exec.Command(cmd, args...).Start()
}
24 changes: 23 additions & 1 deletion internal/cmd/initcmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,26 @@ func TestExtractAuthCode(t *testing.T) {
input: " abc123 ",
want: "abc123",
},
{
name: "localhost URL without port",
input: "http://localhost/?code=abc123xyz",
want: "abc123xyz",
},
{
name: "https localhost URL",
input: "https://localhost/?code=SecureCode456",
want: "SecureCode456",
},
{
name: "code with special characters",
input: "http://localhost:8080/callback?code=4/P-abc_123.xyz~456",
want: "4/P-abc_123.xyz~456",
},
{
name: "URL encoded code",
input: "http://localhost:8080/callback?code=4%2F0AQSTgQ",
want: "4/0AQSTgQ",
},
}

for _, tt := range tests {
Expand All @@ -63,5 +83,7 @@ func TestNewCommand(t *testing.T) {
assert.NotNil(t, cmd.Flags().Lookup("instance-url"))
assert.NotNil(t, cmd.Flags().Lookup("client-id"))
assert.NotNil(t, cmd.Flags().Lookup("no-verify"))
assert.NotNil(t, cmd.Flags().Lookup("no-browser"))

// --no-browser flag was removed (no more callback server or auto-browser-opening)
assert.Nil(t, cmd.Flags().Lookup("no-browser"))
}