github.com/supabase/cli@v1.168.1/internal/utils/flags/db_url.go (about)

     1  package flags
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"math/big"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/go-errors/errors"
    11  	"github.com/jackc/pgconn"
    12  	"github.com/spf13/afero"
    13  	"github.com/spf13/pflag"
    14  	"github.com/spf13/viper"
    15  	"github.com/supabase/cli/internal/utils"
    16  	"github.com/supabase/cli/internal/utils/credentials"
    17  	"github.com/supabase/cli/pkg/api"
    18  )
    19  
    20  type connection int
    21  
    22  const (
    23  	unknown connection = iota
    24  	direct
    25  	local
    26  	linked
    27  	proxy
    28  )
    29  
    30  var DbConfig pgconn.Config
    31  
    32  func ParseDatabaseConfig(flagSet *pflag.FlagSet, fsys afero.Fs) error {
    33  	// Changed flags take precedence over default values
    34  	var connType connection
    35  	if flag := flagSet.Lookup("db-url"); flag != nil && flag.Changed {
    36  		connType = direct
    37  	} else if flag := flagSet.Lookup("local"); flag != nil && flag.Changed {
    38  		connType = local
    39  	} else if flag := flagSet.Lookup("linked"); flag != nil && flag.Changed {
    40  		connType = linked
    41  	} else if flag := flagSet.Lookup("proxy"); flag != nil && flag.Changed {
    42  		connType = proxy
    43  	} else if value, err := flagSet.GetBool("local"); err == nil && value {
    44  		connType = local
    45  	} else if value, err := flagSet.GetBool("linked"); err == nil && value {
    46  		connType = linked
    47  	} else if value, err := flagSet.GetBool("proxy"); err == nil && value {
    48  		connType = proxy
    49  	}
    50  	// Update connection config
    51  	switch connType {
    52  	case direct:
    53  		if flag := flagSet.Lookup("db-url"); flag != nil {
    54  			config, err := pgconn.ParseConfig(flag.Value.String())
    55  			if err != nil {
    56  				return errors.Errorf("failed to parse connection string: %w", err)
    57  			}
    58  			DbConfig = *config
    59  		}
    60  	case local:
    61  		if err := utils.LoadConfigFS(fsys); err != nil {
    62  			return err
    63  		}
    64  		// Ignore other PG settings
    65  		DbConfig.Host = utils.Config.Hostname
    66  		DbConfig.Port = utils.Config.Db.Port
    67  		DbConfig.User = "postgres"
    68  		DbConfig.Password = utils.Config.Db.Password
    69  		DbConfig.Database = "postgres"
    70  	case linked:
    71  		if err := utils.LoadConfigFS(fsys); err != nil {
    72  			return err
    73  		}
    74  		projectRef, err := LoadProjectRef(fsys)
    75  		if err != nil {
    76  			return err
    77  		}
    78  		DbConfig = NewDbConfigWithPassword(projectRef)
    79  	case proxy:
    80  		token, err := utils.LoadAccessTokenFS(fsys)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		projectRef, err := LoadProjectRef(fsys)
    85  		if err != nil {
    86  			return err
    87  		}
    88  		DbConfig.Host = utils.GetSupabaseAPIHost()
    89  		DbConfig.Port = 443
    90  		DbConfig.User = "postgres"
    91  		DbConfig.Password = token
    92  		DbConfig.Database = projectRef
    93  	}
    94  	return nil
    95  }
    96  
    97  func NewDbConfigWithPassword(projectRef string) pgconn.Config {
    98  	config := getDbConfig(projectRef)
    99  	config.Password = getPassword(projectRef)
   100  	return config
   101  }
   102  
   103  func getPassword(projectRef string) string {
   104  	if password := viper.GetString("DB_PASSWORD"); len(password) > 0 {
   105  		return password
   106  	}
   107  	if password, err := credentials.Get(projectRef); err == nil {
   108  		return password
   109  	}
   110  	fmt.Fprint(os.Stderr, "Enter your database password: ")
   111  	return credentials.PromptMasked(os.Stdin)
   112  }
   113  
   114  const PASSWORD_LENGTH = 16
   115  
   116  func PromptPassword(stdin *os.File) string {
   117  	fmt.Fprint(os.Stderr, "Enter your database password (or leave blank to generate one): ")
   118  	if input := credentials.PromptMasked(stdin); len(input) > 0 {
   119  		return input
   120  	}
   121  	// Generate a password, see ./Settings/Database/DatabaseSettings/ResetDbPassword.tsx#L83
   122  	var password []byte
   123  	charset := string(api.AbcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567891)
   124  	charset = strings.ReplaceAll(charset, ":", "")
   125  	maxRange := big.NewInt(int64(len(charset)))
   126  	for i := 0; i < PASSWORD_LENGTH; i++ {
   127  		random, err := rand.Int(rand.Reader, maxRange)
   128  		if err != nil {
   129  			fmt.Fprintln(os.Stderr, "Failed to randomise password:", err)
   130  			continue
   131  		}
   132  		password = append(password, charset[random.Int64()])
   133  	}
   134  	return string(password)
   135  }
   136  
   137  func getDbConfig(projectRef string) pgconn.Config {
   138  	if poolerConfig := utils.GetPoolerConfig(projectRef); poolerConfig != nil {
   139  		return *poolerConfig
   140  	}
   141  	return pgconn.Config{
   142  		Host:     utils.GetSupabaseDbHost(projectRef),
   143  		Port:     5432,
   144  		User:     "postgres",
   145  		Database: "postgres",
   146  	}
   147  }
   148  
   149  func GetDbConfigOptionalPassword(projectRef string) pgconn.Config {
   150  	config := getDbConfig(projectRef)
   151  	config.Password = viper.GetString("DB_PASSWORD")
   152  	if config.Password == "" {
   153  		fmt.Fprint(os.Stderr, "Enter your database password (or leave blank to skip): ")
   154  		config.Password = credentials.PromptMasked(os.Stdin)
   155  	}
   156  	return config
   157  }