From f1f303d61d1d7d537b4563c7517ce4c161f9bc38 Mon Sep 17 00:00:00 2001 From: Beny Dishon K Date: Fri, 31 Jul 2026 17:53:29 +0530 Subject: [PATCH] fix(auth): preserve the session domain during automatic re-login EstablishUserLoginSession re-execs the CLI as 'login --silent', forwarding only --silent. The child resolves its domain from scratch, and because usePresetDomain deliberately ignores default cloud URLs unless --domain was set on that invocation, an expired EU Cloud session falls through to the region prompt, opens https://app.infisical.com/login, and on completion rewrites LoggedInUserDomain to US. Every subsequent command then targets the wrong region. Forwards the domain the parent already resolved. Because the child now sees an explicit --domain, usePresetDomain accepts it even for EU/US cloud URLs, so no change to that function is needed and the explicit login path is untouched. Fixes all callers of EstablishUserLoginSession at once. The bare US default is deliberately not forwarded: it is what an unconfigured invocation looks like, and forwarding it would silently skip the region prompt on first login. Fixes #312 --- packages/util/auth.go | 42 +++++++++++++++- packages/util/auth_test.go | 98 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 packages/util/auth_test.go diff --git a/packages/util/auth.go b/packages/util/auth.go index a40f6022..bf7300b9 100644 --- a/packages/util/auth.go +++ b/packages/util/auth.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" + "github.com/Infisical/infisical-merge/packages/config" infisicalSdk "github.com/infisical/go-sdk" "github.com/rs/zerolog/log" "github.com/spf13/cobra" @@ -60,6 +61,35 @@ func IsAuthMethodValid(authMethod string, allowUserAuth bool) (isValid bool, str return false, "" } +// resolveReLoginDomain decides which domain the automatic re-login child process +// should be pinned to, or returns "" to leave the child's normal domain +// resolution (and its interactive region prompt) untouched. +// +// storedDomain is LoggedInUserDomain from the config file, i.e. the instance +// whose session just expired, so it wins. resolvedURL is config.INFISICAL_URL as +// already resolved by the root command from --domain, INFISICAL_DOMAIN or +// .infisical.json; it is only forwarded when it differs from the built-in US +// default, since that default is what an unconfigured invocation looks like and +// forwarding it would silently skip the region prompt for first-time logins. +func resolveReLoginDomain(storedDomain string, resolvedURL string) string { + if storedDomain != "" { + return storedDomain + } + if resolvedURL != "" && resolvedURL != AppendAPIEndpoint(INFISICAL_DEFAULT_US_URL) { + return resolvedURL + } + return "" +} + +// buildReLoginArgs returns the argv for the re-login child process. +func buildReLoginArgs(domain string) []string { + args := []string{"login", "--silent"} + if domain != "" { + args = append(args, "--domain", domain) + } + return args +} + // EstablishUserLoginSession handles the login flow to either create a new session or restore an expired one. // It returns fresh user details if login is successful. func EstablishUserLoginSession() LoggedInUserDetails { @@ -70,8 +100,18 @@ func EstablishUserLoginSession() LoggedInUserDetails { PrintErrorMessageAndExit(fmt.Sprintf("Failed to determine executable path: %v", err)) } + // The child process resolves its own domain from scratch and would otherwise + // fall back to US Cloud, so forward the domain this session was using. + // Without this an expired EU Cloud session re-prompts for a region, opens the + // US login page, and on completion rewrites LoggedInUserDomain to US. + var storedDomain string + if configFile, configErr := GetConfigFile(); configErr == nil { + storedDomain = configFile.LoggedInUserDomain + } + domain := resolveReLoginDomain(storedDomain, config.INFISICAL_URL) + // Spawn infisical login command - loginCmd := exec.Command(exePath, "login", "--silent") + loginCmd := exec.Command(exePath, buildReLoginArgs(domain)...) loginCmd.Stdin = os.Stdin loginCmd.Stdout = os.Stdout loginCmd.Stderr = os.Stderr diff --git a/packages/util/auth_test.go b/packages/util/auth_test.go new file mode 100644 index 00000000..0c59afab --- /dev/null +++ b/packages/util/auth_test.go @@ -0,0 +1,98 @@ +package util + +import ( + "reflect" + "testing" +) + +func TestResolveReLoginDomain(t *testing.T) { + usDefault := AppendAPIEndpoint(INFISICAL_DEFAULT_US_URL) + euAPI := AppendAPIEndpoint(INFISICAL_DEFAULT_EU_URL) + + tests := []struct { + name string + storedDomain string + resolvedURL string + expected string + }{ + { + // The regression from the reported bug: an expired EU session must + // re-login against EU, not fall back to the US default. + name: "stored EU domain wins over US default", + storedDomain: euAPI, + resolvedURL: usDefault, + expected: euAPI, + }, + { + name: "stored self-hosted domain is forwarded", + storedDomain: "https://vault.example.com/api", + resolvedURL: usDefault, + expected: "https://vault.example.com/api", + }, + { + // --domain / INFISICAL_DOMAIN / .infisical.json, with nothing stored yet. + name: "explicit non-default resolved URL is forwarded when nothing is stored", + storedDomain: "", + resolvedURL: euAPI, + expected: euAPI, + }, + { + // Must stay empty: the US default is what an unconfigured invocation + // looks like, and forwarding it would skip the region prompt. + name: "bare US default is not forwarded", + storedDomain: "", + resolvedURL: usDefault, + expected: "", + }, + { + name: "nothing known at all", + storedDomain: "", + resolvedURL: "", + expected: "", + }, + { + // A user who explicitly logged in to US Cloud should stay pinned there + // rather than be re-prompted. + name: "stored US domain is still forwarded", + storedDomain: usDefault, + resolvedURL: usDefault, + expected: usDefault, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := resolveReLoginDomain(tt.storedDomain, tt.resolvedURL); got != tt.expected { + t.Errorf("resolveReLoginDomain(%q, %q) = %q, expected %q", + tt.storedDomain, tt.resolvedURL, got, tt.expected) + } + }) + } +} + +func TestBuildReLoginArgs(t *testing.T) { + tests := []struct { + name string + domain string + expected []string + }{ + { + name: "no domain keeps the original argv", + domain: "", + expected: []string{"login", "--silent"}, + }, + { + name: "domain is forwarded as an explicit flag", + domain: "https://eu.infisical.com/api", + expected: []string{"login", "--silent", "--domain", "https://eu.infisical.com/api"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := buildReLoginArgs(tt.domain); !reflect.DeepEqual(got, tt.expected) { + t.Errorf("buildReLoginArgs(%q) = %v, expected %v", tt.domain, got, tt.expected) + } + }) + } +}