Skip to content

Commit 5056f2e

Browse files
authored
Merge pull request #2 from rebelice/feat/improve-password-storage
feat(password): switch to 99designs/keyring with fallback support
2 parents c8144d2 + af4d6ea commit 5056f2e

8 files changed

Lines changed: 684 additions & 54 deletions

File tree

go.mod

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ module github.com/rebelice/lazypg
33
go 1.24.0
44

55
require (
6+
github.com/99designs/keyring v1.2.2
7+
github.com/alecthomas/chroma/v2 v2.20.0
68
github.com/atotto/clipboard v0.1.4
79
github.com/charmbracelet/bubbles v0.21.0
810
github.com/charmbracelet/bubbletea v1.3.10
@@ -14,29 +16,30 @@ require (
1416
github.com/mattn/go-runewidth v0.0.16
1517
github.com/mattn/go-sqlite3 v1.14.32
1618
github.com/spf13/viper v1.21.0
17-
github.com/zalando/go-keyring v0.2.6
1819
gopkg.in/yaml.v3 v3.0.1
1920
)
2021

2122
require (
22-
al.essio.dev/pkg/shellescape v1.5.1 // indirect
23-
github.com/alecthomas/chroma/v2 v2.20.0 // indirect
23+
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
2424
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2525
github.com/charmbracelet/colorprofile v0.3.1 // indirect
2626
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
2727
github.com/charmbracelet/x/term v0.2.1 // indirect
2828
github.com/danieljoos/wincred v1.2.2 // indirect
2929
github.com/dlclark/regexp2 v1.11.5 // indirect
30+
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
3031
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
3132
github.com/fsnotify/fsnotify v1.9.0 // indirect
3233
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
33-
github.com/godbus/dbus/v5 v5.1.0 // indirect
34+
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
35+
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
3436
github.com/jackc/pgpassfile v1.0.0 // indirect
3537
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
3638
github.com/jackc/puddle/v2 v2.2.2 // indirect
3739
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
3840
github.com/mattn/go-isatty v0.0.20 // indirect
3941
github.com/mattn/go-localereader v0.0.1 // indirect
42+
github.com/mtibben/percent v0.2.1 // indirect
4043
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
4144
github.com/muesli/cancelreader v0.2.2 // indirect
4245
github.com/muesli/termenv v0.16.0 // indirect
@@ -53,5 +56,6 @@ require (
5356
golang.org/x/crypto v0.31.0 // indirect
5457
golang.org/x/sync v0.16.0 // indirect
5558
golang.org/x/sys v0.36.0 // indirect
59+
golang.org/x/term v0.27.0 // indirect
5660
golang.org/x/text v0.28.0 // indirect
5761
)

go.sum

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=
2-
al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
1+
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
2+
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
3+
github.com/99designs/keyring v1.2.2 h1:pZd3neh/EmUzWONb35LxQfvuY7kiSXAq3HQd97+XBn0=
4+
github.com/99designs/keyring v1.2.2/go.mod h1:wes/FrByc8j7lFOAGLGSNEg8f/PaI3cgTBqhFkHUrPk=
5+
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
6+
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
37
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
48
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
9+
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
10+
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
511
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
612
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
713
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -27,6 +33,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
2733
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
2834
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
2935
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
36+
github.com/dvsekhvalnov/jose2go v1.5.0 h1:3j8ya4Z4kMCwT5nXIKFSV84YS+HdqSSO0VsTQxaLAeM=
37+
github.com/dvsekhvalnov/jose2go v1.5.0/go.mod h1:QsHjhyTlD/lAVqn/NSbVZmSCGeDehTB/mPZadG+mhXU=
3038
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
3139
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
3240
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -35,14 +43,16 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
3543
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
3644
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
3745
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
38-
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
39-
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
46+
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
47+
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
4048
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
4149
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
42-
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
43-
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
4450
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
4551
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
52+
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
53+
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
54+
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
55+
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
4656
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
4757
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
4858
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@@ -53,6 +63,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
5363
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
5464
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
5565
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
66+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
67+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
5668
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
5769
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
5870
github.com/lrstanley/bubblezone v1.0.0 h1:bIpUaBilD42rAQwlg/4u5aTqVAt6DSRKYZuSdmkr8UA=
@@ -67,12 +79,15 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
6779
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
6880
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
6981
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
82+
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
83+
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
7084
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
7185
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
7286
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
7387
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
7488
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
7589
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
90+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
7691
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
7792
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
7893
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -105,8 +120,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
105120
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
106121
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
107122
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
108-
github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s=
109-
github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI=
110123
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
111124
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
112125
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
@@ -119,9 +132,12 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
119132
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
120133
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
121134
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
135+
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
136+
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
122137
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
123138
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
124139
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
140+
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
125141
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
126142
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
127143
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/app/app.go

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ type App struct {
9898
// Connection history
9999
connectionHistory *connection_history.Manager
100100

101+
// Password dialog for missing passwords
102+
showPasswordDialog bool
103+
passwordDialog *components.PasswordDialog
104+
pendingConnectionInfo *models.ConnectionHistoryEntry
105+
101106
// Search input
102107
showSearch bool
103108
searchInput *components.SearchInput
@@ -330,6 +335,7 @@ func New(cfg *config.Config) *App {
330335
favoritesManager: favoritesManager,
331336
favoritesDialog: favoritesDialog,
332337
connectionHistory: connectionHistory,
338+
passwordDialog: components.NewPasswordDialog(th),
333339
showSearch: false,
334340
searchInput: searchInput,
335341
executeSpinner: s,
@@ -588,6 +594,34 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
588594
a.showError = false
589595
return a, nil
590596

597+
case components.PasswordSubmitMsg:
598+
// User submitted password from password dialog
599+
a.showPasswordDialog = false
600+
if a.pendingConnectionInfo != nil {
601+
// Create connection config with the entered password
602+
config := a.pendingConnectionInfo.ToConnectionConfig()
603+
config.Password = msg.Password
604+
605+
// Try to save the password for future use
606+
if a.connectionHistory != nil {
607+
if err := a.connectionHistory.SavePassword(config.Host, config.Port, config.Database, config.User, config.Password); err != nil {
608+
log.Printf("Warning: Failed to save password: %v", err)
609+
}
610+
}
611+
612+
a.pendingConnectionInfo = nil
613+
return a.performConnection(config)
614+
}
615+
return a, nil
616+
617+
case components.PasswordCancelMsg:
618+
// User cancelled password dialog
619+
a.showPasswordDialog = false
620+
a.pendingConnectionInfo = nil
621+
// Re-show connection dialog
622+
a.showConnectionDialog = true
623+
return a, nil
624+
591625
case components.CloseCommandPaletteMsg:
592626
a.showCommandPalette = false
593627
return a, nil
@@ -785,6 +819,13 @@ func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
785819
return a.handleConnectionDialog(msg)
786820
}
787821

822+
// Handle password dialog if visible
823+
if a.showPasswordDialog {
824+
var cmd tea.Cmd
825+
a.passwordDialog, cmd = a.passwordDialog.Update(msg)
826+
return a, cmd
827+
}
828+
788829
// Handle command palette if visible
789830
if a.showCommandPalette {
790831
return a.handleCommandPalette(msg)
@@ -1726,6 +1767,11 @@ func (a *App) View() string {
17261767
return zone.Scan(a.renderConnectionDialog())
17271768
}
17281769

1770+
// If password dialog is showing, render it
1771+
if a.showPasswordDialog {
1772+
return zone.Scan(a.renderPasswordDialog())
1773+
}
1774+
17291775
// If in help mode, show help overlay
17301776
if a.state.ViewMode == models.HelpMode {
17311777
return help.Render(a.state.Width, a.state.Height, lipgloss.NewStyle())
@@ -2749,7 +2795,18 @@ func (a *App) connectToHistoryEntry(entry models.ConnectionHistoryEntry) (tea.Mo
27492795

27502796
// Convert history entry to connection config WITH password from keyring
27512797
if a.connectionHistory != nil {
2752-
config = a.connectionHistory.GetConnectionConfigWithPassword(&entry)
2798+
result := a.connectionHistory.GetConnectionConfigWithPassword(&entry)
2799+
config = result.Config
2800+
2801+
// If password is missing, show password dialog
2802+
if result.PasswordMissing {
2803+
entryCopy := entry
2804+
a.pendingConnectionInfo = &entryCopy
2805+
a.passwordDialog.SetConnectionInfo(entry.Host, entry.Port, entry.Database, entry.User)
2806+
a.showPasswordDialog = true
2807+
a.showConnectionDialog = false
2808+
return a, a.passwordDialog.Init()
2809+
}
27532810
} else {
27542811
config = entry.ToConnectionConfig()
27552812
}
@@ -2799,9 +2856,13 @@ func (a *App) performConnection(config models.ConnectionConfig) (tea.Model, tea.
27992856

28002857
// Save to connection history (ignore errors)
28012858
if a.connectionHistory != nil {
2802-
if err := a.connectionHistory.Add(config); err != nil {
2859+
result, err := a.connectionHistory.Add(config)
2860+
if err != nil {
28032861
log.Printf("Warning: Failed to save connection to history: %v", err)
28042862
} else {
2863+
if result != nil && result.PasswordSaveError != nil {
2864+
log.Printf("Warning: Failed to save password: %v", result.PasswordSaveError)
2865+
}
28052866
// Reload history in dialog
28062867
history := a.connectionHistory.GetRecent(10)
28072868
a.connectionDialog.SetHistoryEntries(history)
@@ -2965,9 +3026,13 @@ func (a *App) handleConnectionDialog(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
29653026

29663027
// Save to connection history (ignore errors)
29673028
if a.connectionHistory != nil {
2968-
if err := a.connectionHistory.Add(config); err != nil {
3029+
result, err := a.connectionHistory.Add(config)
3030+
if err != nil {
29693031
log.Printf("Warning: Failed to save connection to history: %v", err)
29703032
} else {
3033+
if result != nil && result.PasswordSaveError != nil {
3034+
log.Printf("Warning: Failed to save password: %v", result.PasswordSaveError)
3035+
}
29713036
// Reload history in dialog
29723037
history := a.connectionHistory.GetRecent(10)
29733038
a.connectionDialog.SetHistoryEntries(history)
@@ -2993,7 +3058,18 @@ func (a *App) handleConnectionDialog(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
29933058

29943059
// Convert history entry to connection config WITH password from keyring
29953060
if a.connectionHistory != nil {
2996-
config = a.connectionHistory.GetConnectionConfigWithPassword(historyEntry)
3061+
result := a.connectionHistory.GetConnectionConfigWithPassword(historyEntry)
3062+
config = result.Config
3063+
3064+
// If password is missing, show password dialog
3065+
if result.PasswordMissing {
3066+
entryCopy := *historyEntry
3067+
a.pendingConnectionInfo = &entryCopy
3068+
a.passwordDialog.SetConnectionInfo(historyEntry.Host, historyEntry.Port, historyEntry.Database, historyEntry.User)
3069+
a.showPasswordDialog = true
3070+
a.showConnectionDialog = false
3071+
return a, a.passwordDialog.Init()
3072+
}
29973073
} else {
29983074
config = historyEntry.ToConnectionConfig()
29993075
}
@@ -3045,9 +3121,13 @@ func (a *App) handleConnectionDialog(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
30453121

30463122
// Save to connection history (ignore errors)
30473123
if a.connectionHistory != nil {
3048-
if err := a.connectionHistory.Add(config); err != nil {
3124+
result, err := a.connectionHistory.Add(config)
3125+
if err != nil {
30493126
log.Printf("Warning: Failed to save connection to history: %v", err)
30503127
} else {
3128+
if result != nil && result.PasswordSaveError != nil {
3129+
log.Printf("Warning: Failed to save password: %v", result.PasswordSaveError)
3130+
}
30513131
// Reload history in dialog
30523132
history := a.connectionHistory.GetRecent(10)
30533133
a.connectionDialog.SetHistoryEntries(history)
@@ -3323,6 +3403,33 @@ func (a *App) renderConnectionDialog() string {
33233403
return style.Render(dialog)
33243404
}
33253405

3406+
func (a *App) renderPasswordDialog() string {
3407+
// Center the dialog
3408+
dialogWidth := 50
3409+
dialogHeight := 12
3410+
3411+
a.passwordDialog.Width = dialogWidth
3412+
a.passwordDialog.Height = dialogHeight
3413+
3414+
dialog := a.passwordDialog.View()
3415+
3416+
// Center it
3417+
verticalPadding := (a.state.Height - dialogHeight) / 2
3418+
horizontalPadding := (a.state.Width - dialogWidth) / 2
3419+
3420+
if verticalPadding < 0 {
3421+
verticalPadding = 0
3422+
}
3423+
if horizontalPadding < 0 {
3424+
horizontalPadding = 0
3425+
}
3426+
3427+
style := lipgloss.NewStyle().
3428+
Padding(verticalPadding, 0, 0, horizontalPadding)
3429+
3430+
return style.Render(dialog)
3431+
}
3432+
33263433
// triggerDiscovery runs discovery in the background and returns a command
33273434
func (a *App) triggerDiscovery() tea.Cmd {
33283435
return func() tea.Msg {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package connection_history
2+
3+
import "errors"
4+
5+
// ErrPasswordNotFound is returned when a password is not found in the keyring
6+
var ErrPasswordNotFound = errors.New("password not found in keyring")
7+
8+
// PasswordSaveError represents an error that occurred while saving a password
9+
type PasswordSaveError struct {
10+
Err error
11+
Message string
12+
}
13+
14+
func (e *PasswordSaveError) Error() string {
15+
if e.Message != "" {
16+
return e.Message + ": " + e.Err.Error()
17+
}
18+
return e.Err.Error()
19+
}
20+
21+
func (e *PasswordSaveError) Unwrap() error {
22+
return e.Err
23+
}
24+
25+
// PasswordReadError represents an error that occurred while reading a password
26+
type PasswordReadError struct {
27+
Err error
28+
}
29+
30+
func (e *PasswordReadError) Error() string {
31+
return "failed to read password from keyring: " + e.Err.Error()
32+
}
33+
34+
func (e *PasswordReadError) Unwrap() error {
35+
return e.Err
36+
}

0 commit comments

Comments
 (0)