merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit 16d84e8a146dd3eacab92315ad2f654066e11066
parent d67d0a862d7a23e7d9a3f15d0920b1ba6450d7a6
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  6 Aug 2026 00:06:34 +0200

handle auth_hash and auth_salt are technically NULLable, so handle this accordingly

Diffstat:
Msrc/backenddb/get_instance_auth.c | 33+++++++++++++++++++++++++--------
Msrc/backenddb/test_merchantdb.c | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/merchant-database/get_instance_auth.h | 3++-
3 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/src/backenddb/get_instance_auth.c b/src/backenddb/get_instance_auth.c @@ -34,13 +34,20 @@ TALER_MERCHANTDB_get_instance_auth ( GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_end }; + bool no_hash; + bool no_salt; struct GNUNET_PQ_ResultSpec rs[] = { - GNUNET_PQ_result_spec_auto_from_type ("auth_hash", - &ias->auth_hash), - GNUNET_PQ_result_spec_auto_from_type ("auth_salt", - &ias->auth_salt), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("auth_hash", + &ias->auth_hash), + &no_hash), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_auto_from_type ("auth_salt", + &ias->auth_salt), + &no_salt), GNUNET_PQ_result_spec_end }; + enum GNUNET_DB_QueryStatus qs; PREPARE (pg, "get_instance_auth", @@ -49,8 +56,18 @@ TALER_MERCHANTDB_get_instance_auth ( ",auth_salt" " FROM merchant.merchant_instances" " WHERE merchant_id=$1"); - return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, - "get_instance_auth", - params, - rs); + memset (&ias->auth_hash, + 0, + sizeof (ias->auth_hash)); + memset (&ias->auth_salt, + 0, + sizeof (ias->auth_salt)); + qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "get_instance_auth", + params, + rs); + /* 'auth_hash' and 'auth_salt' are nullable; the all-zero value we + left them at is how the rest of the code spells "no password + configured" (see TMH_check_auth()). */ + return qs; } diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c @@ -99,6 +99,7 @@ #include "merchant-database/iterate_webhooks_by_event.h" #include "merchant-database/iterate_webhooks.h" #include "merchant-database/get_exchange_wire_fee.h" +#include "merchant-database/get_instance_auth.h" #include "merchant-database/update_to_contract_terms_paid.h" #include "merchant-database/update_to_contract_terms_wired.h" #include "merchant-database/insert_refund_by_coin.h" @@ -895,6 +896,57 @@ cleanup: /** + * Tests that an instance whose 'auth_hash'/'auth_salt' are NULL is + * read back as "no password configured" instead of failing. + * + * Both columns are nullable, but the result spec did not allow NULL, + * so such a row made TALER_MERCHANTDB_get_instance_auth() return a + * hard error (HTTP 500 on every authenticated request of that + * instance). No code path in the tree writes NULL today -- the + * "no password" state is an all-zero hash -- so the NULL is set here + * directly via SQL. + * + * @param instance the instance to run the test against. + * @return 0 on success, 1 otherwise. + */ +static int +test_get_instance_auth_null (const struct InstanceData *instance) +{ + struct TALER_MERCHANTDB_InstanceAuthSettings ias; + char sql[256]; + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute (sql), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + GNUNET_snprintf (sql, + sizeof (sql), + "UPDATE merchant.merchant_instances" + " SET auth_hash=NULL" + " ,auth_salt=NULL" + " WHERE merchant_id='%s'", + instance->instance.id); + TEST_COND_RET_ON_FAIL (GNUNET_OK == + GNUNET_PQ_exec_statements (pg->conn, + es), + "Failed to NULL out the instance authentication\n"); + memset (&ias, + 42, + sizeof (ias)); + TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == + TALER_MERCHANTDB_get_instance_auth ( + pg, + instance->instance.id, + &ias), + "Lookup of NULL instance authentication failed\n"); + TEST_COND_RET_ON_FAIL (GNUNET_is_zero (&ias.auth_hash) && + GNUNET_is_zero (&ias.auth_salt), + "NULL instance authentication was not zeroed out\n"); + return 0; +} + + +/** * Tests that storing a report always yields a fresh serial. * * 'merchant_reports' has no unique key other than the identity @@ -1054,6 +1106,8 @@ run_test_instances (struct TestInstances_Closure *cls) TEST_RET_ON_FAIL (test_reconnect_search_path (&cls->instances[0])); /* Test that every stored report gets its own serial */ TEST_RET_ON_FAIL (test_insert_report_serial (&cls->instances[0])); + /* Test that a NULL authentication hash/salt is not an error */ + TEST_RET_ON_FAIL (test_get_instance_auth_null (&cls->instances[0])); /* Test instance private key deletion */ TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0], GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); diff --git a/src/include/merchant-database/get_instance_auth.h b/src/include/merchant-database/get_instance_auth.h @@ -32,7 +32,8 @@ struct TALER_MERCHANTDB_PostgresContext; * * @param pg database context * @param instance_id instance to query - * @param[out] ias where to store the auth data + * @param[out] ias where to store the auth data; the hash and the salt + * are zeroed out if the instance has no password configured */ enum GNUNET_DB_QueryStatus TALER_MERCHANTDB_get_instance_auth (struct TALER_MERCHANTDB_PostgresContext *pg,