diff --git a/src/pgo.erl b/src/pgo.erl index 267edf0..b2aab52 100644 --- a/src/pgo.erl +++ b/src/pgo.erl @@ -60,7 +60,7 @@ -type pool_config() :: #{host => string(), port => integer(), user => string(), - password => string(), + password => string() | fun(() -> iodata()), database => string(), %% pool specific settings diff --git a/src/pgo_handler.erl b/src/pgo_handler.erl index a4ee16f..a3ac18c 100644 --- a/src/pgo_handler.erl +++ b/src/pgo_handler.erl @@ -18,6 +18,12 @@ -define(DEFAULT_USER, "postgres"). -define(DEFAULT_PASSWORD, ""). +resolve_password(Options) -> + case maps:get(password, Options, ?DEFAULT_PASSWORD) of + Fun when is_function(Fun, 0) -> Fun(); + Password -> Password + end. + % driver options. %% -type open_option() :: %% {host, inet:ip_address() | inet:hostname()} % default: ?DEFAULT_HOST @@ -229,7 +235,7 @@ setup_startup(Conn=#conn{socket_module=SocketModule, end. setup_authenticate_cleartext_password(Conn, Options) -> - Password = maps:get(password, Options, ?DEFAULT_PASSWORD), + Password = resolve_password(Options), setup_authenticate_password(Conn, Password). setup_authenticate_sasl_password(Conn, MethodsBinary, Options) -> @@ -291,7 +297,7 @@ scram_client_final(Nonce, ServerFirst, #conn{socket_module=SocketModule, pool=Pool}, Options) -> User = maps:get(user, Options, ?DEFAULT_USER), ServerFirstParts = pgo_scram:parse_server_first(ServerFirst, Nonce), - Password = maps:get(password, Options, ?DEFAULT_PASSWORD), + Password = resolve_password(Options), {ClientFinalMessage, ServerProof} = pgo_scram:get_client_final(ServerFirstParts, Nonce, User, Password), case SocketModule:send(Socket, pgo_protocol:encode_scram_response_message(ClientFinalMessage)) of ok -> @@ -302,7 +308,7 @@ scram_client_final(Nonce, ServerFirst, #conn{socket_module=SocketModule, setup_authenticate_md5_password(Conn, Salt, Options) -> User = maps:get(user, Options, ?DEFAULT_USER), - Password = maps:get(password, Options, ?DEFAULT_PASSWORD), + Password = resolve_password(Options), % concat('md5', md5(concat(md5(concat(password, username)), random-salt))) <> = crypto:hash(md5, [Password, User]), MD51Hex = io_lib:format("~32.16.0b", [MD51Int]), diff --git a/test/pgo_SUITE.erl b/test/pgo_SUITE.erl index 2bfe863..fe5e36b 100644 --- a/test/pgo_SUITE.erl +++ b/test/pgo_SUITE.erl @@ -16,7 +16,7 @@ all() -> [checkout_checkin, checkout_break, recheckout, kill_socket, kill_pid, checkout_kill, checkout_disconnect, checkout_query_crash, - query_timeout]. + password_as_function, query_timeout]. init_per_suite(Config) -> Config. @@ -24,6 +24,16 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. +init_per_testcase(password_as_function, Config) -> + Pool = pool_password_as_function, + application:ensure_all_started(pgo), + pgo_sup:start_child(Pool, #{pool_size => 1, + database => ?DATABASE, + user => ?USER, + password => fun() -> ?PASSWORD end}), + Tid = pgo_pool:tid(Pool), + ?UNTIL((catch ets:info(Tid, size)) =:= 1), + [{pool_name, Pool} | Config]; init_per_testcase(T, Config) when T =:= checkout_break ; T =:= checkout_query_crash ; T =:= recheckout ; @@ -225,6 +235,11 @@ checkout_disconnect(Config) -> ok. +password_as_function(Config) -> + Name = ?config(pool_name, Config), + ?assertMatch(#{rows := [{1}]}, pgo:query("select 1", [], #{pool => Name})), + ok. + %% regression test. this would fail with `unexpected message` response to the create query checkout_query_crash(Config) -> Name = ?config(pool_name, Config),