• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / cli / 19325748827

13 Nov 2025 08:49AM UTC coverage: 54.419% (-0.3%) from 54.699%
19325748827

Pull #4372

github

web-flow
Merge e70f78d92 into a057b7430
Pull Request #4372: fix: toggle `DENO_NO_PACKAGE_JSON` conditionally

3 of 3 new or added lines in 1 file covered. (100.0%)

160 existing lines in 12 files now uncovered.

6361 of 11689 relevant lines covered (54.42%)

6.1 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.04
/internal/utils/connect.go
1
package utils
2

3
import (
4
        "context"
5
        "fmt"
6
        "io"
7
        "net"
8
        "net/url"
9
        "os"
10
        "strings"
11
        "time"
12

13
        "github.com/go-errors/errors"
14
        "github.com/jackc/pgconn"
15
        "github.com/jackc/pgx/v4"
16
        "github.com/spf13/viper"
17
        "github.com/supabase/cli/internal/debug"
18
        "github.com/supabase/cli/pkg/api"
19
        "github.com/supabase/cli/pkg/pgxv5"
20
        "golang.org/x/net/publicsuffix"
21
)
22

23
func ToPostgresURL(config pgconn.Config) string {
122✔
24
        timeoutSecond := int64(config.ConnectTimeout.Seconds())
122✔
25
        if timeoutSecond == 0 {
207✔
26
                timeoutSecond = 10
85✔
27
        }
85✔
28
        queryParams := fmt.Sprintf("connect_timeout=%d", timeoutSecond)
122✔
29
        for k, v := range config.RuntimeParams {
123✔
30
                queryParams += fmt.Sprintf("&%s=%s", k, url.QueryEscape(v))
1✔
31
        }
1✔
32
        // IPv6 address must be wrapped in square brackets
33
        host := config.Host
122✔
34
        if ip := net.ParseIP(host); ip != nil && ip.To4() == nil {
123✔
35
                host = fmt.Sprintf("[%s]", host)
1✔
36
        }
1✔
37
        return fmt.Sprintf(
122✔
38
                "postgresql://%s@%s:%d/%s?%s",
122✔
39
                url.UserPassword(config.User, config.Password),
122✔
40
                host,
122✔
41
                config.Port,
122✔
42
                url.PathEscape(config.Database),
122✔
43
                queryParams,
122✔
44
        )
122✔
45
}
46

47
func GetPoolerConfigPrimary(ctx context.Context, ref string) (api.SupavisorConfigResponse, error) {
3✔
48
        var result api.SupavisorConfigResponse
3✔
49
        resp, err := GetSupabase().V1GetPoolerConfigWithResponse(ctx, ref)
3✔
50
        if err != nil {
6✔
51
                return result, errors.Errorf("failed to get pooler: %w", err)
3✔
52
        } else if resp.JSON200 == nil {
3✔
UNCOV
53
                return result, errors.Errorf("unexpected get pooler status %d: %s", resp.StatusCode(), string(resp.Body))
×
UNCOV
54
        }
×
UNCOV
55
        for _, config := range *resp.JSON200 {
×
UNCOV
56
                if config.DatabaseType == api.SupavisorConfigResponseDatabaseTypePRIMARY {
×
UNCOV
57
                        return config, nil
×
58
                }
×
59
        }
UNCOV
60
        return result, errors.Errorf("primary database not found: %s", ref)
×
61
}
62

63
func GetPoolerConfig(projectRef string) *pgconn.Config {
11✔
64
        logger := GetDebugLogger()
11✔
65
        if len(Config.Db.Pooler.ConnectionString) == 0 {
15✔
66
                fmt.Fprintln(logger, "Pooler URL is not configured")
4✔
67
                return nil
4✔
68
        }
4✔
69
        poolerConfig, err := ParsePoolerURL(Config.Db.Pooler.ConnectionString)
7✔
70
        if err != nil {
8✔
71
                fmt.Fprintln(logger, err)
1✔
72
                return nil
1✔
73
        }
1✔
74
        if poolerConfig.RuntimeParams == nil {
6✔
UNCOV
75
                poolerConfig.RuntimeParams = make(map[string]string)
×
UNCOV
76
        }
×
77
        // Verify that the pooler username matches the database host being connected to
78
        if _, ref, found := strings.Cut(poolerConfig.User, "."); !found {
9✔
79
                for option := range strings.SplitSeq(poolerConfig.RuntimeParams["options"], ",") {
6✔
80
                        key, value, found := strings.Cut(option, "=")
3✔
81
                        if found && key == "reference" && value != projectRef {
4✔
82
                                fmt.Fprintln(logger, "Pooler options does not match project ref:", projectRef)
1✔
83
                                return nil
1✔
84
                        }
1✔
85
                }
86
        } else if projectRef != ref {
4✔
87
                fmt.Fprintln(logger, "Pooler username does not match project ref:", projectRef)
1✔
88
                return nil
1✔
89
        }
1✔
90
        // There is a risk of MITM attack if we simply trust the hostname specified in pooler URL.
91
        if err := assertDomainInProfile(poolerConfig.Host); err != nil {
5✔
92
                fmt.Fprintln(logger, err)
1✔
93
                return nil
1✔
94
        }
1✔
95
        fmt.Fprintln(logger, "Using connection pooler:", Config.Db.Pooler.ConnectionString)
3✔
96
        // Supavisor transaction mode does not support prepared statement
3✔
97
        poolerConfig.Port = 5432
3✔
98
        return poolerConfig
3✔
99
}
100

101
func ParsePoolerURL(connString string) (*pgconn.Config, error) {
7✔
102
        // Remove password from pooler connection string because the placeholder text
7✔
103
        // [YOUR-PASSWORD] messes up pgconn.ParseConfig. The password must be percent
7✔
104
        // escaped so we cannot simply call strings.Replace with actual password.
7✔
105
        poolerUrl := strings.ReplaceAll(connString, "[YOUR-PASSWORD]", "")
7✔
106
        poolerConfig, err := pgconn.ParseConfig(poolerUrl)
7✔
107
        if err != nil {
8✔
108
                return nil, errors.Errorf("failed to parse pooler URL: %w", err)
1✔
109
        }
1✔
110
        return poolerConfig, nil
6✔
111
}
112

113
func assertDomainInProfile(host string) error {
4✔
114
        domain, err := publicsuffix.EffectiveTLDPlusOne(host)
4✔
115
        if err != nil {
5✔
116
                return errors.Errorf("failed to parse pooler TLD: %w", err)
1✔
117
        }
1✔
118
        if len(CurrentProfile.PoolerHost) > 0 && !strings.EqualFold(CurrentProfile.PoolerHost, domain) {
3✔
UNCOV
119
                return errors.Errorf("Pooler domain does not belong to current profile: %s", domain)
×
UNCOV
120
        }
×
121
        return nil
3✔
122
}
123

124
// Connnect to local Postgres with optimised settings. The caller is responsible for closing the connection returned.
125
func ConnectLocalPostgres(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) {
37✔
126
        if len(config.Host) == 0 {
69✔
127
                config.Host = Config.Hostname
32✔
128
        }
32✔
129
        if config.Port == 0 {
60✔
130
                config.Port = Config.Db.Port
23✔
131
        }
23✔
132
        if len(config.User) == 0 {
62✔
133
                config.User = "postgres"
25✔
134
        }
25✔
135
        if len(config.Password) == 0 {
70✔
136
                config.Password = Config.Db.Password
33✔
137
        }
33✔
138
        if len(config.Database) == 0 {
59✔
139
                config.Database = "postgres"
22✔
140
        }
22✔
141
        if config.ConnectTimeout == 0 {
74✔
142
                config.ConnectTimeout = 2 * time.Second
37✔
143
        }
37✔
144
        options = append(options, func(cc *pgx.ConnConfig) {
69✔
145
                cc.TLSConfig = nil
32✔
146
        })
32✔
147
        return ConnectByUrl(ctx, ToPostgresURL(config), options...)
37✔
148
}
149

150
func ConnectByUrl(ctx context.Context, url string, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) {
104✔
151
        if viper.GetBool("DEBUG") {
106✔
152
                options = append(options, debug.SetupPGX)
2✔
153
        }
2✔
154
        // No fallback from TLS to unsecure connection
155
        options = append(options, func(cc *pgx.ConnConfig) {
195✔
156
                if cc.TLSConfig == nil {
177✔
157
                        return
86✔
158
                }
86✔
159
                var fallbacks []*pgconn.FallbackConfig
5✔
160
                for _, fc := range cc.Fallbacks {
7✔
161
                        if fc.TLSConfig != nil {
2✔
UNCOV
162
                                fallbacks = append(fallbacks, fc)
×
UNCOV
163
                        }
×
164
                }
165
                cc.Fallbacks = fallbacks
5✔
166
        })
167
        return pgxv5.Connect(ctx, url, options...)
104✔
168
}
169

170
const (
171
        SUPERUSER_ROLE   = "supabase_admin"
172
        CLI_LOGIN_PREFIX = "cli_login_"
173
        SET_SESSION_ROLE = "SET SESSION ROLE postgres"
174
)
175

176
func ConnectByConfigStream(ctx context.Context, config pgconn.Config, w io.Writer, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) {
65✔
177
        if IsLocalDatabase(config) {
69✔
178
                fmt.Fprintln(w, "Connecting to local database...")
4✔
179
                return ConnectLocalPostgres(ctx, config, options...)
4✔
180
        }
4✔
181
        fmt.Fprintln(w, "Connecting to remote database...")
61✔
182
        opts := append(options, func(cc *pgx.ConnConfig) {
114✔
183
                if DNSResolver.Value == DNS_OVER_HTTPS {
55✔
184
                        cc.LookupFunc = FallbackLookupIP
2✔
185
                }
2✔
186
                // Step down from platform provisioned login roles or privileged roles
187
                if user := strings.Split(cc.User, ".")[0]; strings.EqualFold(user, SUPERUSER_ROLE) ||
53✔
188
                        strings.HasPrefix(user, CLI_LOGIN_PREFIX) {
53✔
UNCOV
189
                        cc.AfterConnect = func(ctx context.Context, pgconn *pgconn.PgConn) error {
×
UNCOV
190
                                return pgconn.Exec(ctx, SET_SESSION_ROLE).Close()
×
UNCOV
191
                        }
×
192
                }
193
        })
194
        return ConnectByUrl(ctx, ToPostgresURL(config), opts...)
61✔
195
}
196

197
func ConnectByConfig(ctx context.Context, config pgconn.Config, options ...func(*pgx.ConnConfig)) (*pgx.Conn, error) {
64✔
198
        return ConnectByConfigStream(ctx, config, os.Stderr, options...)
64✔
199
}
64✔
200

201
func IsLocalDatabase(config pgconn.Config) bool {
84✔
202
        return config.Host == Config.Hostname && (config.Port == Config.Db.Port || config.Port == Config.Db.ShadowPort)
84✔
203
}
84✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc