merchant

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

test_merchant_mfa.sh (19490B)


      1 #!/usr/bin/env bash
      2 # This file is part of TALER
      3 # Copyright (C) 2025 Taler Systems SA
      4 #
      5 # TALER is free software; you can redistribute it and/or modify
      6 # it under the terms of the GNU General Public License as
      7 # published by the Free Software Foundation; either version 3, or
      8 # (at your option) any later version.
      9 #
     10 # TALER is distributed in the hope that it will be useful, but
     11 # WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU General Public License for more details.
     14 #
     15 # You should have received a copy of the GNU General Public
     16 # License along with TALER; see the file COPYING.  If not, see
     17 # <http://www.gnu.org/licenses/>
     18 #
     19 set -eu
     20 
     21 . setup.sh
     22 
     23 
     24 # Launch system.
     25 setup \
     26     -c "test_merchant_mfa.conf" \
     27     -m
     28 LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX)
     29 
     30 solve_challenge()
     31 {
     32     CHALLENGE_ID=$1
     33     CHANNEL=$2
     34     EXPECTED_ADDRESS=$3
     35 
     36     rm -f "/tmp/test-merchant-$CHANNEL-tan.txt" \
     37         "/tmp/test-merchant-$CHANNEL-address.txt"
     38     STATUS=$(curl -H "Content-Type: application/json" -X POST \
     39         "http://localhost:9966/challenge/$CHALLENGE_ID" \
     40         -d '{}' \
     41         -w "%{http_code}" -s \
     42         -o "$LAST_RESPONSE")
     43     if [ "$STATUS" != "200" ]
     44     then
     45         jq < "$LAST_RESPONSE"
     46         exit_fail "Expected challenge transmission to return 200 OK. Got: $STATUS"
     47     fi
     48 
     49     TAN=$(head -n1 "/tmp/test-merchant-$CHANNEL-tan.txt" | awk '{print $1}')
     50     ADDRESS=$(cat "/tmp/test-merchant-$CHANNEL-address.txt")
     51     if [ "$ADDRESS" != "$EXPECTED_ADDRESS" ]
     52     then
     53         exit_fail "Expected $CHANNEL address '$EXPECTED_ADDRESS'. Got: $ADDRESS"
     54     fi
     55 
     56     STATUS=$(curl -H "Content-Type: application/json" -X POST \
     57         "http://localhost:9966/challenge/$CHALLENGE_ID/confirm" \
     58         -d '{"tan":"'"$TAN"'"}' \
     59         -w "%{http_code}" -s \
     60         -o "$LAST_RESPONSE")
     61     if [ "$STATUS" != "204" ]
     62     then
     63         jq < "$LAST_RESPONSE"
     64         exit_fail "Expected challenge confirmation to return 204 No Content. Got: $STATUS"
     65     fi
     66 }
     67 
     68 
     69 solve_response_challenges()
     70 {
     71     RESPONSE=$1
     72     while IFS=$'\t' read -r CHALLENGE_ID CHANNEL
     73     do
     74         case "$CHANNEL" in
     75         email)
     76             solve_challenge "$CHALLENGE_ID" "$CHANNEL" "self@example.com"
     77             ;;
     78         sms)
     79             solve_challenge "$CHALLENGE_ID" "$CHANNEL" "+4171234"
     80             ;;
     81         *)
     82             exit_fail "Unexpected TAN channel: $CHANNEL"
     83             ;;
     84         esac
     85     done < <(jq -r '.challenges[] | [.challenge_id, .tan_channel] | @tsv' "$RESPONSE")
     86 }
     87 
     88 echo -n "Configuring a merchant admin instance ..."
     89 
     90 STATUS=$(curl -H "Content-Type: application/json" -X POST \
     91     -H 'Authorization: Bearer secret-token:super_secret' \
     92     http://localhost:9966/management/instances \
     93     -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}}' \
     94     -w "%{http_code}" -s \
     95     -o "$LAST_RESPONSE")
     96 
     97 if [ "$STATUS" != "204" ]
     98 then
     99     jq < "$LAST_RESPONSE"
    100     exit_fail "Expected 204 ok, instance created. got: $STATUS"
    101 fi
    102 
    103 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    104     -H 'Authorization: Bearer secret-token:super_secret' \
    105     http://localhost:9966/private/accounts \
    106     -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/43?receiver-name=user43"}' \
    107     -w "%{http_code}" -s \
    108     -o "$LAST_RESPONSE")
    109 
    110 
    111 if [ "$STATUS" != "200" ]
    112 then
    113     jq < "$LAST_RESPONSE"
    114     exit_fail "Expected 200 OK. Got: $STATUS"
    115 fi
    116 
    117 echo " OK"
    118 echo -n "Self-provision instance ..."
    119 
    120 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    121     http://localhost:9966/instances \
    122     -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
    123     -w "%{http_code}" -s \
    124     -o "$LAST_RESPONSE")
    125 
    126 if [ "$STATUS" != "202" ]
    127 then
    128     jq < "$LAST_RESPONSE"
    129     exit_fail "Expected 202 Accepted. Got: $STATUS"
    130 fi
    131 echo " OK"
    132 
    133 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE")
    134 C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE")
    135 
    136 echo -n "Requesting challenge $C1 "
    137 
    138 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    139     "http://localhost:9966/challenge/$C1" \
    140     -d '{}' \
    141     -w "%{http_code}" -s \
    142     -o "$LAST_RESPONSE")
    143 
    144 if [ "$STATUS" != "200" ]
    145 then
    146     jq < "$LAST_RESPONSE"
    147     exit_fail "Expected 200 OK. Got: $STATUS"
    148 fi
    149 echo "OK"
    150 
    151 TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}')
    152 ADDR=$(cat /tmp/test-merchant-email-address.txt)
    153 
    154 if [ "$ADDR" != "self@example.com" ]
    155 then
    156     exit_fail "Expected address 'self@example.com'. Got: $ADDR"
    157 fi
    158 
    159 echo -n "Sending challenge $C1 solution "
    160 
    161 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    162     "http://localhost:9966/challenge/$C1/confirm" \
    163     -d '{"tan":"'"$TAN"'"}' \
    164     -w "%{http_code}" -s \
    165     -o "$LAST_RESPONSE")
    166 
    167 if [ "$STATUS" != "204" ]
    168 then
    169     jq < "$LAST_RESPONSE"
    170     exit_fail "Expected 204 OK. Got: $STATUS"
    171 fi
    172 echo "OK"
    173 
    174 echo -n "Requesting challenge $C2 "
    175 
    176 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    177     "http://localhost:9966/challenge/$C2" \
    178     -d '{}' \
    179     -w "%{http_code}" -s \
    180     -o "$LAST_RESPONSE")
    181 
    182 if [ "$STATUS" != "200" ]
    183 then
    184     jq < "$LAST_RESPONSE"
    185     exit_fail "Expected 200 OK. Got: $STATUS"
    186 fi
    187 echo "OK"
    188 
    189 TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1 | awk '{print $1}')
    190 
    191 echo -n "Sending challenge $C2 solution "
    192 
    193 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    194     "http://localhost:9966/challenge/$C2/confirm" \
    195     -d '{"tan":"'"$TAN"'"}' \
    196     -w "%{http_code}" -s \
    197     -o "$LAST_RESPONSE")
    198 
    199 if [ "$STATUS" != "204" ]
    200 then
    201     jq < "$LAST_RESPONSE"
    202     exit_fail "Expected 204 OK. Got: $STATUS"
    203 fi
    204 echo "OK"
    205 
    206 
    207 echo -n "Retrying instance creation with other body "
    208 
    209 STATUS=$(curl \
    210     -H "Content-Type: application/json" \
    211     -H "Taler-Challenge-Ids: $C1,$C2" \
    212     -X POST \
    213     http://localhost:9966/instances \
    214     -d '{"auth":{"method":"external"},"id":"self","name":"change","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
    215     -w "%{http_code}" -s \
    216     -o "$LAST_RESPONSE")
    217 
    218 if [ "$STATUS" != "202" ]
    219 then
    220     jq < "$LAST_RESPONSE"
    221     exit_fail "Expected 202 Accepted. Got: $STATUS"
    222 fi
    223 echo "OK"
    224 
    225 
    226 
    227 echo -n "Retrying instance creation with original body "
    228 
    229 STATUS=$(curl \
    230     -H "Content-Type: application/json" \
    231     -H "Taler-Challenge-Ids: $C1,$C2" \
    232     -X POST \
    233     http://localhost:9966/instances \
    234     -d '{"auth":{"method":"token", "password":"pass1234"},"id":"self","name":"default","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \
    235     -w "%{http_code}" -s \
    236     -o "$LAST_RESPONSE")
    237 
    238 if [ "$STATUS" != "204" ]
    239 then
    240     jq < "$LAST_RESPONSE"
    241     exit_fail "Expected 204 OK. Got: $STATUS"
    242 fi
    243 
    244 echo "OK"
    245 
    246 
    247 echo -n "Unauthorized trigger MFA to add bank account "
    248 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    249     -H 'Authorization: Bearer secret-token:bad_password' \
    250     http://localhost:9966/instances/self/private/accounts \
    251     -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \
    252     -w "%{http_code}" -s \
    253     -o "$LAST_RESPONSE")
    254 
    255 if [ "$STATUS" != "401" ]
    256 then
    257     jq < "$LAST_RESPONSE"
    258     exit_fail "Expected 401 Forbidden. Got: $STATUS"
    259 fi
    260 
    261 echo " OK"
    262 
    263 
    264 echo -n "Do NOT Trigger MFA to add first bank account "
    265 STATUS=$(curl \
    266     -H "Content-Type: application/json" \
    267     -X POST \
    268     -H 'Authorization: Bearer secret-token:pass1234' \
    269     http://localhost:9966/instances/self/private/accounts \
    270     -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \
    271     -w "%{http_code}" -s \
    272     -o "$LAST_RESPONSE")
    273 
    274 if [ "$STATUS" != "200" ]
    275 then
    276     jq < "$LAST_RESPONSE"
    277     exit_fail "Expected 200 OK. Got: $STATUS"
    278 fi
    279 
    280 echo " OK"
    281 
    282 
    283 echo -n "Trigger MFA to add 2nd bank account with 2-FA authorization "
    284 STATUS=$(curl \
    285     -H "Content-Type: application/json" \
    286     -X POST \
    287     -H 'Authorization: Bearer secret-token:pass1234' \
    288     http://localhost:9966/instances/self/private/accounts \
    289     -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \
    290     -w "%{http_code}" -s \
    291     -o "$LAST_RESPONSE")
    292 
    293 if [ "$STATUS" != "202" ]
    294 then
    295     jq < "$LAST_RESPONSE"
    296     exit_fail "Expected 202 Accepted. Got: $STATUS"
    297 fi
    298 
    299 echo " OK"
    300 
    301 
    302 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE")
    303 
    304 # Delete old TANs.
    305 rm /tmp/test-merchant-*-tan.txt
    306 echo -n "Requesting challenge $C1 "
    307 
    308 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    309     "http://localhost:9966/challenge/$C1" \
    310     -d '{}' \
    311     -w "%{http_code}" -s \
    312     -o "$LAST_RESPONSE")
    313 
    314 if [ "$STATUS" != "200" ]
    315 then
    316     jq < "$LAST_RESPONSE"
    317     exit_fail "Expected 200 OK. Got: $STATUS"
    318 fi
    319 echo "OK"
    320 
    321 TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}')
    322 ADDR=$(cat /tmp/test-merchant-email-address.txt)
    323 
    324 if [ "$ADDR" != "self@example.com" ]
    325 then
    326     exit_fail "Expected address 'self@example.com'. Got: $ADDR"
    327 fi
    328 
    329 echo -n "Sending challenge $C1 solution "
    330 
    331 STATUS=$(curl \
    332     -H "Content-Type: application/json" \
    333     -X POST \
    334     "http://localhost:9966/challenge/$C1/confirm" \
    335     -d '{"tan":"'"$TAN"'"}' \
    336     -w "%{http_code}" -s \
    337     -o "$LAST_RESPONSE")
    338 
    339 if [ "$STATUS" != "204" ]
    340 then
    341     jq < "$LAST_RESPONSE"
    342     exit_fail "Expected 204 OK. Got: $STATUS"
    343 fi
    344 echo "OK"
    345 
    346 echo -n "Finally, add 2nd bank account "
    347 STATUS=$(curl \
    348     -X POST \
    349     -H "Content-Type: application/json" \
    350     -H "Taler-Challenge-Ids: $C1" \
    351     -H 'Authorization: Bearer secret-token:pass1234' \
    352     http://localhost:9966/instances/self/private/accounts \
    353     -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \
    354     -w "%{http_code}" -s \
    355     -o "$LAST_RESPONSE")
    356 
    357 if [ "$STATUS" != "200" ]
    358 then
    359     jq < "$LAST_RESPONSE"
    360     exit_fail "Expected 200 OK. Got: $STATUS"
    361 fi
    362 
    363 echo " OK"
    364 
    365 
    366 echo -n "Begin forgotten password reset "
    367 STATUS=$(curl \
    368     -X POST \
    369     -H "Content-Type: application/json" \
    370     http://localhost:9966/instances/self/forgot-password \
    371     -d '{"method":"token","password":"amnesia"}' \
    372     -w "%{http_code}" -s \
    373     -o "$LAST_RESPONSE")
    374 
    375 if [ "$STATUS" != "202" ]
    376 then
    377     jq < "$LAST_RESPONSE"
    378     exit_fail "Expected 202 Accepted. Got: $STATUS"
    379 fi
    380 
    381 echo " OK"
    382 
    383 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE")
    384 C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE")
    385 
    386 echo -n "Requesting challenge $C1 "
    387 
    388 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    389     "http://localhost:9966/challenge/$C1" \
    390     -d '{}' \
    391     -w "%{http_code}" -s \
    392     -o "$LAST_RESPONSE")
    393 
    394 if [ "$STATUS" != "200" ]
    395 then
    396     jq < "$LAST_RESPONSE"
    397     exit_fail "Expected 200 OK. Got: $STATUS"
    398 fi
    399 echo "OK"
    400 
    401 TAN=$(cat /tmp/test-merchant-email-tan.txt | head -n1 | awk '{print $1}')
    402 ADDR=$(cat /tmp/test-merchant-email-address.txt)
    403 
    404 if [ "$ADDR" != "self@example.com" ]
    405 then
    406     exit_fail "Expected address 'self@example.com'. Got: $ADDR"
    407 fi
    408 
    409 echo -n "Sending challenge $C1 solution "
    410 
    411 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    412     "http://localhost:9966/challenge/$C1/confirm" \
    413     -d '{"tan":"'"$TAN"'"}' \
    414     -w "%{http_code}" -s \
    415     -o "$LAST_RESPONSE")
    416 
    417 if [ "$STATUS" != "204" ]
    418 then
    419     jq < "$LAST_RESPONSE"
    420     exit_fail "Expected 204 OK. Got: $STATUS"
    421 fi
    422 echo "OK"
    423 
    424 echo -n "Reject password reset with only one mandatory challenge solved "
    425 STATUS=$(curl \
    426     -X POST \
    427     -H "Content-Type: application/json" \
    428     -H "Taler-Challenge-Ids: $C1" \
    429     http://localhost:9966/instances/self/forgot-password \
    430     -d '{"method":"token","password":"amnesia"}' \
    431     -w "%{http_code}" -s \
    432     -o "$LAST_RESPONSE")
    433 
    434 if [ "$STATUS" != "202" ] ||
    435    [ "$(jq -r .combi_and < "$LAST_RESPONSE")" != "true" ]
    436 then
    437     jq < "$LAST_RESPONSE"
    438     exit_fail "Expected both mandatory channels to remain required. Got: $STATUS"
    439 fi
    440 echo "OK"
    441 
    442 
    443 echo -n "Requesting challenge $C2 "
    444 
    445 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    446     "http://localhost:9966/challenge/$C2" \
    447     -d '{}' \
    448     -w "%{http_code}" -s \
    449     -o "$LAST_RESPONSE")
    450 
    451 if [ "$STATUS" != "200" ]
    452 then
    453     jq < "$LAST_RESPONSE"
    454     exit_fail "Expected 200 OK. Got: $STATUS"
    455 fi
    456 echo "OK"
    457 
    458 TAN=$(cat /tmp/test-merchant-sms-tan.txt | head -n1 | awk '{print $1}')
    459 
    460 echo -n "Sending challenge $C2 solution "
    461 
    462 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    463     "http://localhost:9966/challenge/$C2/confirm" \
    464     -d '{"tan":"'"$TAN"'"}' \
    465     -w "%{http_code}" -s \
    466     -o "$LAST_RESPONSE")
    467 
    468 if [ "$STATUS" != "204" ]
    469 then
    470     jq < "$LAST_RESPONSE"
    471     exit_fail "Expected 204 OK. Got: $STATUS"
    472 fi
    473 echo "OK"
    474 
    475 echo -n "Complete password reset "
    476 STATUS=$(curl \
    477     -X POST \
    478     -H "Content-Type: application/json" \
    479     -H "Taler-Challenge-Ids: $C1,$C2" \
    480     http://localhost:9966/instances/self/forgot-password \
    481     -d '{"method":"token","password":"amnesia"}' \
    482     -w "%{http_code}" -s \
    483     -o "$LAST_RESPONSE")
    484 
    485 if [ "$STATUS" != "204" ]
    486 then
    487     jq < "$LAST_RESPONSE"
    488     exit_fail "Expected 204 No content. Got: $STATUS"
    489 fi
    490 
    491 echo " OK"
    492 
    493 
    494 
    495 
    496 
    497 echo -n "Begin password reset with login token issuance "
    498 TOKEN_RESET_BODY='{"method":"token","password":"recovered","token_duration":{"d_us":600000000}}'
    499 STATUS=$(curl \
    500     -X POST \
    501     -H "Content-Type: application/json" \
    502     http://localhost:9966/instances/self/forgot-password \
    503     -d "$TOKEN_RESET_BODY" \
    504     -w "%{http_code}" -s \
    505     -o "$LAST_RESPONSE")
    506 
    507 if [ "$STATUS" != "202" ]
    508 then
    509     jq < "$LAST_RESPONSE"
    510     exit_fail "Expected 202 Accepted. Got: $STATUS"
    511 fi
    512 cp "$LAST_RESPONSE" "$LAST_RESPONSE.challenges"
    513 RESET_CHALLENGE_IDS=$(jq -r '[.challenges[].challenge_id] | join(",")' \
    514     < "$LAST_RESPONSE")
    515 solve_response_challenges "$LAST_RESPONSE.challenges"
    516 echo "OK"
    517 
    518 
    519 echo -n "Reject solved challenges for a modified token duration "
    520 STATUS=$(curl \
    521     -X POST \
    522     -H "Content-Type: application/json" \
    523     -H "Taler-Challenge-Ids: $RESET_CHALLENGE_IDS" \
    524     http://localhost:9966/instances/self/forgot-password \
    525     -d '{"method":"token","password":"recovered","token_duration":{"d_us":300000000}}' \
    526     -w "%{http_code}" -s \
    527     -o "$LAST_RESPONSE")
    528 
    529 if [ "$STATUS" != "202" ]
    530 then
    531     jq < "$LAST_RESPONSE"
    532     exit_fail "Expected changed request body to require new challenges. Got: $STATUS"
    533 fi
    534 echo "OK"
    535 
    536 
    537 echo -n "Complete password reset and receive login token "
    538 STATUS=$(curl \
    539     -X POST \
    540     -H "Content-Type: application/json" \
    541     -H "Taler-Challenge-Ids: $RESET_CHALLENGE_IDS" \
    542     http://localhost:9966/instances/self/forgot-password \
    543     -d "$TOKEN_RESET_BODY" \
    544     -w "%{http_code}" -s \
    545     -o "$LAST_RESPONSE")
    546 
    547 if [ "$STATUS" != "200" ]
    548 then
    549     jq < "$LAST_RESPONSE"
    550     exit_fail "Expected 200 OK with login token. Got: $STATUS"
    551 fi
    552 RESET_TOKEN=$(jq -er \
    553     'select(.scope == "spa" and .refreshable == true) | .access_token' \
    554     < "$LAST_RESPONSE")
    555 if [ "$(jq -r .token < "$LAST_RESPONSE")" != "$RESET_TOKEN" ]
    556 then
    557     exit_fail "Expected token and access_token response fields to match"
    558 fi
    559 echo "OK"
    560 
    561 
    562 echo -n "Use login token returned by password reset "
    563 STATUS=$(curl \
    564     -X GET \
    565     -H "Authorization: Bearer $RESET_TOKEN" \
    566     http://localhost:9966/instances/self/private/products \
    567     -w "%{http_code}" -s \
    568     -o "$LAST_RESPONSE")
    569 
    570 if [ "$STATUS" != "200" ]
    571 then
    572     jq < "$LAST_RESPONSE"
    573     exit_fail "Expected reset login token to authorize SPA access. Got: $STATUS"
    574 fi
    575 echo "OK"
    576 
    577 
    578 echo -n "Self-provision second instance for instance-binding test "
    579 OTHER_INSTANCE_BODY='{"auth":{"method":"token","password":"recovered"},"id":"other","name":"other","phone_number":"+4171234","email":"self@example.com","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us":50000000},"default_pay_delay":{"d_us":60000000}}'
    580 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    581     http://localhost:9966/instances \
    582     -d "$OTHER_INSTANCE_BODY" \
    583     -w "%{http_code}" -s \
    584     -o "$LAST_RESPONSE")
    585 
    586 if [ "$STATUS" != "202" ]
    587 then
    588     jq < "$LAST_RESPONSE"
    589     exit_fail "Expected 202 Accepted. Got: $STATUS"
    590 fi
    591 cp "$LAST_RESPONSE" "$LAST_RESPONSE.challenges"
    592 OTHER_CREATE_CHALLENGE_IDS=$(jq -r \
    593     '[.challenges[].challenge_id] | join(",")' < "$LAST_RESPONSE")
    594 solve_response_challenges "$LAST_RESPONSE.challenges"
    595 
    596 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    597     -H "Taler-Challenge-Ids: $OTHER_CREATE_CHALLENGE_IDS" \
    598     http://localhost:9966/instances \
    599     -d "$OTHER_INSTANCE_BODY" \
    600     -w "%{http_code}" -s \
    601     -o "$LAST_RESPONSE")
    602 if [ "$STATUS" != "204" ]
    603 then
    604     jq < "$LAST_RESPONSE"
    605     exit_fail "Expected 204 No Content. Got: $STATUS"
    606 fi
    607 echo "OK"
    608 
    609 
    610 echo -n "Solve token-creation challenge for first instance "
    611 TOKEN_BODY='{"scope":"spa","duration":{"d_us":600000000},"refreshable":true}'
    612 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    613     -H 'Authorization: Bearer secret-token:recovered' \
    614     http://localhost:9966/instances/self/private/token \
    615     -d "$TOKEN_BODY" \
    616     -w "%{http_code}" -s \
    617     -o "$LAST_RESPONSE")
    618 if [ "$STATUS" != "202" ]
    619 then
    620     jq < "$LAST_RESPONSE"
    621     exit_fail "Expected 202 Accepted. Got: $STATUS"
    622 fi
    623 INSTANCE_CHALLENGE_ID=$(jq -r '.challenges[0].challenge_id' < "$LAST_RESPONSE")
    624 INSTANCE_CHALLENGE_CHANNEL=$(jq -r '.challenges[0].tan_channel' < "$LAST_RESPONSE")
    625 case "$INSTANCE_CHALLENGE_CHANNEL" in
    626 email)
    627     INSTANCE_CHALLENGE_ADDRESS=self@example.com
    628     ;;
    629 sms)
    630     INSTANCE_CHALLENGE_ADDRESS=+4171234
    631     ;;
    632 *)
    633     exit_fail "Unexpected TAN channel: $INSTANCE_CHALLENGE_CHANNEL"
    634     ;;
    635 esac
    636 solve_challenge "$INSTANCE_CHALLENGE_ID" \
    637     "$INSTANCE_CHALLENGE_CHANNEL" \
    638     "$INSTANCE_CHALLENGE_ADDRESS"
    639 echo "OK"
    640 
    641 
    642 echo -n "Reject solved challenge at a different instance "
    643 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    644     -H 'Authorization: Bearer secret-token:recovered' \
    645     -H "Taler-Challenge-Ids: $INSTANCE_CHALLENGE_ID" \
    646     http://localhost:9966/instances/other/private/token \
    647     -d "$TOKEN_BODY" \
    648     -w "%{http_code}" -s \
    649     -o "$LAST_RESPONSE")
    650 if [ "$STATUS" != "202" ]
    651 then
    652     jq < "$LAST_RESPONSE"
    653     exit_fail "Expected foreign challenge to be rejected with 202. Got: $STATUS"
    654 fi
    655 if jq -e --arg cid "$INSTANCE_CHALLENGE_ID" \
    656     '.challenges[] | select(.challenge_id == $cid)' "$LAST_RESPONSE" > /dev/null
    657 then
    658     exit_fail "Foreign challenge was returned as applicable to the other instance"
    659 fi
    660 echo "OK"
    661 
    662 
    663 echo -n "Accept solved challenge at its original instance "
    664 STATUS=$(curl -H "Content-Type: application/json" -X POST \
    665     -H 'Authorization: Bearer secret-token:recovered' \
    666     -H "Taler-Challenge-Ids: $INSTANCE_CHALLENGE_ID" \
    667     http://localhost:9966/instances/self/private/token \
    668     -d "$TOKEN_BODY" \
    669     -w "%{http_code}" -s \
    670     -o "$LAST_RESPONSE")
    671 if [ "$STATUS" != "200" ]
    672 then
    673     jq < "$LAST_RESPONSE"
    674     exit_fail "Expected same-instance challenge to authorize request. Got: $STATUS"
    675 fi
    676 echo "OK"
    677 
    678 
    679 echo "TEST PASSED"
    680 
    681 exit 0