22
33from __future__ import annotations
44
5+ import copy
56from dataclasses import dataclass
67from typing import Any
78
@@ -32,19 +33,38 @@ def populate_credentials(self, config: ConnectionConfig) -> ConnectionConfig:
3233 return config
3334
3435 service = self ._services .credentials_service
35- if endpoint and endpoint .password is None and uses_db_password (config ):
36+ if endpoint and endpoint .password is None and not endpoint . password_command and uses_db_password (config ):
3637 password = service .get_password (config .name )
3738 if password is not None :
3839 endpoint .password = password
39- if config .tunnel and config .tunnel .password is None :
40+ if config .tunnel and config .tunnel .password is None and not config . tunnel . password_command :
4041 ssh_password = service .get_ssh_password (config .name )
4142 if ssh_password is not None :
4243 config .tunnel .password = ssh_password
4344 return config
4445
46+ def _resolve_dynamic_credentials (self , config : ConnectionConfig ) -> ConnectionConfig :
47+ """Resolve commands on a copy so short-lived secrets are not persisted."""
48+ from sqlit .domains .connections .domain .password_command import (
49+ run_password_command ,
50+ )
51+ from sqlit .domains .connections .providers .config_service import (
52+ normalize_connection_config ,
53+ )
54+
55+ resolved = normalize_connection_config (copy .deepcopy (config ))
56+ endpoint = resolved .tcp_endpoint
57+ if endpoint and endpoint .password is None and endpoint .password_command and resolved .db_type != "postgresql" :
58+ # PostgreSQL resolves this at the adapter boundary so reconnects and
59+ # database switches always fetch a fresh short-lived token.
60+ endpoint .password = run_password_command (endpoint .password_command )
61+ if resolved .tunnel and resolved .tunnel .auth_type == "password" and resolved .tunnel .password is None and resolved .tunnel .password_command :
62+ resolved .tunnel .password = run_password_command (resolved .tunnel .password_command )
63+ return resolved
64+
4565 def connect (self , config : ConnectionConfig ) -> Any :
4666 """Create a session for the given config."""
47- return self ._services .session_factory (config )
67+ return self ._services .session_factory (self . _resolve_dynamic_credentials ( config ) )
4868
4969 def test_connection (self , config : ConnectionConfig ) -> ConnectionTestResult :
5070 """Test a connection without mutating UI state."""
@@ -55,13 +75,14 @@ def test_connection(self, config: ConnectionConfig) -> ConnectionTestResult:
5575 error : Exception | None = None
5676
5777 try :
58- tunnel , host , port = self ._services .tunnel_factory (config )
78+ resolved = self ._resolve_dynamic_credentials (config )
79+ tunnel , host , port = self ._services .tunnel_factory (resolved )
5980 if tunnel :
60- connect_config = config .with_endpoint (host = host , port = str (port ))
81+ connect_config = resolved .with_endpoint (host = host , port = str (port ))
6182 else :
62- connect_config = config
83+ connect_config = resolved
6384
64- provider = self ._services .provider_factory (config .db_type )
85+ provider = self ._services .provider_factory (resolved .db_type )
6586 conn = provider .connection_factory .connect (connect_config )
6687 conn .close ()
6788 except Exception as exc :
0 commit comments