merchant

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

commit 758c34eba6e4c5557743b4e53630f1748289957c
parent c88681f75eec657d7a54d62e5ac32a23392c4b3b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat,  6 Sep 2025 20:22:06 +0200

finish implementation of #9816, #9817 and #10181 in merchant backend

Diffstat:
Msrc/backend/taler-merchant-httpd_mfa.c | 16+++++++++++-----
Msrc/backend/taler-merchant-httpd_post-challenge-ID-confirm.c | 1-
Msrc/backend/taler-merchant-httpd_post-challenge-ID.c | 1-
Msrc/backend/taler-merchant-httpd_private-post-account.c | 3+++
Msrc/backend/taler-merchant-httpd_private-post-instances-ID-auth.c | 4++++
Msrc/backenddb/merchant-0023.sql | 8--------
Msrc/backenddb/pg_create_mfa_challenge.c | 13++++---------
Msrc/backenddb/pg_create_mfa_challenge.h | 2--
Msrc/backenddb/pg_gc.c | 4+++-
Msrc/backenddb/pg_insert_instance.c | 6+++++-
Msrc/backenddb/pg_lookup_mfa_challenge.c | 16+++++-----------
Msrc/backenddb/pg_lookup_mfa_challenge.h | 2--
Msrc/backenddb/pg_solve_mfa_challenge.c | 4++--
Msrc/backenddb/pg_solve_mfa_challenge.h | 2--
Msrc/backenddb/pg_solve_mfa_challenge.sql | 17+++--------------
Msrc/backenddb/pg_update_instance.c | 4++++
Msrc/backenddb/procedures.sql.in | 10+++-------
Msrc/include/taler_merchantdb_plugin.h | 6------
Msrc/testing/test_merchant_mfa.sh | 227++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
19 files changed, 261 insertions(+), 85 deletions(-)

diff --git a/src/backend/taler-merchant-httpd_mfa.c b/src/backend/taler-merchant-httpd_mfa.c @@ -134,10 +134,13 @@ mfa_challenge_check ( struct GNUNET_TIME_Absolute confirmation_date; enum GNUNET_GenericReturnValue ret; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Checking status of challenge %s\n", + challenge_id); ret = TMH_mfa_parse_challenge_id (hc, challenge_id, &challenge_serial, - &h_body); + &x_h_body); if (GNUNET_OK != ret) return ret; *target_address = NULL; @@ -145,7 +148,6 @@ mfa_challenge_check ( *channel = TALER_MERCHANT_MFA_CHANNEL_NONE; *retry_counter = UINT_MAX; qs = TMH_db->lookup_mfa_challenge (TMH_db->cls, - hc->instance->settings.id, challenge_serial, &x_h_body, &salt, @@ -176,6 +178,9 @@ mfa_challenge_check ( ? GNUNET_SYSERR : GNUNET_NO; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Challenge %s not found\n", + challenge_id); return GNUNET_OK; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; @@ -256,7 +261,6 @@ mfa_challenge_start ( 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, @@ -655,10 +659,10 @@ TMH_mfa_check_simple ( enum GNUNET_GenericReturnValue ret; bool have_sms = (NULL != mi->settings.phone) && (NULL != TMH_helper_sms) && - (! mi->settings.phone_validated); + (mi->settings.phone_validated); bool have_email = (NULL != mi->settings.email) && (NULL != TMH_helper_email) && - (! mi->settings.email_validated); + (mi->settings.email_validated); /* Note: we check for 'validated' above, but in theory we could also use unvalidated for this operation. @@ -689,6 +693,8 @@ TMH_mfa_check_simple ( } else { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No MFA possible, skipping 2-FA\n"); ret = GNUNET_OK; } return ret; diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID-confirm.c b/src/backend/taler-merchant-httpd_post-challenge-ID-confirm.c @@ -68,7 +68,6 @@ TMH_post_challenge_ID_confirm (const struct TMH_RequestHandler *rh, } qs = TMH_db->solve_mfa_challenge (TMH_db->cls, challenge_serial, - hc->instance->settings.id, &h_body, tan, &solved, diff --git a/src/backend/taler-merchant-httpd_post-challenge-ID.c b/src/backend/taler-merchant-httpd_post-challenge-ID.c @@ -494,7 +494,6 @@ phase_lookup (struct MfaState *mfa) enum TALER_MERCHANT_MFA_CriticalOperation op; qs = TMH_db->lookup_mfa_challenge (TMH_db->cls, - mfa->hc->instance->settings.id, mfa->challenge_id, &mfa->h_body, &salt, diff --git a/src/backend/taler-merchant-httpd_private-post-account.c b/src/backend/taler-merchant-httpd_private-post-account.c @@ -121,6 +121,9 @@ TMH_private_post_account (const struct TMH_RequestHandler *rh, TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION, mi); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Account creation MFA check returned %d\n", + (int) ret); if (GNUNET_OK != ret) { return (GNUNET_NO == ret) diff --git a/src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c b/src/backend/taler-merchant-httpd_private-post-instances-ID-auth.c @@ -74,6 +74,8 @@ post_instances_ID_auth (struct TMH_MerchantInstance *mi, (NULL == TMH_helper_sms) || (! mi->settings.phone_validated) ) ) ) { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Cannot reset password: SMS factor not available\n"); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_FORBIDDEN, @@ -85,6 +87,8 @@ post_instances_ID_auth (struct TMH_MerchantInstance *mi, (NULL == TMH_helper_email) || (! mi->settings.email_validated) ) ) ) { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Cannot reset password: E-mail factor not available\n"); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_FORBIDDEN, diff --git a/src/backenddb/merchant-0023.sql b/src/backenddb/merchant-0023.sql @@ -46,9 +46,6 @@ CREATE TABLE tan_challenges ,retransmission_date INT8 NOT NULL DEFAULT 0 ,confirmation_date INT8 DEFAULT NULL ,retry_counter INT4 NOT NULL - ,merchant_serial INT8 NOT NULL - REFERENCES merchant_instances(merchant_serial) - ON DELETE CASCADE ,tan_channel tan_enum NOT NULL ,required_address TEXT NOT NULL ); @@ -79,11 +76,6 @@ COMMENT ON COLUMN tan_challenges.tan_channel COMMENT ON COLUMN tan_challenges.required_address IS 'Address to which the challenge will be sent'; -CREATE INDEX tan_challenges_lookup_index - ON tan_challenges (merchant_serial,op,expiration_date,creation_date DESC); -COMMENT ON INDEX tan_challenges_lookup_index - IS 'for lookup_mfa_challenge collection'; - CREATE INDEX tan_challenges_expiration_index ON tan_challenges (expiration_date); COMMENT ON INDEX tan_challenges_expiration_index diff --git a/src/backenddb/pg_create_mfa_challenge.c b/src/backenddb/pg_create_mfa_challenge.c @@ -29,7 +29,6 @@ enum GNUNET_DB_QueryStatus TMH_PG_create_mfa_challenge ( void *cls, - const char *instance_id, enum TALER_MERCHANT_MFA_CriticalOperation op, const struct TALER_MERCHANT_MFA_BodyHash *h_body, const struct TALER_MERCHANT_MFA_BodySalt *salt, @@ -52,9 +51,8 @@ TMH_PG_create_mfa_challenge ( GNUNET_PQ_query_param_absolute_time (&now), /* $5 */ GNUNET_PQ_query_param_absolute_time (&expiration_date), GNUNET_PQ_query_param_absolute_time (&retransmission_date), - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (channel_str), - GNUNET_PQ_query_param_string (required_address), /* $10 */ + GNUNET_PQ_query_param_string (required_address), /* $9 */ GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { @@ -73,14 +71,11 @@ TMH_PG_create_mfa_challenge ( " ,creation_date" " ,expiration_date" " ,retransmission_date" - " ,retry_counter" - " ,merchant_serial" + " ,retry_counter" /* always set to 3 */ " ,tan_channel" " ,required_address)" - " SELECT" - " $1, $2, $3, $4, $5, $6, $7, 3, merchant_serial, $9, $10" - " FROM merchant_instances" - " WHERE merchant_id=$8" + " VALUES" + " ($1, $2, $3, $4, $5, $6, $7, 3, $8, $9)" " RETURNING challenge_id;"); return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, "create_mfa_challenge", diff --git a/src/backenddb/pg_create_mfa_challenge.h b/src/backenddb/pg_create_mfa_challenge.h @@ -30,7 +30,6 @@ * Create new multi-factor authorization (MFA) challenge in the database. * * @param cls closure - * @param instance_id instance the operation effects * @param op operation that triggered the MFA request * @param h_body hash of the request body * @param salt salt used to compute @a h_body @@ -47,7 +46,6 @@ enum GNUNET_DB_QueryStatus TMH_PG_create_mfa_challenge ( void *cls, - const char *instance_id, enum TALER_MERCHANT_MFA_CriticalOperation op, const struct TALER_MERCHANT_MFA_BodyHash *h_body, const struct TALER_MERCHANT_MFA_BodySalt *salt, diff --git a/src/backenddb/pg_gc.c b/src/backenddb/pg_gc.c @@ -30,7 +30,9 @@ enum GNUNET_GenericReturnValue TMH_PG_gc (void *cls) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_Context *conn; @@ -43,7 +45,7 @@ TMH_PG_gc (void *cls) }; struct GNUNET_PQ_PreparedStatement ps[] = { GNUNET_PQ_make_prepare ("run_gc", - "CALL merchant_do_gc ();"), + "CALL merchant_do_gc ($1);"), GNUNET_PQ_PREPARED_STATEMENT_END }; diff --git a/src/backenddb/pg_insert_instance.c b/src/backenddb/pg_insert_instance.c @@ -60,6 +60,8 @@ TMH_PG_insert_instance ( (NULL == is->phone) ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_string (is->phone), + GNUNET_PQ_query_param_bool (is->phone_validated), + GNUNET_PQ_query_param_bool (is->email_validated), GNUNET_PQ_query_param_bool (validation_needed), GNUNET_PQ_query_param_end }; @@ -88,9 +90,11 @@ TMH_PG_insert_instance ( ",email" ",logo" ",phone_number" + ",phone_validated" + ",email_validated" ",validation_needed)" "VALUES" - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)") + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)") ; qs = GNUNET_PQ_eval_prepared_non_select (pg->conn, "insert_instance", diff --git a/src/backenddb/pg_lookup_mfa_challenge.c b/src/backenddb/pg_lookup_mfa_challenge.c @@ -29,7 +29,6 @@ enum GNUNET_DB_QueryStatus TMH_PG_lookup_mfa_challenge ( void *cls, - const char *instance_id, uint64_t challenge_id, const struct TALER_MERCHANT_MFA_BodyHash *h_body, struct TALER_MERCHANT_MFA_BodySalt *salt, @@ -41,10 +40,11 @@ TMH_PG_lookup_mfa_challenge ( enum TALER_MERCHANT_MFA_Channel *tan_channel) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct GNUNET_PQ_QueryParam params[] = { - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_uint64 (&challenge_id), GNUNET_PQ_query_param_auto_from_type (h_body), + GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; char *op_str; @@ -82,15 +82,9 @@ TMH_PG_lookup_mfa_challenge ( " ,required_address" " ,tan_channel::TEXT" " FROM tan_challenges" - " WHERE merchant_serial IN" - " (SELECT merchant_serial" - " FROM merchant_instances" - " WHERE merchant_id = $1)" - " AND (challenge_id = $2)" - " AND (h_body = $3)" - " AND (expiration_date / 1000 / 1000" - " > EXTRACT(EPOCH FROM NOW()" - " AT TIME ZONE 'UTC')::bigint)"); + " WHERE (challenge_id = $1)" + " AND (h_body = $2)" + " AND (expiration_date > $3)"); /* Initialize to conservative values in case qs ends up <= 0 */ *tan_channel = TALER_MERCHANT_MFA_CHANNEL_NONE; *op = TALER_MERCHANT_MFA_CO_NONE; diff --git a/src/backenddb/pg_lookup_mfa_challenge.h b/src/backenddb/pg_lookup_mfa_challenge.h @@ -31,7 +31,6 @@ * matching the body exists in the database. * * @param cls closure - * @param instance_id instance for which the challenge is being looked up * @param challenge_id set to the ID of the challenge * @param h_body hash of the request body * @param[out] salt salt used to compute @a h_body @@ -49,7 +48,6 @@ enum GNUNET_DB_QueryStatus TMH_PG_lookup_mfa_challenge ( void *cls, - const char *instance_id, uint64_t challenge_id, const struct TALER_MERCHANT_MFA_BodyHash *h_body, struct TALER_MERCHANT_MFA_BodySalt *salt, diff --git a/src/backenddb/pg_solve_mfa_challenge.c b/src/backenddb/pg_solve_mfa_challenge.c @@ -30,19 +30,19 @@ enum GNUNET_DB_QueryStatus TMH_PG_solve_mfa_challenge ( void *cls, uint64_t challenge_id, - const char *instance_id, const struct TALER_MERCHANT_MFA_BodyHash *h_body, const char *solution, bool *solved, uint32_t *retry_counter) { struct PostgresClosure *pg = cls; + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); bool no_match; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_uint64 (&challenge_id), - GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_auto_from_type (h_body), GNUNET_PQ_query_param_string (solution), + GNUNET_PQ_query_param_absolute_time (&now), GNUNET_PQ_query_param_end }; struct GNUNET_PQ_ResultSpec rs[] = { diff --git a/src/backenddb/pg_solve_mfa_challenge.h b/src/backenddb/pg_solve_mfa_challenge.h @@ -33,7 +33,6 @@ * * @param cls closure * @param challenge_id challenge ID to be solved - * @param instance_id instance for which the challenge is being solved * @param h_body body of the operation the challenge authorizes * @param solution proposed solution to be checked against the actual code * @param[out] solved set to true if the challenge was solved by @@ -46,7 +45,6 @@ enum GNUNET_DB_QueryStatus TMH_PG_solve_mfa_challenge ( void *cls, uint64_t challenge_id, - const char *instance_id, const struct TALER_MERCHANT_MFA_BodyHash *h_body, const char *solution, bool *solved, diff --git a/src/backenddb/pg_solve_mfa_challenge.sql b/src/backenddb/pg_solve_mfa_challenge.sql @@ -17,24 +17,19 @@ DROP FUNCTION IF EXISTS merchant_do_solve_mfa_challenge; CREATE FUNCTION merchant_do_solve_mfa_challenge ( IN in_challenge_id INT8, - IN in_instance_id TEXT, IN in_h_body BYTEA, IN in_solution TEXT, + IN in_now INT8, OUT out_solved BOOLEAN, OUT out_retry_counter INT4 ) LANGUAGE plpgsql AS $$ DECLARE - my_instance_id INT8; my_confirmation_date INT8; DECLARE my_rec RECORD; BEGIN - SELECT merchant_serial - INTO my_instance_id - FROM merchant_instances - WHERE merchant_id=in_instance_id; -- Check if challenge exists and matches SELECT @@ -45,12 +40,8 @@ BEGIN my_rec FROM tan_challenges tc WHERE tc.challenge_id = in_challenge_id - AND tc.merchant_serial = my_instance_id AND tc.h_body = in_h_body - AND tc.expiration_date > - EXTRACT(EPOCH FROM NOW() - AT TIME ZONE 'UTC')::bigint - * 1000 * 1000; + AND tc.expiration_date > in_now; IF NOT FOUND THEN @@ -78,9 +69,7 @@ BEGIN IF out_solved THEN -- Newly solved, update DB! - my_confirmation_date = EXTRACT(EPOCH FROM NOW() - AT TIME ZONE 'UTC')::bigint - * 1000 * 1000; + my_confirmation_date = in_now; UPDATE tan_challenges SET confirmation_date = my_confirmation_date WHERE challenge_id = in_challenge_id; diff --git a/src/backenddb/pg_update_instance.c b/src/backenddb/pg_update_instance.c @@ -54,6 +54,8 @@ TMH_PG_update_instance (void *cls, (NULL == is->phone) ? GNUNET_PQ_query_param_null () : GNUNET_PQ_query_param_string (is->phone), + GNUNET_PQ_query_param_bool (is->phone_validated), + GNUNET_PQ_query_param_bool (is->email_validated), GNUNET_PQ_query_param_end }; @@ -71,6 +73,8 @@ TMH_PG_update_instance (void *cls, ",email=$9" ",logo=$10" ",phone_number=$11" + ",phone_validated=$12" + ",email_validated=$13" " WHERE merchant_id=$1"); return GNUNET_PQ_eval_prepared_non_select (pg->conn, "update_instance", diff --git a/src/backenddb/procedures.sql.in b/src/backenddb/procedures.sql.in @@ -31,23 +31,19 @@ SET search_path TO merchant; DROP PROCEDURE IF EXISTS merchant_do_gc; -CREATE PROCEDURE merchant_do_gc() +CREATE PROCEDURE merchant_do_gc(in_now INT8) LANGUAGE plpgsql AS $$ BEGIN DELETE FROM merchant_instances WHERE validation_needed - AND validation_expiration / 1000 / 1000 < - EXTRACT(EPOCH FROM now() - AT TIME ZONE 'UTC')::bigint; + AND validation_expiration < in_now; CALL merchant_statistic_amount_gc (); CALL merchant_statistic_bucket_gc (); CALL merchant_statistic_counter_gc (); DELETE FROM tan_challenges - WHERE expiration_date / 1000 / 1000 < - EXTRACT(EPOCH FROM now() - AT TIME ZONE 'UTC')::bigint; + WHERE expiration_date < in_now; END $$; COMMENT ON PROCEDURE merchant_do_gc IS 'calls all other garbage collection subroutines'; diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h @@ -4172,7 +4172,6 @@ struct TALER_MERCHANTDB_Plugin * exists in the database. * * @param cls closure - * @param instance_id instance for which the challenge is being looked up * @param challenge_id set to the ID of the challenge * @param h_body hash of the request body * @param[out] salt salt used to compute @a h_body @@ -4190,7 +4189,6 @@ struct TALER_MERCHANTDB_Plugin enum GNUNET_DB_QueryStatus (*lookup_mfa_challenge)( void *cls, - const char *instance_id, uint64_t challenge_id, const struct TALER_MERCHANT_MFA_BodyHash *h_body, struct TALER_MERCHANT_MFA_BodySalt *salt, @@ -4210,7 +4208,6 @@ struct TALER_MERCHANTDB_Plugin * * @param cls closure * @param challenge_id challenge ID to be solved - * @param instance_id instance for which the challenge is being solved * @param h_body body of the operation the challenge authorizes * @param solution proposed solution to be checked against the actual code * @param[out] solved set to true if the challenge was solved by @@ -4223,7 +4220,6 @@ struct TALER_MERCHANTDB_Plugin (*solve_mfa_challenge)( void *cls, uint64_t challenge_id, - const char *instance_id, const struct TALER_MERCHANT_MFA_BodyHash *h_body, const char *solution, bool *solved, @@ -4258,7 +4254,6 @@ struct TALER_MERCHANTDB_Plugin * Create new multi-factor authorization (MFA) challenge in the database. * * @param cls closure - * @param instance_id instance the operation effects * @param op operation that triggered the MFA request * @param h_body hash of the request body * @param salt salt used to compute @a h_body @@ -4275,7 +4270,6 @@ struct TALER_MERCHANTDB_Plugin enum GNUNET_DB_QueryStatus (*create_mfa_challenge)( void *cls, - const char *instance_id, enum TALER_MERCHANT_MFA_CriticalOperation op, const struct TALER_MERCHANT_MFA_BodyHash *h_body, const struct TALER_MERCHANT_MFA_BodySalt *salt, 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 ..." @@ -60,7 +60,7 @@ 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}}' \ + -d '{"auth":{"method":"token", "password":"pass1234"},"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") @@ -145,15 +145,34 @@ fi echo "OK" +echo -n "Retrying instance creation with other body " + +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":"change","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 +echo "OK" -echo -n "Retrying instance creation " + + +echo -n "Retrying instance creation with original body " 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}}' \ + -d '{"auth":{"method":"token", "password":"pass1234"},"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") @@ -163,14 +182,97 @@ then exit_fail "Expected 204 OK. Got: $STATUS" fi +echo "OK" + +echo -n "Unauthorized trigger MFA to add bank account " STATUS=$(curl -H "Content-Type: application/json" -X POST \ - -H 'Authorization: Bearer secret-token:super_secret' \ - http://localhost:9966/private/accounts \ + -H 'Authorization: Bearer secret-token:bad_password' \ + http://localhost:9966/instances/self/private/accounts \ + -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "401" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 401 Forbidden. Got: $STATUS" +fi + +echo " OK" + + +echo -n "Trigger MFA to add bank account with 2-FA authorization " +STATUS=$(curl \ + -H "Content-Type: application/json" \ + -X POST \ + -H 'Authorization: Bearer secret-token:pass1234' \ + http://localhost:9966/instances/self/private/accounts \ -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ -w "%{http_code}" -s \ -o "$LAST_RESPONSE") +if [ "$STATUS" != "202" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 202 Accepted. Got: $STATUS" +fi + +echo " OK" + + +C1=$(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 "Finally, add bank account " +STATUS=$(curl \ + -X POST \ + -H "Content-Type: application/json" \ + -H "Taler-Challenge-Ids: $C1" \ + -H 'Authorization: Bearer secret-token:pass1234' \ + http://localhost:9966/instances/self/private/accounts \ + -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") if [ "$STATUS" != "200" ] then @@ -180,21 +282,120 @@ fi echo " OK" -echo -n "Check the instance exists ..." -STATUS=$(curl -H "Content-Type: application/json" -X GET \ - http://localhost:9966/private/ \ +echo -n "Begin forgotten password reset " +STATUS=$(curl \ + -X POST \ + -H "Content-Type: application/json" \ + http://localhost:9966/instances/self/forgot-password \ + -d '{"method":"token","password":"amnesia"}' \ -w "%{http_code}" -s \ -o "$LAST_RESPONSE") -if [ "$STATUS" != "200" ] +if [ "$STATUS" != "202" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 202 Accepted. Got: $STATUS" +fi + +echo " OK" + +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 200 ok, instance exists. got: $STATUS" + 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 "Complete password reset " +STATUS=$(curl \ + -X POST \ + -H "Content-Type: application/json" \ + -H "Taler-Challenge-Ids: $C1,$C2" \ + http://localhost:9966/instances/self/forgot-password \ + -d '{"method":"token","password":"amnesia"}' \ + -w "%{http_code}" -s \ + -o "$LAST_RESPONSE") + +if [ "$STATUS" != "204" ] +then + jq < "$LAST_RESPONSE" + exit_fail "Expected 204 No content. Got: $STATUS" fi echo " OK" + + + + echo "TEST PASSED" exit 0