Skip to content
Open
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
42 changes: 41 additions & 1 deletion packages/util/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
98 changes: 98 additions & 0 deletions packages/util/auth_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}