From da08eb34d28a0f99a9cd5cf12daa4227046dca2c Mon Sep 17 00:00:00 2001 From: Samir Ketema Date: Wed, 18 Feb 2026 20:44:41 -0800 Subject: [PATCH] fix: clarify db connection error for `Address not in tenant allow_list` --- internal/utils/connect.go | 40 +++++++++++------- internal/utils/connect_test.go | 77 ++++++++++++++++++++++++++++++++++ internal/utils/flags/db_url.go | 9 ++-- 3 files changed, 108 insertions(+), 18 deletions(-) diff --git a/internal/utils/connect.go b/internal/utils/connect.go index eca6b410a..b9e2d39df 100644 --- a/internal/utils/connect.go +++ b/internal/utils/connect.go @@ -167,23 +167,35 @@ func ConnectByUrl(ctx context.Context, url string, options ...func(*pgx.ConnConf cc.Fallbacks = fallbacks }) conn, err := pgxv5.Connect(ctx, url, options...) - var pgErr *pgconn.PgError - if errors.As(err, &pgErr) { - if strings.Contains(pgErr.Message, "connect: connection refused") { - CmdSuggestion = fmt.Sprintf("Make sure your local IP is allowed in Network Restrictions and Network Bans.\n%s/project/_/database/settings", CurrentProfile.DashboardURL) - } else if strings.Contains(pgErr.Message, "SSL connection is required") && viper.GetBool("DEBUG") { - CmdSuggestion = "SSL connection is not supported with --debug flag" - } else if strings.Contains(pgErr.Message, "SCRAM exchange: Wrong password") || strings.Contains(pgErr.Message, "failed SASL auth") { - // password authentication failed for user / invalid SCRAM server-final-message received from server - CmdSuggestion = "Try setting the SUPABASE_DB_PASSWORD environment variable" - } else if strings.Contains(pgErr.Message, "connect: no route to host") || strings.Contains(pgErr.Message, "Tenant or user not found") { - // Assumes IPv6 check has been performed before this - CmdSuggestion = "Make sure your project exists on profile: " + CurrentProfile.Name - } - } + SetConnectSuggestion(err) return conn, err } +const SuggestEnvVar = "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD" + +// Sets CmdSuggestion to an actionable hint based on the given pg connection error. +func SetConnectSuggestion(err error) { + if err == nil { + return + } + msg := err.Error() + if strings.Contains(msg, "connect: connection refused") || + strings.Contains(msg, "Address not in tenant allow_list") { + CmdSuggestion = fmt.Sprintf( + "Make sure your local IP is allowed in Network Restrictions and Network Bans.\n%s/project/_/database/settings", + CurrentProfile.DashboardURL, + ) + } else if strings.Contains(msg, "SSL connection is required") && viper.GetBool("DEBUG") { + CmdSuggestion = "SSL connection is not supported with --debug flag" + } else if strings.Contains(msg, "SCRAM exchange: Wrong password") || strings.Contains(msg, "failed SASL auth") { + // password authentication failed for user / invalid SCRAM server-final-message received from server + CmdSuggestion = SuggestEnvVar + } else if strings.Contains(msg, "connect: no route to host") || strings.Contains(msg, "Tenant or user not found") { + // Assumes IPv6 check has been performed before this + CmdSuggestion = "Make sure your project exists on profile: " + CurrentProfile.Name + } +} + const ( SUPERUSER_ROLE = "supabase_admin" CLI_LOGIN_PREFIX = "cli_login_" diff --git a/internal/utils/connect_test.go b/internal/utils/connect_test.go index 140b0c92e..55a81c0ca 100644 --- a/internal/utils/connect_test.go +++ b/internal/utils/connect_test.go @@ -168,6 +168,83 @@ func TestPoolerConfig(t *testing.T) { }) } +func TestSetConnectSuggestion(t *testing.T) { + oldProfile := CurrentProfile + CurrentProfile = allProfiles[0] + defer t.Cleanup(func() { CurrentProfile = oldProfile }) + + cases := []struct { + name string + err error + suggestion string + debug bool + }{ + { + name: "no-op on nil error", + err: nil, + suggestion: "", + }, + { + name: "no-op on unrecognised error", + err: errors.New("some unknown error"), + suggestion: "", + }, + { + name: "connection refused", + err: errors.New("connect: connection refused"), + suggestion: "Make sure your local IP is allowed in Network Restrictions and Network Bans", + }, + { + name: "address not in allow list", + err: errors.New("server error (FATAL: Address not in tenant allow_list: {1,2,3} (SQLSTATE XX000))"), + suggestion: "Make sure your local IP is allowed in Network Restrictions and Network Bans", + }, + { + name: "ssl required without debug flag", + err: errors.New("SSL connection is required"), + suggestion: "", + }, + { + name: "ssl required with debug flag", + err: errors.New("SSL connection is required"), + debug: true, + suggestion: "SSL connection is not supported with --debug flag", + }, + { + name: "wrong password via SCRAM", + err: errors.New("SCRAM exchange: Wrong password"), + suggestion: "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD", + }, + { + name: "failed SASL auth", + err: errors.New("failed SASL auth"), + suggestion: "Connect to your database by setting the env var correctly: SUPABASE_DB_PASSWORD", + }, + { + name: "no route to host", + err: errors.New("connect: no route to host"), + suggestion: "Make sure your project exists on profile: " + CurrentProfile.Name, + }, + { + name: "tenant or user not found", + err: errors.New("Tenant or user not found"), + suggestion: "Make sure your project exists on profile: " + CurrentProfile.Name, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + CmdSuggestion = "" + viper.Set("DEBUG", tc.debug) + SetConnectSuggestion(tc.err) + if tc.suggestion == "" { + assert.Empty(t, CmdSuggestion) + } else { + assert.Contains(t, CmdSuggestion, tc.suggestion) + } + }) + } +} + func TestPostgresURL(t *testing.T) { url := ToPostgresURL(pgconn.Config{ Host: "2406:da18:4fd:9b0d:80ec:9812:3e65:450b", diff --git a/internal/utils/flags/db_url.go b/internal/utils/flags/db_url.go index 3ec94073b..b3fda6a00 100644 --- a/internal/utils/flags/db_url.go +++ b/internal/utils/flags/db_url.go @@ -120,8 +120,6 @@ func RandomString(size int) (string, error) { return string(data), nil } -const suggestEnvVar = "Connect to your database by setting the env var: SUPABASE_DB_PASSWORD" - func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Config, error) { config := pgconn.Config{ Host: utils.GetSupabaseDbHost(projectRef), @@ -144,7 +142,10 @@ func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Con fmt.Fprintln(logger, "Using database password from env var...") poolerConfig.Password = config.Password } else if err := initPoolerLogin(ctx, projectRef, poolerConfig); err != nil { - utils.CmdSuggestion = suggestEnvVar + utils.SetConnectSuggestion(err) + if utils.CmdSuggestion == "" { + utils.CmdSuggestion = utils.SuggestEnvVar + } return *poolerConfig, err } return *poolerConfig, nil @@ -157,7 +158,7 @@ func NewDbConfigWithPassword(ctx context.Context, projectRef string) (pgconn.Con fmt.Fprintln(logger, "Using database password from env var...") } else if err := initLoginRole(ctx, projectRef, &config); err != nil { // Do not prompt because reading masked input is buggy on windows - utils.CmdSuggestion = suggestEnvVar + utils.CmdSuggestion = utils.SuggestEnvVar return config, err } return config, nil