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

supabase / cli / 16976524820

14 Aug 2025 08:57PM UTC coverage: 54.808% (-0.5%) from 55.271%
16976524820

Pull #3969

github

web-flow
Merge 419dd46ad into b3d509dd5
Pull Request #3969: feat: generate jwt tokens from signing key

11 of 13 new or added lines in 1 file covered. (84.62%)

391 existing lines in 14 files now uncovered.

6196 of 11305 relevant lines covered (54.81%)

6.09 hits per line

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

61.49
/internal/utils/flags/db_url.go
1
package flags
2

3
import (
4
        "bytes"
5
        "context"
6
        "crypto/rand"
7
        _ "embed"
8
        "fmt"
9
        "math/big"
10
        "net/http"
11
        "os"
12
        "strings"
13
        "text/template"
14

15
        "github.com/cenkalti/backoff/v4"
16
        "github.com/go-errors/errors"
17
        "github.com/jackc/pgconn"
18
        "github.com/spf13/afero"
19
        "github.com/spf13/pflag"
20
        "github.com/spf13/viper"
21
        "github.com/supabase/cli/internal/utils"
22
        "github.com/supabase/cli/internal/utils/credentials"
23
        "github.com/supabase/cli/pkg/api"
24
        "github.com/supabase/cli/pkg/config"
25
        "github.com/supabase/cli/pkg/pgxv5"
26
)
27

28
type connection int
29

30
const (
31
        unknown connection = iota
32
        direct
33
        local
34
        linked
35
        proxy
36
)
37

38
var DbConfig pgconn.Config
39

40
func ParseDatabaseConfig(ctx context.Context, flagSet *pflag.FlagSet, fsys afero.Fs) error {
3✔
41
        // Changed flags take precedence over default values
3✔
42
        var connType connection
3✔
43
        if flag := flagSet.Lookup("db-url"); flag != nil && flag.Changed {
4✔
44
                connType = direct
1✔
45
        } else if flag := flagSet.Lookup("local"); flag != nil && flag.Changed {
4✔
46
                connType = local
1✔
47
        } else if flag := flagSet.Lookup("linked"); flag != nil && flag.Changed {
3✔
48
                connType = linked
1✔
49
        } else if flag := flagSet.Lookup("proxy"); flag != nil && flag.Changed {
1✔
50
                connType = proxy
×
51
        } else if value, err := flagSet.GetBool("local"); err == nil && value {
×
52
                connType = local
×
53
        } else if value, err := flagSet.GetBool("linked"); err == nil && value {
×
54
                connType = linked
×
55
        } else if value, err := flagSet.GetBool("proxy"); err == nil && value {
×
56
                connType = proxy
×
UNCOV
57
        }
×
58
        // Update connection config
59
        switch connType {
3✔
60
        case direct:
1✔
61
                if err := LoadConfig(fsys); err != nil {
1✔
62
                        return err
×
UNCOV
63
                }
×
64
                if flag := flagSet.Lookup("db-url"); flag != nil {
2✔
65
                        config, err := pgconn.ParseConfig(flag.Value.String())
1✔
66
                        if err != nil {
1✔
67
                                return errors.Errorf("failed to parse connection string: %w", err)
×
UNCOV
68
                        }
×
69
                        DbConfig = *config
1✔
70
                }
71
        case local:
1✔
72
                if err := LoadConfig(fsys); err != nil {
1✔
73
                        return err
×
UNCOV
74
                }
×
75
                // Ignore other PG settings
76
                DbConfig.Host = utils.Config.Hostname
1✔
77
                DbConfig.Port = utils.Config.Db.Port
1✔
78
                DbConfig.User = "postgres"
1✔
79
                DbConfig.Password = utils.Config.Db.Password
1✔
80
                DbConfig.Database = "postgres"
1✔
81
        case linked:
1✔
82
                if err := LoadProjectRef(fsys); err != nil {
1✔
83
                        return err
×
UNCOV
84
                }
×
85
                if err := LoadConfig(fsys); err != nil {
1✔
86
                        return err
×
UNCOV
87
                }
×
88
                DbConfig = NewDbConfigWithPassword(ctx, ProjectRef)
1✔
89
        case proxy:
×
90
                token, err := utils.LoadAccessTokenFS(fsys)
×
91
                if err != nil {
×
92
                        return err
×
93
                }
×
94
                if err := LoadProjectRef(fsys); err != nil {
×
95
                        return err
×
96
                }
×
97
                DbConfig.Host = utils.GetSupabaseAPIHost()
×
98
                DbConfig.Port = 443
×
99
                DbConfig.User = "postgres"
×
100
                DbConfig.Password = token
×
UNCOV
101
                DbConfig.Database = ProjectRef
×
102
        }
103
        return nil
3✔
104
}
105

106
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
107

108
func RandomString(size int) (string, error) {
4✔
109
        data := make([]byte, size)
4✔
110
        _, err := rand.Read(data)
4✔
111
        if err != nil {
4✔
112
                return "", errors.Errorf("failed to read random: %w", err)
×
UNCOV
113
        }
×
114
        for i := range data {
132✔
115
                n := int(data[i]) % len(letters)
128✔
116
                data[i] = letters[n]
128✔
117
        }
128✔
118
        return string(data), nil
4✔
119
}
120

121
func NewDbConfigWithPassword(ctx context.Context, projectRef string) pgconn.Config {
4✔
122
        config := getDbConfig(projectRef)
4✔
123
        config.Password = viper.GetString("DB_PASSWORD")
4✔
124
        if len(config.Password) > 0 {
4✔
125
                return config
×
UNCOV
126
        }
×
127
        var err error
4✔
128
        if config.Password, err = RandomString(32); err == nil {
8✔
129
                newRole := pgconn.Config{
4✔
130
                        User:     pgxv5.CLI_LOGIN_ROLE,
4✔
131
                        Password: config.Password,
4✔
132
                }
4✔
133
                if err := initLoginRole(ctx, projectRef, newRole); err == nil {
6✔
134
                        // Special handling for pooler username
2✔
135
                        if suffix := "." + projectRef; strings.HasSuffix(config.User, suffix) {
2✔
136
                                newRole.User += suffix
×
UNCOV
137
                                defer tryPooler(ctx, &config)
×
UNCOV
138
                        }
×
139
                        config.User = newRole.User
2✔
140
                        return config
2✔
141
                }
142
        }
143
        if config.Password, err = credentials.StoreProvider.Get(projectRef); err == nil {
2✔
UNCOV
144
                return config
×
UNCOV
145
        }
×
146
        resetUrl := fmt.Sprintf("%s/project/%s/settings/database", utils.GetSupabaseDashboardURL(), projectRef)
2✔
147
        fmt.Fprintln(os.Stderr, "Forgot your password? Reset it from the Dashboard:", utils.Bold(resetUrl))
2✔
148
        fmt.Fprint(os.Stderr, "Enter your database password: ")
2✔
149
        config.Password = credentials.PromptMasked(os.Stdin)
2✔
150
        return config
2✔
151
}
152

UNCOV
153
func tryPooler(ctx context.Context, config *pgconn.Config) {
×
UNCOV
154
        if err := backoff.RetryNotify(func() error {
×
UNCOV
155
                conn, err := pgconn.ConnectConfig(ctx, config)
×
UNCOV
156
                if err != nil {
×
UNCOV
157
                        return errors.Errorf("failed to connect as temp role: %w", err)
×
UNCOV
158
                }
×
UNCOV
159
                return conn.Close(ctx)
×
UNCOV
160
        }, utils.NewBackoffPolicy(ctx), utils.NewErrorCallback()); err != nil {
×
161
                fmt.Fprintln(os.Stderr, err)
×
162
        }
×
163
}
164

165
var (
166
        //go:embed queries/role.sql
167
        initRoleEmbed    string
168
        initRoleTemplate = template.Must(template.New("initRole").Parse(initRoleEmbed))
169
)
170

171
func initLoginRole(ctx context.Context, projectRef string, config pgconn.Config) error {
4✔
172
        fmt.Fprintf(os.Stderr, "Initialising %s role...\n", config.User)
4✔
173
        var initRoleBuf bytes.Buffer
4✔
174
        if err := initRoleTemplate.Option("missingkey=error").Execute(&initRoleBuf, config); err != nil {
4✔
UNCOV
175
                return errors.Errorf("failed to exec template: %w", err)
×
UNCOV
176
        }
×
177
        body := api.V1RunQueryBody{Query: initRoleBuf.String()}
4✔
178
        if resp, err := utils.GetSupabase().V1RunAQueryWithResponse(ctx, projectRef, body); err != nil {
4✔
UNCOV
179
                return errors.Errorf("failed to initialise login role: %w", err)
×
180
        } else if resp.StatusCode() != http.StatusCreated {
6✔
181
                return errors.Errorf("unexpected query status %d: %s", resp.StatusCode(), string(resp.Body))
2✔
182
        }
2✔
183
        return nil
2✔
184
}
185

186
const PASSWORD_LENGTH = 16
187

188
func PromptPassword(stdin *os.File) string {
2✔
189
        fmt.Fprint(os.Stderr, "Enter your database password (or leave blank to generate one): ")
2✔
190
        if input := credentials.PromptMasked(stdin); len(input) > 0 {
3✔
191
                return input
1✔
192
        }
1✔
193
        // Generate a password, see ./Settings/Database/DatabaseSettings/ResetDbPassword.tsx#L83
194
        var password []byte
1✔
195
        charset := string(config.LowerUpperLettersDigits.ToChar())
1✔
196
        charset = strings.ReplaceAll(charset, ":", "")
1✔
197
        maxRange := big.NewInt(int64(len(charset)))
1✔
198
        for i := 0; i < PASSWORD_LENGTH; i++ {
17✔
199
                random, err := rand.Int(rand.Reader, maxRange)
16✔
200
                if err != nil {
16✔
UNCOV
201
                        fmt.Fprintln(os.Stderr, "Failed to randomise password:", err)
×
UNCOV
202
                        continue
×
203
                }
204
                password = append(password, charset[random.Int64()])
16✔
205
        }
206
        return string(password)
1✔
207
}
208

209
func getDbConfig(projectRef string) pgconn.Config {
4✔
210
        if poolerConfig := utils.GetPoolerConfig(projectRef); poolerConfig != nil {
4✔
UNCOV
211
                return *poolerConfig
×
UNCOV
212
        }
×
213
        return pgconn.Config{
4✔
214
                Host:     utils.GetSupabaseDbHost(projectRef),
4✔
215
                Port:     5432,
4✔
216
                User:     "postgres",
4✔
217
                Database: "postgres",
4✔
218
        }
4✔
219
}
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