Skip to content

Commit 8448e5b

Browse files
authored
Allow to enter password manually when using tiger db connect or reset (#118)
1 parent 9bdf992 commit 8448e5b

File tree

5 files changed

+597
-111
lines changed

5 files changed

+597
-111
lines changed

internal/tiger/cmd/db.go

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,11 @@ Examples:
7979
Args: cobra.MaximumNArgs(1),
8080
ValidArgsFunction: serviceIDCompletion,
8181
RunE: func(cmd *cobra.Command, args []string) error {
82-
service, err := getServiceDetails(cmd, args)
82+
cfg, projectID, client, err := loadConfigAndApiClient()
83+
if err != nil {
84+
return err
85+
}
86+
service, err := getServiceDetails(cmd, cfg, projectID, client, args)
8387
if err != nil {
8488
return err
8589
}
@@ -131,7 +135,9 @@ with the appropriate connection parameters.
131135
Authentication is handled automatically using:
132136
1. Stored password (keyring, ~/.pgpass, or none based on --password-storage setting)
133137
2. PGPASSWORD environment variable
134-
3. Interactive password prompt (if neither above is available)
138+
3. If authentication fails, offers interactive options:
139+
- Enter password manually (will be saved for future use)
140+
- Reset password (update or generates a new password via the API)
135141
136142
Examples:
137143
# Connect to default service
@@ -158,8 +164,11 @@ Examples:
158164
RunE: func(cmd *cobra.Command, args []string) error {
159165
// Separate service ID from additional psql flags
160166
serviceArgs, psqlFlags := separateServiceAndPsqlArgs(cmd, args)
161-
162-
service, err := getServiceDetails(cmd, serviceArgs)
167+
cfg, projectID, client, err := loadConfigAndApiClient()
168+
if err != nil {
169+
return err
170+
}
171+
service, err := getServiceDetails(cmd, cfg, projectID, client, serviceArgs)
163172
if err != nil {
164173
return err
165174
}
@@ -182,8 +191,7 @@ Examples:
182191
return fmt.Errorf("connection pooler not available for this service")
183192
}
184193

185-
// Launch psql with additional flags
186-
return launchPsqlWithConnectionString(details.String(), psqlPath, psqlFlags, service, dbConnectRole, cmd)
194+
return connectWithPasswordMenu(cmd.Context(), cmd, client, service, details, psqlPath, psqlFlags)
187195
},
188196
}
189197

@@ -232,7 +240,11 @@ Examples:
232240
Args: cobra.MaximumNArgs(1),
233241
ValidArgsFunction: serviceIDCompletion,
234242
RunE: func(cmd *cobra.Command, args []string) error {
235-
service, err := getServiceDetails(cmd, args)
243+
cfg, projectID, client, err := loadConfigAndApiClient()
244+
if err != nil {
245+
return err
246+
}
247+
service, err := getServiceDetails(cmd, cfg, projectID, client, args)
236248
if err != nil {
237249
return common.ExitWithCode(common.ExitInvalidParameters, err)
238250
}
@@ -306,7 +318,11 @@ Examples:
306318
Args: cobra.MaximumNArgs(1),
307319
ValidArgsFunction: serviceIDCompletion,
308320
RunE: func(cmd *cobra.Command, args []string) error {
309-
service, err := getServiceDetailsFunc(cmd, args)
321+
cfg, projectID, client, err := loadConfigAndApiClient()
322+
if err != nil {
323+
return err
324+
}
325+
service, err := getServiceDetailsFunc(cmd, cfg, projectID, client, args)
310326
if err != nil {
311327
return err
312328
}
@@ -651,10 +667,9 @@ PostgreSQL Configuration Parameters That May Be Set:
651667

652668
cmd.SilenceUsage = true
653669

654-
// Get config
655-
cfg, err := config.Load()
670+
cfg, projectID, client, err := loadConfigAndApiClient()
656671
if err != nil {
657-
return fmt.Errorf("failed to load config: %w", err)
672+
return err
658673
}
659674

660675
// Get password
@@ -664,7 +679,7 @@ PostgreSQL Configuration Parameters That May Be Set:
664679
}
665680

666681
// Get service details
667-
service, err := getServiceDetails(cmd, args)
682+
service, err := getServiceDetails(cmd, cfg, projectID, client, args)
668683
if err != nil {
669684
return err
670685
}
@@ -748,34 +763,39 @@ func buildDbCmd() *cobra.Command {
748763
return cmd
749764
}
750765

751-
// getServiceDetails is a helper that handles common service lookup logic and returns the service details
752-
func getServiceDetails(cmd *cobra.Command, args []string) (api.Service, error) {
753-
// Get config
766+
func loadConfigAndApiClient() (*config.Config, string, *api.ClientWithResponses, error) {
767+
// Load config
754768
cfg, err := config.Load()
755769
if err != nil {
756-
return api.Service{}, fmt.Errorf("failed to load config: %w", err)
770+
return nil, "", nil, fmt.Errorf("failed to load config: %w", err)
757771
}
758772

759-
// Determine service ID
760-
serviceID, err := getServiceID(cfg, args)
761-
if err != nil {
762-
return api.Service{}, err
763-
}
764-
765-
cmd.SilenceUsage = true
766-
767773
// Get API key and project ID for authentication
768774
apiKey, projectID, err := getCredentialsForDB()
769775
if err != nil {
770-
return api.Service{}, common.ExitWithCode(common.ExitAuthenticationError, fmt.Errorf("authentication required: %w. Please run 'tiger auth login'", err))
776+
return nil, "", nil, common.ExitWithCode(common.ExitAuthenticationError, fmt.Errorf("authentication required: %w. Please run 'tiger auth login'", err))
771777
}
772778

773779
// Create API client
774780
client, err := api.NewTigerClient(cfg, apiKey)
775781
if err != nil {
776-
return api.Service{}, fmt.Errorf("failed to create API client: %w", err)
782+
return nil, "", nil, fmt.Errorf("failed to create API client: %w", err)
777783
}
778784

785+
return cfg, projectID, client, nil
786+
}
787+
788+
// getServiceDetails is a helper that handles common service lookup logic and returns the service details
789+
func getServiceDetails(cmd *cobra.Command, cfg *config.Config, projectID string, client *api.ClientWithResponses, args []string) (api.Service, error) {
790+
791+
// Determine service ID
792+
serviceID, err := getServiceID(cfg, args)
793+
if err != nil {
794+
return api.Service{}, err
795+
}
796+
797+
cmd.SilenceUsage = true
798+
779799
// Fetch service details
780800
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
781801
defer cancel()
@@ -820,17 +840,22 @@ func separateServiceAndPsqlArgs(cmd ArgsLenAtDashProvider, args []string) ([]str
820840
return serviceArgs, psqlFlags
821841
}
822842

823-
// launchPsqlWithConnectionString launches psql using the connection string and additional flags
824-
func launchPsqlWithConnectionString(connectionString, psqlPath string, additionalFlags []string, service api.Service, role string, cmd *cobra.Command) error {
825-
psqlCmd := buildPsqlCommand(connectionString, psqlPath, additionalFlags, service, role, cmd)
843+
// launchPsql launches psql using the connection string and additional flags.
844+
// It retrieves the password from storage and sets PGPASSWORD environment variable.
845+
func launchPsql(details *common.ConnectionDetails, psqlPath string, additionalFlags []string, service api.Service, cmd *cobra.Command) error {
846+
psqlCmd := buildPsqlCommand(details, psqlPath, additionalFlags, service, cmd)
826847
return psqlCmd.Run()
827848
}
828849

829850
// buildPsqlCommand creates the psql command with proper environment setup
830-
func buildPsqlCommand(connectionString, psqlPath string, additionalFlags []string, service api.Service, role string, cmd *cobra.Command) *exec.Cmd {
831-
// Build command arguments: connection string first, then additional flags
832-
// Note: connectionString contains only "postgresql://user@host:port/db" - no password
851+
func buildPsqlCommand(details *common.ConnectionDetails, psqlPath string, additionalFlags []string, service api.Service, cmd *cobra.Command) *exec.Cmd {
852+
password := details.Password
853+
// Ensure we don't include password in the connection string to make it not show up in process lists
833854
// Passwords are passed via PGPASSWORD environment variable (see below)
855+
detailsCopy := *details
856+
detailsCopy.Password = ""
857+
connectionString := detailsCopy.String()
858+
// Build command arguments: connection string first, then additional flags
834859
args := []string{connectionString}
835860
args = append(args, additionalFlags...)
836861

@@ -841,16 +866,21 @@ func buildPsqlCommand(connectionString, psqlPath string, additionalFlags []strin
841866
psqlCmd.Stdout = cmd.OutOrStdout()
842867
psqlCmd.Stderr = cmd.ErrOrStderr()
843868

844-
// Only set PGPASSWORD for keyring storage method
845-
// pgpass storage relies on psql automatically reading ~/.pgpass file
846-
storage := common.GetPasswordStorage()
847-
if _, isKeyring := storage.(*common.KeyringStorage); isKeyring {
848-
if password, err := storage.Get(service, role); err == nil && password != "" {
849-
// Set PGPASSWORD environment variable for psql when using keyring
850-
psqlCmd.Env = append(os.Environ(), "PGPASSWORD="+password)
869+
// Use provided password directly if available
870+
if password != "" {
871+
psqlCmd.Env = append(os.Environ(), "PGPASSWORD="+password)
872+
} else {
873+
storage := common.GetPasswordStorage()
874+
// Only set PGPASSWORD for keyring storage method
875+
// pgpass storage relies on psql automatically reading ~/.pgpass file
876+
if _, isKeyring := storage.(*common.KeyringStorage); isKeyring {
877+
if storedPassword, err := storage.Get(service, details.Role); err == nil && storedPassword != "" {
878+
// Set PGPASSWORD environment variable for psql when using keyring
879+
psqlCmd.Env = append(os.Environ(), "PGPASSWORD="+storedPassword)
880+
}
881+
// Note: If keyring password retrieval fails, we let psql try without it
882+
// This allows fallback to other authentication methods
851883
}
852-
// Note: If keyring password retrieval fails, we let psql try without it
853-
// This allows fallback to other authentication methods
854884
}
855885

856886
return psqlCmd

0 commit comments

Comments
 (0)