test_merchant_mfa.sh (21508B)
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 echo -n "Reject password-change MFA without mandatory TAN channels " 25 INVALID_CONFIG=$(mktemp -p "${TMPDIR:-/tmp}" test_merchant_mfa.conf-XXXXXX) 26 INVALID_LOG=$(mktemp -p "${TMPDIR:-/tmp}" test_merchant_mfa.log-XXXXXX) 27 cp test_merchant_mfa.conf "$INVALID_CONFIG" 28 sed -i '/^MANDATORY_TAN_CHANNELS[[:space:]]*=/d' "$INVALID_CONFIG" 29 STATUS=0 30 timeout 10 taler-merchant-httpd -L INFO -c "$INVALID_CONFIG" \ 31 > "$INVALID_LOG" 2>&1 || STATUS=$? 32 if [ "$STATUS" = "0" ] || 33 [ "$STATUS" = "124" ] || 34 ! grep -q \ 35 'requires at least one MANDATORY_TAN_CHANNELS entry' \ 36 "$INVALID_LOG" 37 then 38 cat "$INVALID_LOG" 39 rm -f "$INVALID_CONFIG" "$INVALID_LOG" 40 exit_fail "Expected invalid password-change MFA configuration to fail" 41 fi 42 rm -f "$INVALID_CONFIG" "$INVALID_LOG" 43 echo "OK" 44 45 46 # The TAN helpers started by the merchant write into this directory. 47 TALER_MERCHANT_TEST_TAN_DIR=$(mktemp -d -p "${TMPDIR:-/tmp}" test_merchant_mfa-tan-XXXXXX) 48 export TALER_MERCHANT_TEST_TAN_DIR 49 50 # Launch system. 51 setup \ 52 -c "test_merchant_mfa.conf" \ 53 -m 54 LAST_RESPONSE=$(mktemp -p "${TMPDIR:-/tmp}" test_response.conf-XXXXXX) 55 56 echo -n "Configuring a merchant admin instance ..." 57 58 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 59 http://localhost:9966/management/instances \ 60 -d '{"auth":{"method":"token","password":"super_secret"},"id":"admin","name":"default","address":{},"jurisdiction":{},"use_stefan":true,"default_wire_transfer_delay":{"d_us" : 50000000},"default_pay_delay":{"d_us": 60000000}}' \ 61 -w "%{http_code}" -s \ 62 -o "$LAST_RESPONSE") 63 64 if [ "$STATUS" != "204" ] 65 then 66 jq < "$LAST_RESPONSE" 67 exit_fail "Expected 204 ok, instance created. got: $STATUS" 68 fi 69 70 ADMIN_TOKEN=$(merchant_login admin super_secret) 71 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 72 -H "Authorization: Bearer $ADMIN_TOKEN" \ 73 http://localhost:9966/private/accounts \ 74 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/43?receiver-name=user43"}' \ 75 -w "%{http_code}" -s \ 76 -o "$LAST_RESPONSE") 77 78 79 if [ "$STATUS" != "200" ] 80 then 81 jq < "$LAST_RESPONSE" 82 exit_fail "Expected 200 OK. Got: $STATUS" 83 fi 84 85 echo " OK" 86 echo -n "Self-provision instance ..." 87 88 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 89 http://localhost:9966/instances \ 90 -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}}' \ 91 -w "%{http_code}" -s \ 92 -o "$LAST_RESPONSE") 93 94 if [ "$STATUS" != "202" ] 95 then 96 jq < "$LAST_RESPONSE" 97 exit_fail "Expected 202 Accepted. Got: $STATUS" 98 fi 99 echo " OK" 100 101 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 102 C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE") 103 104 echo -n "Requesting challenge $C1 " 105 106 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 107 "http://localhost:9966/challenge/$C1" \ 108 -d '{}' \ 109 -w "%{http_code}" -s \ 110 -o "$LAST_RESPONSE") 111 112 if [ "$STATUS" != "200" ] 113 then 114 jq < "$LAST_RESPONSE" 115 exit_fail "Expected 200 OK. Got: $STATUS" 116 fi 117 echo "OK" 118 119 TAN=$(cat "$(tan_dir)/test-merchant-email-tan.txt" | head -n1 | awk '{print $1}') 120 ADDR=$(cat "$(tan_dir)/test-merchant-email-address.txt") 121 122 if [ "$ADDR" != "self@example.com" ] 123 then 124 exit_fail "Expected address 'self@example.com'. Got: $ADDR" 125 fi 126 127 echo -n "Sending challenge $C1 solution " 128 129 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 130 "http://localhost:9966/challenge/$C1/confirm" \ 131 -d '{"tan":"'"$TAN"'"}' \ 132 -w "%{http_code}" -s \ 133 -o "$LAST_RESPONSE") 134 135 if [ "$STATUS" != "204" ] 136 then 137 jq < "$LAST_RESPONSE" 138 exit_fail "Expected 204 OK. Got: $STATUS" 139 fi 140 echo "OK" 141 142 echo -n "Requesting challenge $C2 " 143 144 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 145 "http://localhost:9966/challenge/$C2" \ 146 -d '{}' \ 147 -w "%{http_code}" -s \ 148 -o "$LAST_RESPONSE") 149 150 if [ "$STATUS" != "200" ] 151 then 152 jq < "$LAST_RESPONSE" 153 exit_fail "Expected 200 OK. Got: $STATUS" 154 fi 155 echo "OK" 156 157 TAN=$(cat "$(tan_dir)/test-merchant-sms-tan.txt" | head -n1 | awk '{print $1}') 158 159 echo -n "Sending challenge $C2 solution " 160 161 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 162 "http://localhost:9966/challenge/$C2/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 175 echo -n "Retrying instance creation with other body " 176 177 STATUS=$(curl \ 178 -H "Content-Type: application/json" \ 179 -H "Taler-Challenge-Ids: $C1,$C2" \ 180 -X POST \ 181 http://localhost:9966/instances \ 182 -d '{"auth":{"method":"token","password":"pass1234"},"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}}' \ 183 -w "%{http_code}" -s \ 184 -o "$LAST_RESPONSE") 185 186 if [ "$STATUS" != "202" ] 187 then 188 jq < "$LAST_RESPONSE" 189 exit_fail "Expected 202 Accepted. Got: $STATUS" 190 fi 191 echo "OK" 192 193 194 195 echo -n "Retrying instance creation with original body " 196 197 STATUS=$(curl \ 198 -H "Content-Type: application/json" \ 199 -H "Taler-Challenge-Ids: $C1,$C2" \ 200 -X POST \ 201 http://localhost:9966/instances \ 202 -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}}' \ 203 -w "%{http_code}" -s \ 204 -o "$LAST_RESPONSE") 205 206 if [ "$STATUS" != "204" ] 207 then 208 jq < "$LAST_RESPONSE" 209 exit_fail "Expected 204 OK. Got: $STATUS" 210 fi 211 212 echo "OK" 213 214 215 echo -n "Unauthorized trigger MFA to add bank account " 216 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 217 -H 'Authorization: Bearer secret-token:bad_password' \ 218 http://localhost:9966/instances/self/private/accounts \ 219 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ 220 -w "%{http_code}" -s \ 221 -o "$LAST_RESPONSE") 222 223 if [ "$STATUS" != "401" ] 224 then 225 jq < "$LAST_RESPONSE" 226 exit_fail "Expected 401 Forbidden. Got: $STATUS" 227 fi 228 229 echo " OK" 230 231 232 echo -n "Log in to MFA-protected instance " 233 SELF_TOKEN=$(merchant_login_mfa self pass1234 self@example.com +4171234) 234 echo "OK" 235 236 237 echo -n "Do NOT Trigger MFA to add first bank account " 238 STATUS=$(curl \ 239 -H "Content-Type: application/json" \ 240 -X POST \ 241 -H "Authorization: Bearer $SELF_TOKEN" \ 242 http://localhost:9966/instances/self/private/accounts \ 243 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/44?receiver-name=user44"}' \ 244 -w "%{http_code}" -s \ 245 -o "$LAST_RESPONSE") 246 247 if [ "$STATUS" != "200" ] 248 then 249 jq < "$LAST_RESPONSE" 250 exit_fail "Expected 200 OK. Got: $STATUS" 251 fi 252 253 echo " OK" 254 255 256 echo -n "Trigger MFA to add 2nd bank account with 2-FA authorization " 257 STATUS=$(curl \ 258 -H "Content-Type: application/json" \ 259 -X POST \ 260 -H "Authorization: Bearer $SELF_TOKEN" \ 261 http://localhost:9966/instances/self/private/accounts \ 262 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \ 263 -w "%{http_code}" -s \ 264 -o "$LAST_RESPONSE") 265 266 if [ "$STATUS" != "202" ] 267 then 268 jq < "$LAST_RESPONSE" 269 exit_fail "Expected 202 Accepted. Got: $STATUS" 270 fi 271 272 echo " OK" 273 274 275 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 276 277 # Delete old TANs. 278 rm -f "$(tan_dir)"/test-merchant-*-tan.txt 279 echo -n "Requesting challenge $C1 " 280 281 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 282 "http://localhost:9966/challenge/$C1" \ 283 -d '{}' \ 284 -w "%{http_code}" -s \ 285 -o "$LAST_RESPONSE") 286 287 if [ "$STATUS" != "200" ] 288 then 289 jq < "$LAST_RESPONSE" 290 exit_fail "Expected 200 OK. Got: $STATUS" 291 fi 292 echo "OK" 293 294 TAN=$(cat "$(tan_dir)/test-merchant-email-tan.txt" | head -n1 | awk '{print $1}') 295 ADDR=$(cat "$(tan_dir)/test-merchant-email-address.txt") 296 297 if [ "$ADDR" != "self@example.com" ] 298 then 299 exit_fail "Expected address 'self@example.com'. Got: $ADDR" 300 fi 301 302 echo -n "Sending challenge $C1 solution " 303 304 STATUS=$(curl \ 305 -H "Content-Type: application/json" \ 306 -X POST \ 307 "http://localhost:9966/challenge/$C1/confirm" \ 308 -d '{"tan":"'"$TAN"'"}' \ 309 -w "%{http_code}" -s \ 310 -o "$LAST_RESPONSE") 311 312 if [ "$STATUS" != "204" ] 313 then 314 jq < "$LAST_RESPONSE" 315 exit_fail "Expected 204 OK. Got: $STATUS" 316 fi 317 echo "OK" 318 319 echo -n "Finally, add 2nd bank account " 320 STATUS=$(curl \ 321 -X POST \ 322 -H "Content-Type: application/json" \ 323 -H "Taler-Challenge-Ids: $C1" \ 324 -H "Authorization: Bearer $SELF_TOKEN" \ 325 http://localhost:9966/instances/self/private/accounts \ 326 -d '{"payto_uri":"payto://x-taler-bank/localhost:8082/45?receiver-name=user45"}' \ 327 -w "%{http_code}" -s \ 328 -o "$LAST_RESPONSE") 329 330 if [ "$STATUS" != "200" ] 331 then 332 jq < "$LAST_RESPONSE" 333 exit_fail "Expected 200 OK. Got: $STATUS" 334 fi 335 336 echo " OK" 337 338 339 echo -n "Begin forgotten password reset " 340 STATUS=$(curl \ 341 -X POST \ 342 -H "Content-Type: application/json" \ 343 http://localhost:9966/instances/self/forgot-password \ 344 -d '{"method":"token","password":"amnesia"}' \ 345 -w "%{http_code}" -s \ 346 -o "$LAST_RESPONSE") 347 348 if [ "$STATUS" != "202" ] 349 then 350 jq < "$LAST_RESPONSE" 351 exit_fail "Expected 202 Accepted. Got: $STATUS" 352 fi 353 354 echo " OK" 355 356 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 357 C2=$(jq -r .challenges[1].challenge_id < "$LAST_RESPONSE") 358 359 echo -n "Requesting challenge $C1 " 360 361 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 362 "http://localhost:9966/challenge/$C1" \ 363 -d '{}' \ 364 -w "%{http_code}" -s \ 365 -o "$LAST_RESPONSE") 366 367 if [ "$STATUS" != "200" ] 368 then 369 jq < "$LAST_RESPONSE" 370 exit_fail "Expected 200 OK. Got: $STATUS" 371 fi 372 echo "OK" 373 374 TAN=$(cat "$(tan_dir)/test-merchant-email-tan.txt" | head -n1 | awk '{print $1}') 375 ADDR=$(cat "$(tan_dir)/test-merchant-email-address.txt") 376 377 if [ "$ADDR" != "self@example.com" ] 378 then 379 exit_fail "Expected address 'self@example.com'. Got: $ADDR" 380 fi 381 382 echo -n "Sending challenge $C1 solution " 383 384 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 385 "http://localhost:9966/challenge/$C1/confirm" \ 386 -d '{"tan":"'"$TAN"'"}' \ 387 -w "%{http_code}" -s \ 388 -o "$LAST_RESPONSE") 389 390 if [ "$STATUS" != "204" ] 391 then 392 jq < "$LAST_RESPONSE" 393 exit_fail "Expected 204 OK. Got: $STATUS" 394 fi 395 echo "OK" 396 397 echo -n "Reject password reset with only one mandatory challenge solved " 398 STATUS=$(curl \ 399 -X POST \ 400 -H "Content-Type: application/json" \ 401 -H "Taler-Challenge-Ids: $C1,$C2" \ 402 http://localhost:9966/instances/self/forgot-password \ 403 -d '{"method":"token","password":"amnesia"}' \ 404 -w "%{http_code}" -s \ 405 -o "$LAST_RESPONSE") 406 407 if [ "$STATUS" != "202" ] || 408 [ "$(jq -r .combi_and < "$LAST_RESPONSE")" != "true" ] 409 then 410 jq < "$LAST_RESPONSE" 411 exit_fail "Expected both mandatory channels to remain required. Got: $STATUS" 412 fi 413 echo "OK" 414 415 416 echo -n "Requesting challenge $C2 " 417 418 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 419 "http://localhost:9966/challenge/$C2" \ 420 -d '{}' \ 421 -w "%{http_code}" -s \ 422 -o "$LAST_RESPONSE") 423 424 if [ "$STATUS" != "200" ] 425 then 426 jq < "$LAST_RESPONSE" 427 exit_fail "Expected 200 OK. Got: $STATUS" 428 fi 429 echo "OK" 430 431 TAN=$(cat "$(tan_dir)/test-merchant-sms-tan.txt" | head -n1 | awk '{print $1}') 432 433 echo -n "Sending challenge $C2 solution " 434 435 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 436 "http://localhost:9966/challenge/$C2/confirm" \ 437 -d '{"tan":"'"$TAN"'"}' \ 438 -w "%{http_code}" -s \ 439 -o "$LAST_RESPONSE") 440 441 if [ "$STATUS" != "204" ] 442 then 443 jq < "$LAST_RESPONSE" 444 exit_fail "Expected 204 OK. Got: $STATUS" 445 fi 446 echo "OK" 447 448 echo -n "Complete password reset " 449 STATUS=$(curl \ 450 -X POST \ 451 -H "Content-Type: application/json" \ 452 -H "Taler-Challenge-Ids: $C1,$C2" \ 453 http://localhost:9966/instances/self/forgot-password \ 454 -d '{"method":"token","password":"amnesia"}' \ 455 -w "%{http_code}" -s \ 456 -o "$LAST_RESPONSE") 457 458 if [ "$STATUS" != "204" ] 459 then 460 jq < "$LAST_RESPONSE" 461 exit_fail "Expected 204 No content. Got: $STATUS" 462 fi 463 464 echo " OK" 465 466 467 468 469 470 echo -n "Begin password reset with login token issuance " 471 TOKEN_RESET_BODY='{"method":"token","password":"recovered","token_duration":{"d_us":600000000}}' 472 STATUS=$(curl \ 473 -X POST \ 474 -H "Content-Type: application/json" \ 475 http://localhost:9966/instances/self/forgot-password \ 476 -d "$TOKEN_RESET_BODY" \ 477 -w "%{http_code}" -s \ 478 -o "$LAST_RESPONSE") 479 480 if [ "$STATUS" != "202" ] 481 then 482 jq < "$LAST_RESPONSE" 483 exit_fail "Expected 202 Accepted. Got: $STATUS" 484 fi 485 cp "$LAST_RESPONSE" "$LAST_RESPONSE.challenges" 486 RESET_CHALLENGE_IDS=$(jq -r '[.challenges[].challenge_id] | join(",")' \ 487 < "$LAST_RESPONSE") 488 solve_response_challenges "$LAST_RESPONSE.challenges" \ 489 self@example.com +4171234 > /dev/null 490 echo "OK" 491 492 493 echo -n "Reject solved challenges for a modified token duration " 494 STATUS=$(curl \ 495 -X POST \ 496 -H "Content-Type: application/json" \ 497 -H "Taler-Challenge-Ids: $RESET_CHALLENGE_IDS" \ 498 http://localhost:9966/instances/self/forgot-password \ 499 -d '{"method":"token","password":"recovered","token_duration":{"d_us":300000000}}' \ 500 -w "%{http_code}" -s \ 501 -o "$LAST_RESPONSE") 502 503 if [ "$STATUS" != "202" ] 504 then 505 jq < "$LAST_RESPONSE" 506 exit_fail "Expected changed request body to require new challenges. Got: $STATUS" 507 fi 508 echo "OK" 509 510 511 echo -n "Complete password reset and receive login token " 512 STATUS=$(curl \ 513 -X POST \ 514 -H "Content-Type: application/json" \ 515 -H "Taler-Challenge-Ids: $RESET_CHALLENGE_IDS" \ 516 http://localhost:9966/instances/self/forgot-password \ 517 -d "$TOKEN_RESET_BODY" \ 518 -w "%{http_code}" -s \ 519 -o "$LAST_RESPONSE") 520 521 if [ "$STATUS" != "200" ] 522 then 523 jq < "$LAST_RESPONSE" 524 exit_fail "Expected 200 OK with login token. Got: $STATUS" 525 fi 526 RESET_TOKEN=$(jq -er \ 527 'select(.scope == "spa" and .refreshable == true) | .access_token' \ 528 < "$LAST_RESPONSE") 529 if [ "$(jq -r .token < "$LAST_RESPONSE")" != "$RESET_TOKEN" ] 530 then 531 exit_fail "Expected token and access_token response fields to match" 532 fi 533 echo "OK" 534 535 536 echo -n "Use login token returned by password reset " 537 STATUS=$(curl \ 538 -X GET \ 539 -H "Authorization: Bearer $RESET_TOKEN" \ 540 http://localhost:9966/instances/self/private/products \ 541 -w "%{http_code}" -s \ 542 -o "$LAST_RESPONSE") 543 544 if [ "$STATUS" != "200" ] 545 then 546 jq < "$LAST_RESPONSE" 547 exit_fail "Expected reset login token to authorize SPA access. Got: $STATUS" 548 fi 549 echo "OK" 550 551 552 echo -n "Require one additional factor for a password change " 553 PASSWORD_CHANGE_BODY='{"method":"token","password":"recovered","old_password":"recovered"}' 554 STATUS=$(curl \ 555 -X POST \ 556 -H "Content-Type: application/json" \ 557 -H "Authorization: Bearer $RESET_TOKEN" \ 558 http://localhost:9966/instances/self/private/auth \ 559 -d "$PASSWORD_CHANGE_BODY" \ 560 -w "%{http_code}" -s \ 561 -o "$LAST_RESPONSE") 562 563 if [ "$STATUS" != "202" ] || 564 [ "$(jq -r .combi_and < "$LAST_RESPONSE")" != "false" ] || 565 [ "$(jq -r '.challenges | length' < "$LAST_RESPONSE")" != "2" ] 566 then 567 jq < "$LAST_RESPONSE" 568 exit_fail "Expected password change to offer either MFA channel. Got: $STATUS" 569 fi 570 571 C1=$(jq -r .challenges[0].challenge_id < "$LAST_RESPONSE") 572 CHANNEL=$(jq -r .challenges[0].tan_channel < "$LAST_RESPONSE") 573 case "$CHANNEL" in 574 email) 575 EXPECTED_ADDRESS="self@example.com" 576 ;; 577 sms) 578 EXPECTED_ADDRESS="+4171234" 579 ;; 580 *) 581 exit_fail "Unexpected TAN channel: $CHANNEL" 582 ;; 583 esac 584 solve_challenge "$C1" "$CHANNEL" "$EXPECTED_ADDRESS" 585 586 STATUS=$(curl \ 587 -X POST \ 588 -H "Content-Type: application/json" \ 589 -H "Taler-Challenge-Ids: $C1" \ 590 -H "Authorization: Bearer $RESET_TOKEN" \ 591 http://localhost:9966/instances/self/private/auth \ 592 -d "$PASSWORD_CHANGE_BODY" \ 593 -w "%{http_code}" -s \ 594 -o "$LAST_RESPONSE") 595 596 if [ "$STATUS" != "204" ] 597 then 598 jq < "$LAST_RESPONSE" 599 exit_fail "Expected one solved challenge to authorize password change. Got: $STATUS" 600 fi 601 echo "OK" 602 603 604 echo -n "Self-provision second instance for instance-binding test " 605 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}}' 606 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 607 http://localhost:9966/instances \ 608 -d "$OTHER_INSTANCE_BODY" \ 609 -w "%{http_code}" -s \ 610 -o "$LAST_RESPONSE") 611 612 if [ "$STATUS" != "202" ] 613 then 614 jq < "$LAST_RESPONSE" 615 exit_fail "Expected 202 Accepted. Got: $STATUS" 616 fi 617 cp "$LAST_RESPONSE" "$LAST_RESPONSE.challenges" 618 OTHER_CREATE_CHALLENGE_IDS=$(jq -r \ 619 '[.challenges[].challenge_id] | join(",")' < "$LAST_RESPONSE") 620 solve_response_challenges "$LAST_RESPONSE.challenges" \ 621 self@example.com +4171234 > /dev/null 622 623 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 624 -H "Taler-Challenge-Ids: $OTHER_CREATE_CHALLENGE_IDS" \ 625 http://localhost:9966/instances \ 626 -d "$OTHER_INSTANCE_BODY" \ 627 -w "%{http_code}" -s \ 628 -o "$LAST_RESPONSE") 629 if [ "$STATUS" != "204" ] 630 then 631 jq < "$LAST_RESPONSE" 632 exit_fail "Expected 204 No Content. Got: $STATUS" 633 fi 634 echo "OK" 635 636 637 echo -n "Solve token-creation challenge for first instance " 638 TOKEN_BODY='{"scope":"spa","duration":{"d_us":600000000},"refreshable":true}' 639 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 640 -H "Authorization: Basic $(printf %s self:recovered | base64 -w0)" \ 641 http://localhost:9966/instances/self/private/token \ 642 -d "$TOKEN_BODY" \ 643 -w "%{http_code}" -s \ 644 -o "$LAST_RESPONSE") 645 if [ "$STATUS" != "202" ] 646 then 647 jq < "$LAST_RESPONSE" 648 exit_fail "Expected 202 Accepted. Got: $STATUS" 649 fi 650 INSTANCE_CHALLENGE_ID=$(jq -r '.challenges[0].challenge_id' < "$LAST_RESPONSE") 651 INSTANCE_CHALLENGE_CHANNEL=$(jq -r '.challenges[0].tan_channel' < "$LAST_RESPONSE") 652 case "$INSTANCE_CHALLENGE_CHANNEL" in 653 email) 654 INSTANCE_CHALLENGE_ADDRESS=self@example.com 655 ;; 656 sms) 657 INSTANCE_CHALLENGE_ADDRESS=+4171234 658 ;; 659 *) 660 exit_fail "Unexpected TAN channel: $INSTANCE_CHALLENGE_CHANNEL" 661 ;; 662 esac 663 solve_challenge "$INSTANCE_CHALLENGE_ID" \ 664 "$INSTANCE_CHALLENGE_CHANNEL" \ 665 "$INSTANCE_CHALLENGE_ADDRESS" 666 echo "OK" 667 668 669 echo -n "Reject solved challenge at a different instance " 670 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 671 -H "Authorization: Basic $(printf %s other:recovered | base64 -w0)" \ 672 -H "Taler-Challenge-Ids: $INSTANCE_CHALLENGE_ID" \ 673 http://localhost:9966/instances/other/private/token \ 674 -d "$TOKEN_BODY" \ 675 -w "%{http_code}" -s \ 676 -o "$LAST_RESPONSE") 677 if [ "$STATUS" != "202" ] 678 then 679 jq < "$LAST_RESPONSE" 680 exit_fail "Expected foreign challenge to be rejected with 202. Got: $STATUS" 681 fi 682 if jq -e --arg cid "$INSTANCE_CHALLENGE_ID" \ 683 '.challenges[] | select(.challenge_id == $cid)' "$LAST_RESPONSE" > /dev/null 684 then 685 exit_fail "Foreign challenge was returned as applicable to the other instance" 686 fi 687 echo "OK" 688 689 690 echo -n "Accept solved challenge at its original instance " 691 STATUS=$(curl -H "Content-Type: application/json" -X POST \ 692 -H "Authorization: Basic $(printf %s self:recovered | base64 -w0)" \ 693 -H "Taler-Challenge-Ids: $INSTANCE_CHALLENGE_ID" \ 694 http://localhost:9966/instances/self/private/token \ 695 -d "$TOKEN_BODY" \ 696 -w "%{http_code}" -s \ 697 -o "$LAST_RESPONSE") 698 if [ "$STATUS" != "200" ] 699 then 700 jq < "$LAST_RESPONSE" 701 exit_fail "Expected same-instance challenge to authorize request. Got: $STATUS" 702 fi 703 echo "OK" 704 705 706 echo -n "Start a password reset and leave it unfinished " 707 STATUS=$(curl \ 708 -X POST \ 709 -H "Content-Type: application/json" \ 710 http://localhost:9966/instances/self/forgot-password \ 711 -d '{"method":"token","password":"unfinished"}' \ 712 -w "%{http_code}" -s \ 713 -o "$LAST_RESPONSE") 714 if [ "$STATUS" != "202" ] 715 then 716 jq < "$LAST_RESPONSE" 717 exit_fail "Expected 202 Accepted. Got: $STATUS" 718 fi 719 echo "OK" 720 721 722 echo -n "Refuse the next password reset while the last one is unfinished " 723 STATUS=$(curl \ 724 -X POST \ 725 -H "Content-Type: application/json" \ 726 http://localhost:9966/instances/self/forgot-password \ 727 -d '{"method":"token","password":"unfinished"}' \ 728 -w "%{http_code}" -s \ 729 -o "$LAST_RESPONSE") 730 if [ "$STATUS" != "429" ] || 731 [ "$(jq -r .code < "$LAST_RESPONSE")" != "2130" ] 732 then 733 jq < "$LAST_RESPONSE" 734 exit_fail "Expected 429 with code 2130. Got: $STATUS" 735 fi 736 echo "OK" 737 738 739 echo "TEST PASSED" 740 741 exit 0