merchant

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

commit c88681f75eec657d7a54d62e5ac32a23392c4b3b
parent 0922dcdf0cf7bb584e78e6ee2c038032a4b27fd2
Author: Christian Grothoff <christian@grothoff.org>
Date:   Fri,  5 Sep 2025 18:36:44 +0200

-debugging MFA logic (WiP)

Diffstat:
Msrc/backend/taler-merchant-httpd_mfa.c | 11+++++++++--
Msrc/backend/taler-merchant-httpd_post-challenge-ID.c | 2+-
Msrc/backenddb/merchant-0023.sql | 2+-
Msrc/backenddb/pg_lookup_mfa_challenge.c | 13+++++++++----
Msrc/backenddb/pg_solve_mfa_challenge.c | 2+-
Msrc/backenddb/pg_solve_mfa_challenge.sql | 4++--
Msrc/testing/test_merchant_mfa.conf | 6+++---
Msrc/testing/test_merchant_mfa.sh | 116++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/util/mfa.c | 4++--
9 files changed, 140 insertions(+), 20 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_mfa.c b/src/backend/taler-merchant-httpd_mfa.c @@ -78,7 +78,7 @@ TMH_mfa_parse_challenge_id (struct TMH_HandlerContext *hc, } if (GNUNET_OK != GNUNET_STRINGS_string_to_data (dash + 1, - strlen (dash) + 1, + strlen (dash + 1), h_body, sizeof (*h_body))) { @@ -242,6 +242,7 @@ mfa_challenge_start ( struct TALER_MERCHANT_MFA_BodySalt salt; struct TALER_MERCHANT_MFA_BodyHash h_body; uint64_t challenge_serial; + char *code; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &salt, @@ -249,17 +250,23 @@ mfa_challenge_start ( TALER_MERCHANT_mfa_body_hash (hc->request_body, &salt, &h_body); + GNUNET_asprintf (&code, + "%llu", + (unsigned long long) + GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, + 1000 * 1000 * 100)); qs = TMH_db->create_mfa_challenge (TMH_db->cls, hc->instance->settings.id, op, &h_body, &salt, - NULL, + code, expiration_date, GNUNET_TIME_UNIT_ZERO_ABS, channel, required_address, &challenge_serial); + GNUNET_free (code); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID.c b/src/backend/taler-merchant-httpd_post-challenge-ID.c @@ -539,7 +539,7 @@ phase_lookup (struct MfaState *mfa) NULL); return; } - if (! GNUNET_TIME_absolute_is_future (retransmission_date)) + if (GNUNET_TIME_absolute_is_future (retransmission_date)) { /* too early to try again */ respond_with_error (mfa, diff --git a/src/backenddb/merchant-0023.sql b/src/backenddb/merchant-0023.sql @@ -37,7 +37,7 @@ CREATE TYPE op_enum CREATE TABLE tan_challenges (challenge_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE - ,h_body BYTEA NOT NULL CHECK (LENGTH(h_body)=64) + ,h_body BYTEA NOT NULL CHECK (LENGTH(h_body)=32) ,salt BYTEA NOT NULL CHECK (LENGTH(salt)=16) ,op op_enum NOT NULL ,code TEXT NOT NULL diff --git a/src/backenddb/pg_lookup_mfa_challenge.c b/src/backenddb/pg_lookup_mfa_challenge.c @@ -49,13 +49,16 @@ TMH_PG_lookup_mfa_challenge ( }; char *op_str; char *chan_str; + bool no_conf; struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("op", &op_str), GNUNET_PQ_result_spec_auto_from_type ("salt", salt), - GNUNET_PQ_result_spec_absolute_time ("confirmation_date", - confirmation_date), + GNUNET_PQ_result_spec_allow_null ( + GNUNET_PQ_result_spec_absolute_time ("confirmation_date", + confirmation_date), + &no_conf), GNUNET_PQ_result_spec_absolute_time ("retransmission_date", retransmission_date), GNUNET_PQ_result_spec_uint32 ("retry_counter", @@ -83,8 +86,8 @@ TMH_PG_lookup_mfa_challenge ( " (SELECT merchant_serial" " FROM merchant_instances" " WHERE merchant_id = $1)" - " AND (tc.challenge_id = $2)" - " AND (tc.h_body = $3)" + " AND (challenge_id = $2)" + " AND (h_body = $3)" " AND (expiration_date / 1000 / 1000" " > EXTRACT(EPOCH FROM NOW()" " AT TIME ZONE 'UTC')::bigint)"); @@ -98,6 +101,8 @@ TMH_PG_lookup_mfa_challenge ( rs); if (qs <= 0) return qs; + if (no_conf) + *confirmation_date = GNUNET_TIME_UNIT_FOREVER_ABS; *tan_channel = TALER_MERCHANT_MFA_channel_from_string (chan_str); *op = TALER_MERCHANT_MFA_co_from_string (op_str); GNUNET_free (chan_str); diff --git a/src/backenddb/pg_solve_mfa_challenge.c b/src/backenddb/pg_solve_mfa_challenge.c @@ -65,7 +65,7 @@ TMH_PG_solve_mfa_challenge ( "SELECT" " out_solved" " ,out_retry_counter" - " FROM merchant_do_insert_issued_token" + " FROM merchant_do_solve_mfa_challenge" " ($1, $2, $3, $4);"); qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "solve_mfa_challenge", diff --git a/src/backenddb/pg_solve_mfa_challenge.sql b/src/backenddb/pg_solve_mfa_challenge.sql @@ -65,11 +65,11 @@ BEGIN -- Check if already solved before IF my_confirmation_date IS NOT NULL THEN - out_solved := TRUE; + out_solved = TRUE; RETURN; END IF; - IF (0 == out_retry_counter) + IF (0 = out_retry_counter) THEN out_solved = FALSE; RETURN; diff --git a/src/testing/test_merchant_mfa.conf b/src/testing/test_merchant_mfa.conf @@ -14,10 +14,10 @@ PORT = 9966 SERVE = tcp DB = postgres -HELPER_SMS = test_sms_helper.sh -HELPER_EMAIL = test_email_helper.sh +HELPER_SMS = ./test_sms_helper.sh +HELPER_EMAIL = ./test_email_helper.sh MANDATORY_TAN_CHANNELS = sms email - +ENABLE_SELF_PROVISIONING = YES # This specifies which database the postgres backend uses. [merchantdb-postgres] diff --git a/src/testing/test_merchant_mfa.sh b/src/testing/test_merchant_mfa.sh @@ -22,9 +22,9 @@ set -eu # Launch system. -setup \ - -c "test_merchant_mfa.conf" \ - -m +#setup \ +# -c "test_merchant_mfa.conf" \ +# -m LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX) echo -n "Configuring a merchant admin instance ..." @@ -32,7 +32,7 @@ echo -n "Configuring a merchant admin instance ..." STATUS=$(curl -H "Content-Type: application/json" -X POST \ -H 'Authorization: Bearer secret-token:super_secret' \ http://localhost:9966/management/instances \ - -d '{"auth":{"method":"external"},"id":"admin","name":"default","user_type":"business","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ + -d '{"auth":{"method":"external"},"id":"admin","name":"default","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ -w "%{http_code}" -s \ -o "$LAST_RESPONSE") @@ -56,6 +56,114 @@ then exit_fail "Expected 200 OK. Got: $STATUS" fi +echo -n "Self-provision instance ..." + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + http://localhost:9966/instances \ + -d '{"auth":{"method":"external"},"id":"self","name":"default","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "202" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 202 Accepted. Got: $STATUS" +fi + +C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") +C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE") + +echo -n "Requesting challenge $C1 " + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + "http://localhost:9966/challenge/$C1" \ + -d '{}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204 OK. Got: $STATUS" +fi +echo "OK" + +TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1) +ADDR=$(cat /tmp/test-merchant-sms-address.txt) + +if [ "$ADDR" != "1234" ] +then + exit_fail "Expected address '1234'. Got: $ADDR" +fi + +echo -n "Sending challenge $C1 solution " + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + "http://localhost:9966/challenge/$C1/confirm" \ + -d '{"tan":"'"$TAN"'"}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204 OK. Got: $STATUS" +fi +echo "OK" + + +echo -n "Requesting challenge $C2 " + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + "http://localhost:9966/challenge/$C2" \ + -d '{}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204 OK. Got: $STATUS" +fi +echo "OK" + +TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1) + +echo -n "Sending challenge $C2 solution " + +STATUS=$(curl -H "Content-Type: application/json" -X POST \ + "http://localhost:9966/challenge/$C2/confirm" \ + -d '{"tan":"'"$TAN"'"}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204 OK. Got: $STATUS" +fi +echo "OK" + + + +echo -n "Retrying instance creation " + +STATUS=$(curl \ + -H "Content-Type: application/json" \ + -H "Taler-Challenge-Ids: $C1,$C2" \ + -X POST \ + http://localhost:9966/instances \ + -d '{"auth":{"method":"external"},"id":"self","name":"default","phone_number":"1234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204 OK. Got: $STATUS" +fi + + STATUS=$(curl -H "Content-Type: application/json" -X POST \ -H 'Authorization: Bearer secret-token:super_secret' \ http://localhost:9966/private/accounts \ diff --git a/src/util/mfa.c b/src/util/mfa.c @@ -68,7 +68,7 @@ TALER_MERCHANT_MFA_co_from_string (const char *str) { if (NULL == str) return TALER_MERCHANT_MFA_CO_NONE; - for (unsigned int i = 0; + for (unsigned int i = 1; i < sizeof (co_strings) / sizeof (co_strings[0]); i++) { @@ -100,7 +100,7 @@ TALER_MERCHANT_MFA_channel_from_string (const char *str) { if (NULL == str) return TALER_MERCHANT_MFA_CHANNEL_NONE; - for (unsigned int i = 0; + for (unsigned int i = 1; i < sizeof (channel_strings) / sizeof (channel_strings[0]); i++) {