routine.rs (44931B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2024, 2025, 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use std::{ 18 fmt::Debug, 19 future::Future, 20 time::{Duration, Instant}, 21 }; 22 23 use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _}; 24 use axum::{Router, http::StatusCode}; 25 use jiff::{SignedDuration, Timestamp}; 26 use serde::de::DeserializeOwned; 27 use taler_api::{ 28 crypto::{check_eddsa_signature, eddsa_sign}, 29 subject::fmt_in_subject, 30 }; 31 use taler_common::{ 32 api_common::{EddsaPublicKey, HashCode, ShortHashCode}, 33 api_params::PageParams, 34 api_revenue::RevenueIncomingHistory, 35 api_transfer::{RegistrationResponse, TransferSubject, TransferType}, 36 api_wire::{ 37 IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferList, TransferRequest, 38 TransferResponse, TransferState, TransferStatus, 39 }, 40 db::IncomingType, 41 error_code::ErrorCode, 42 types::{amount::amount, base32::Base32, payto::PaytoURI, url}, 43 }; 44 use tokio::time::sleep; 45 46 use crate::{ 47 json, 48 server::{TestResponse, TestServer as _}, 49 }; 50 51 pub trait Page: DeserializeOwned { 52 fn ids(&self) -> Vec<i64>; 53 } 54 55 impl Page for IncomingHistory { 56 fn ids(&self) -> Vec<i64> { 57 self.incoming_transactions 58 .iter() 59 .map(|it| match it { 60 IncomingBankTransaction::Reserve { row_id, .. } 61 | IncomingBankTransaction::Wad { row_id, .. } 62 | IncomingBankTransaction::Kyc { row_id, .. } => **row_id as i64, 63 }) 64 .collect() 65 } 66 } 67 68 impl Page for OutgoingHistory { 69 fn ids(&self) -> Vec<i64> { 70 self.outgoing_transactions 71 .iter() 72 .map(|it| *it.row_id as i64) 73 .collect() 74 } 75 } 76 77 impl Page for RevenueIncomingHistory { 78 fn ids(&self) -> Vec<i64> { 79 self.incoming_transactions 80 .iter() 81 .map(|it| *it.row_id as i64) 82 .collect() 83 } 84 } 85 86 impl Page for TransferList { 87 fn ids(&self) -> Vec<i64> { 88 self.transfers.iter().map(|it| *it.row_id as i64).collect() 89 } 90 } 91 92 pub async fn latest_id<T: Page>(router: &Router, url: &str) -> i64 { 93 let res = router.get(&format!("{url}?limit=-1")).await; 94 if res.status == StatusCode::NO_CONTENT { 95 0 96 } else { 97 res.assert_ids::<T>(1)[0] 98 } 99 } 100 101 pub async fn routine_pagination<T: Page>( 102 server: &Router, 103 url: &str, 104 mut register: impl AsyncFnMut(usize) -> (), 105 ) { 106 // Check supported 107 if !server.get(url).await.is_implemented() { 108 return; 109 } 110 111 // Check history is following specs 112 let assert_history = async |args: &str, size: usize| { 113 server 114 .get(&format!("{url}?{args}")) 115 .await 116 .assert_ids::<T>(size) 117 }; 118 // Get latest registered id 119 let latest_id = async || latest_id::<T>(server, url).await; 120 121 for i in 0..20 { 122 register(i).await; 123 } 124 125 let id = latest_id().await; 126 127 // default 128 assert_history("", 20).await; 129 130 // forward range 131 assert_history("limit=10", 10).await; 132 assert_history("limit=10&offset=4", 10).await; 133 134 // backward range 135 assert_history("limit=-10", 10).await; 136 assert_history(&format!("limit=-10&{}", id - 4), 10).await; 137 } 138 139 async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Future<Output = R>) { 140 let start = Instant::now(); 141 task.await; 142 let elapsed = start.elapsed().as_millis(); 143 if !range.contains(&elapsed) { 144 panic!("Expected to last {range:?} got {elapsed:?}") 145 } 146 } 147 148 pub async fn routine_history<T: Page>( 149 server: &Router, 150 url: &str, 151 nb_register: usize, 152 mut register: impl AsyncFnMut(usize) -> (), 153 nb_ignore: usize, 154 mut ignore: impl AsyncFnMut(usize) -> (), 155 ) { 156 // Check history is following specs 157 macro_rules! assert_history { 158 ($args:expr, $size:expr) => { 159 async { 160 server 161 .get(&format!("{url}?{}", $args)) 162 .await 163 .assert_ids::<T>($size) 164 } 165 }; 166 } 167 // Get latest registered id 168 let latest_id = async || assert_history!("limit=-1", 1).await[0]; 169 170 // Check error when no transactions 171 assert_history!("limit=7".to_owned(), 0).await; 172 173 let mut register_iter = (0..nb_register).peekable(); 174 let mut ignore_iter = (0..nb_ignore).peekable(); 175 while register_iter.peek().is_some() || ignore_iter.peek().is_some() { 176 if let Some(idx) = register_iter.next() { 177 register(idx).await 178 } 179 if let Some(idx) = ignore_iter.next() { 180 ignore(idx).await 181 } 182 } 183 let nb_total = nb_register + nb_ignore; 184 185 // Check ignored 186 assert_history!(format_args!("limit={nb_total}"), nb_register).await; 187 // Check skip ignored 188 assert_history!(format_args!("limit={nb_register}"), nb_register).await; 189 190 // Check no polling when we cannot have more transactions 191 assert_time( 192 0..200, 193 assert_history!( 194 format_args!("limit=-{}&timeout_ms=1000", nb_register + 1), 195 nb_register 196 ), 197 ) 198 .await; 199 // Check no polling when already find transactions even if less than delta 200 assert_time( 201 0..200, 202 assert_history!( 203 format_args!("limit={}&timeout_ms=1000", nb_register + 1), 204 nb_register 205 ), 206 ) 207 .await; 208 209 // Check polling 210 let id = latest_id().await; 211 tokio::join!( 212 // Check polling succeed 213 assert_time( 214 100..400, 215 assert_history!(format_args!("limit=2&offset={id}&timeout_ms=1000"), 1) 216 ), 217 assert_time( 218 200..500, 219 assert_history!( 220 format_args!( 221 "limit=1&offset={}&timeout_ms=200", 222 id as usize + nb_total * 3 223 ), 224 0 225 ) 226 ), 227 async { 228 sleep(Duration::from_millis(100)).await; 229 register(0).await 230 } 231 ); 232 233 // Test triggers 234 for i in 0..nb_register { 235 let id = latest_id().await; 236 tokio::join!( 237 // Check polling succeed 238 assert_time( 239 100..400, 240 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=1000"), 1) 241 ), 242 async { 243 sleep(Duration::from_millis(100)).await; 244 register(i).await 245 } 246 ); 247 } 248 249 // Test doesn't trigger 250 let id = latest_id().await; 251 tokio::join!( 252 // Check polling succeed 253 assert_time( 254 200..500, 255 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=200"), 0) 256 ), 257 async { 258 sleep(Duration::from_millis(100)).await; 259 for i in 0..nb_ignore { 260 ignore(i).await 261 } 262 } 263 ); 264 265 routine_pagination::<T>(server, url, register).await; 266 } 267 268 impl TestResponse { 269 #[track_caller] 270 fn assert_ids<T: Page>(&self, size: usize) -> Vec<i64> { 271 if size == 0 { 272 self.assert_no_content(); 273 return vec![]; 274 } 275 let body = self.assert_ok_json::<T>(); 276 let page = body.ids(); 277 let params = self.query::<PageParams>().check(1024).unwrap(); 278 279 // testing the size is like expected 280 assert_eq!(size, page.len(), "bad page length: {page:?}"); 281 if params.limit < 0 { 282 // testing that the first id is at most the 'offset' query param. 283 assert!( 284 params 285 .offset 286 .map(|offset| page[0] <= offset) 287 .unwrap_or(true), 288 "bad page offset: {params:?} {page:?}" 289 ); 290 // testing that the id decreases. 291 assert!( 292 page.as_slice().is_sorted_by(|a, b| a > b), 293 "bad page order: {page:?}" 294 ) 295 } else { 296 // testing that the first id is at least the 'offset' query param. 297 assert!( 298 params 299 .offset 300 .map(|offset| page[0] >= offset) 301 .unwrap_or(true), 302 "bad page offset: {params:?} {page:?}" 303 ); 304 // testing that the id increases. 305 assert!(page.as_slice().is_sorted(), "bad page order: {page:?}") 306 } 307 page 308 } 309 } 310 311 // Get currency from config 312 async fn get_currency(server: &Router) -> String { 313 let config = server 314 .get("/taler-wire-gateway/config") 315 .await 316 .assert_ok_json::<serde_json::Value>(); 317 let currency = config["currency"].as_str().unwrap(); 318 currency.to_owned() 319 } 320 321 /// Test standard behavior of the transfer endpoints 322 pub async fn transfer_routine( 323 server: &Router, 324 default_status: TransferState, 325 credit_account: &PaytoURI, 326 ) { 327 let currency = &get_currency(server).await; 328 let default_amount = amount(format!("{currency}:42")); 329 let request_uid = HashCode::rand(); 330 let wtid = ShortHashCode::rand(); 331 let transfer_req = json!({ 332 "request_uid": request_uid, 333 "amount": default_amount, 334 "exchange_base_url": "http://exchange.taler/", 335 "wtid": wtid, 336 "credit_account": credit_account, 337 }); 338 339 // Check empty db 340 { 341 server 342 .get("/taler-wire-gateway/transfers") 343 .await 344 .assert_no_content(); 345 server 346 .get(&format!( 347 "/taler-wire-gateway/transfers?status={}", 348 default_status.as_ref() 349 )) 350 .await 351 .assert_no_content(); 352 } 353 354 // TODO check subject formatting 355 356 let routine = async |req: &TransferRequest| { 357 // Check OK 358 let first = server 359 .post("/taler-wire-gateway/transfer") 360 .json(&req) 361 .await 362 .assert_ok_json::<TransferResponse>(); 363 // Check idempotent 364 let second = server 365 .post("/taler-wire-gateway/transfer") 366 .json(&req) 367 .await 368 .assert_ok_json::<TransferResponse>(); 369 assert_eq!(first.row_id, second.row_id); 370 assert_eq!(first.timestamp, second.timestamp); 371 372 // Check by id 373 let tx = server 374 .get(&format!("/taler-wire-gateway/transfers/{}", first.row_id)) 375 .await 376 .assert_ok_json::<TransferStatus>(); 377 assert_eq!(default_status, tx.status); 378 assert_eq!(default_amount, tx.amount); 379 assert_eq!("http://exchange.taler/", tx.origin_exchange_url); 380 assert_eq!(req.wtid, tx.wtid); 381 assert_eq!(first.timestamp, tx.timestamp); 382 assert_eq!(req.metadata, tx.metadata); 383 assert_eq!(credit_account, &tx.credit_account); 384 385 // Check page 386 let list = server 387 .get("/taler-wire-gateway/transfers?limit=-1") 388 .await 389 .assert_ok_json::<TransferList>(); 390 let tx = &list.transfers[0]; 391 assert_eq!(first.row_id, tx.row_id); 392 assert_eq!(default_status, tx.status); 393 assert_eq!(default_amount, tx.amount); 394 assert_eq!(first.timestamp, tx.timestamp); 395 assert_eq!(credit_account, &tx.credit_account); 396 }; 397 398 let req = TransferRequest { 399 request_uid, 400 amount: default_amount.clone(), 401 exchange_base_url: url("http://exchange.taler/"), 402 metadata: None, 403 wtid, 404 credit_account: credit_account.clone(), 405 }; 406 // Simple 407 routine(&req).await; 408 // With metadata 409 routine(&TransferRequest { 410 request_uid: HashCode::rand(), 411 wtid: ShortHashCode::rand(), 412 metadata: Some("test:medatata".into()), 413 ..req 414 }) 415 .await; 416 417 // Check create transfer errors 418 { 419 // Check request uid reuse 420 server 421 .post("/taler-wire-gateway/transfer") 422 .json(&json!(transfer_req + { 423 "wtid": ShortHashCode::rand() 424 })) 425 .await 426 .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED); 427 // Check wtid reuse 428 server 429 .post("/taler-wire-gateway/transfer") 430 .json(&json!(transfer_req + { 431 "request_uid": HashCode::rand(), 432 })) 433 .await 434 .assert_error(ErrorCode::BANK_TRANSFER_WTID_REUSED); 435 436 // Check currency mismatch 437 server 438 .post("/taler-wire-gateway/transfer") 439 .json(&json!(transfer_req + { 440 "amount": "BAD:42" 441 })) 442 .await 443 .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH); 444 445 // Base Base32 446 server 447 .post("/taler-wire-gateway/transfer") 448 .json(&json!(transfer_req + { 449 "wtid": "I love chocolate" 450 })) 451 .await 452 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 453 server 454 .post("/taler-wire-gateway/transfer") 455 .json(&json!(transfer_req + { 456 "wtid": Base32::<31>::rand() 457 })) 458 .await 459 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 460 server 461 .post("/taler-wire-gateway/transfer") 462 .json(&json!(transfer_req + { 463 "request_uid": "I love chocolate" 464 })) 465 .await 466 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 467 server 468 .post("/taler-wire-gateway/transfer") 469 .json(&json!(transfer_req + { 470 "request_uid": Base32::<65>::rand() 471 })) 472 .await 473 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 474 475 // Missing receiver-name 476 server 477 .post("/taler-wire-gateway/transfer") 478 .json(&json!(transfer_req + { 479 "credit_account": credit_account.as_ref().as_str().split_once('?').unwrap().0 480 })) 481 .await 482 .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED); 483 484 // TODO check bad payto 485 // TODO Bad base URL 486 /**for base_url in [ 487 "not-a-url", 488 "file://not.http.com/", 489 "no.transport.com/", 490 "https://not.a/base/url", 491 ] { 492 server 493 .post("/taler-wire-gateway/transfer") 494 .json(&json!(transfer_req + { 495 "exchange_base_url": base_url 496 })) 497 .await 498 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 499 }**/ 500 // Malformed metadata 501 for metadata in ["bad_id", "bad id", "bad@id.com", &"A".repeat(41)] { 502 server 503 .post("/taler-wire-gateway/transfer") 504 .json(&json!(transfer_req + { 505 "metadata": metadata 506 })) 507 .await 508 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 509 } 510 } 511 512 // Check transfer by id errors 513 { 514 // Check unknown transaction 515 server 516 .get("/taler-wire-gateway/transfers/42") 517 .await 518 .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND); 519 } 520 521 // Check transfer page 522 { 523 for _ in 0..4 { 524 server 525 .post("/taler-wire-gateway/transfer") 526 .json(&json!(transfer_req + { 527 "request_uid": HashCode::rand(), 528 "wtid": ShortHashCode::rand(), 529 })) 530 .await 531 .assert_ok_json::<TransferResponse>(); 532 } 533 { 534 let list = server 535 .get("/taler-wire-gateway/transfers") 536 .await 537 .assert_ok_json::<TransferList>(); 538 assert_eq!(list.transfers.len(), 6); 539 assert_eq!( 540 list, 541 server 542 .get(&format!( 543 "/taler-wire-gateway/transfers?status={}", 544 default_status.as_ref() 545 )) 546 .await 547 .assert_ok_json::<TransferList>() 548 ) 549 } 550 551 // Pagination test 552 routine_pagination::<TransferList>(server, "/taler-wire-gateway/transfers", async |i| { 553 server 554 .post("/taler-wire-gateway/transfer") 555 .json(&json!({ 556 "request_uid": HashCode::rand(), 557 "amount": amount(format!("{currency}:0.0{i}")), 558 "exchange_base_url": url("http://exchange.taler"), 559 "wtid": ShortHashCode::rand(), 560 "credit_account": credit_account, 561 })) 562 .await 563 .assert_ok_json::<TransferResponse>(); 564 }) 565 .await; 566 } 567 } 568 569 async fn add_incoming_routine( 570 server: &Router, 571 currency: &str, 572 kind: IncomingType, 573 debit_acount: &PaytoURI, 574 ) { 575 let (path, key) = match kind { 576 IncomingType::reserve => ("/taler-wire-gateway/admin/add-incoming", "reserve_pub"), 577 IncomingType::kyc => ("/taler-wire-gateway/admin/add-kycauth", "account_pub"), 578 IncomingType::map => ("/taler-wire-gateway/admin/add-mapped", "authorization_pub"), 579 }; 580 let key_pair = Ed25519KeyPair::generate().unwrap(); 581 let pub_key = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 582 // Valid 583 server 584 .post("/taler-prepared-transfer/registration") 585 .json(&json!({ 586 "type": "reserve", 587 "credit_amount": format!("{currency}:44"), 588 "alg": "EdDSA", 589 "account_pub": pub_key, 590 "authorization_pub": pub_key, 591 "authorization_sig": eddsa_sign(&key_pair, pub_key.as_ref()), 592 "recurrent": false 593 })) 594 .await 595 .assert_ok_json::<RegistrationResponse>(); 596 let valid_req = json!({ 597 "amount": format!("{currency}:44"), 598 key: pub_key, 599 "debit_account": debit_acount, 600 }); 601 602 // Check OK 603 server.post(path).json(&valid_req).await.assert_ok(); 604 605 match kind { 606 IncomingType::reserve => { 607 // Trigger conflict due to reused reserve_pub 608 server 609 .post(path) 610 .json(&json!(valid_req + { 611 "amount": format!("{currency}:44.1"), 612 })) 613 .await 614 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT) 615 } 616 IncomingType::kyc => { 617 // Non conflict on reuse 618 server.post(path).json(&valid_req).await.assert_ok(); 619 } 620 IncomingType::map => { 621 // Trigger conflict due to reused authorization_pub 622 server 623 .post(path) 624 .json(&valid_req) 625 .await 626 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED); 627 // Trigger conflict due to unknown authorization_pub 628 server 629 .post(path) 630 .json(&json!(valid_req + { 631 key: EddsaPublicKey::rand() 632 })) 633 .await 634 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN); 635 } 636 } 637 638 // Currency mismatch 639 server 640 .post(path) 641 .json(&json!(valid_req + { 642 "amount": "BAD:33" 643 })) 644 .await 645 .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH); 646 647 // Bad BASE32 reserve_pub 648 server 649 .post(path) 650 .json(&json!(valid_req + { 651 key: "I love chocolate" 652 })) 653 .await 654 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 655 656 server 657 .post(path) 658 .json(&json!(valid_req + { 659 key: Base32::<31>::rand() 660 })) 661 .await 662 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 663 664 // Bad payto kind 665 server 666 .post(path) 667 .json(&json!(valid_req + { 668 "debit_account": "http://email@test.com" 669 })) 670 .await 671 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 672 } 673 674 /// Test standard behavior of the revenue endpoints 675 pub async fn revenue_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool) { 676 let currency = &get_currency(server).await; 677 678 routine_history::<RevenueIncomingHistory>( 679 server, 680 "/taler-revenue/history", 681 2, 682 async |i| { 683 if i % 2 == 0 || !kyc { 684 server 685 .post("/taler-wire-gateway/admin/add-incoming") 686 .json(&json!({ 687 "amount": format!("{currency}:0.0{i}"), 688 "reserve_pub": EddsaPublicKey::rand(), 689 "debit_account": debit_acount, 690 })) 691 .await 692 .assert_ok_json::<TransferResponse>(); 693 } else { 694 server 695 .post("/taler-wire-gateway/admin/add-kycauth") 696 .json(&json!({ 697 "amount": format!("{currency}:0.0{i}"), 698 "account_pub": EddsaPublicKey::rand(), 699 "debit_account": debit_acount, 700 })) 701 .await 702 .assert_ok_json::<TransferResponse>(); 703 } 704 }, 705 0, 706 async |_| {}, 707 ) 708 .await; 709 } 710 711 /// Test standard behavior of the incoming history endpoint 712 pub async fn in_history_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool) { 713 let currency = &get_currency(server).await; 714 // History 715 // TODO check non taler some are ignored 716 let mut key_pair = Ed25519KeyPair::generate().unwrap(); 717 let len = if kyc { 6 } else { 3 }; 718 routine_history::<IncomingHistory>( 719 server, 720 "/taler-wire-gateway/history/incoming", 721 len, 722 async |i| match i % len { 723 0 => { 724 server 725 .post("/taler-wire-gateway/admin/add-incoming") 726 .json(&json!({ 727 "amount": format!("{currency}:0.0{i}"), 728 "reserve_pub": EddsaPublicKey::rand(), 729 "debit_account": debit_acount, 730 })) 731 .await 732 .assert_ok_json::<TransferResponse>(); 733 } 734 1 => { 735 key_pair = Ed25519KeyPair::generate().unwrap(); 736 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 737 let reserve_pub = EddsaPublicKey::rand(); 738 let amount = format!("{currency}:0.0{i}"); 739 server 740 .post("/taler-prepared-transfer/registration") 741 .json(&json!({ 742 "credit_amount": amount, 743 "type": "reserve", 744 "alg": "EdDSA", 745 "account_pub": reserve_pub, 746 "authorization_pub": auth_pub, 747 "authorization_sig": eddsa_sign(&key_pair, reserve_pub.as_ref()), 748 "recurrent": true 749 })) 750 .await 751 .assert_ok_json::<RegistrationResponse>(); 752 server 753 .post("/taler-wire-gateway/admin/add-mapped") 754 .json(&json!({ 755 "amount": amount, 756 "authorization_pub": auth_pub, 757 "debit_account": debit_acount, 758 })) 759 .await 760 .assert_ok_json::<TransferResponse>(); 761 server 762 .post("/taler-wire-gateway/admin/add-mapped") 763 .json(&json!({ 764 "amount": amount, 765 "authorization_pub": auth_pub, 766 "debit_account": debit_acount, 767 })) 768 .await 769 .assert_ok_json::<TransferResponse>(); 770 } 771 2 => { 772 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 773 let reserve_pub = EddsaPublicKey::rand(); 774 server 775 .post("/taler-prepared-transfer/registration") 776 .json(&json!({ 777 "credit_amount": format!("{currency}:0.0{i}"), 778 "type": "reserve", 779 "alg": "EdDSA", 780 "account_pub": reserve_pub, 781 "authorization_pub": auth_pub, 782 "authorization_sig": eddsa_sign(&key_pair, reserve_pub.as_ref()), 783 "recurrent": true 784 })) 785 .await 786 .assert_ok_json::<RegistrationResponse>(); 787 } 788 3 => { 789 server 790 .post("/taler-wire-gateway/admin/add-kycauth") 791 .json(&json!({ 792 "amount": format!("{currency}:0.0{i}"), 793 "account_pub": EddsaPublicKey::rand(), 794 "debit_account": debit_acount, 795 })) 796 .await 797 .assert_ok_json::<TransferResponse>(); 798 } 799 4 => { 800 key_pair = Ed25519KeyPair::generate().unwrap(); 801 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 802 let account_pub = EddsaPublicKey::rand(); 803 let amount = format!("{currency}:0.0{i}"); 804 server 805 .post("/taler-prepared-transfer/registration") 806 .json(&json!({ 807 "credit_amount": amount, 808 "type": "kyc", 809 "alg": "EdDSA", 810 "account_pub": account_pub, 811 "authorization_pub": auth_pub, 812 "authorization_sig": eddsa_sign(&key_pair, account_pub.as_ref()), 813 "recurrent": true 814 })) 815 .await 816 .assert_ok_json::<RegistrationResponse>(); 817 server 818 .post("/taler-wire-gateway/admin/add-mapped") 819 .json(&json!({ 820 "amount": amount, 821 "authorization_pub": auth_pub, 822 "debit_account": debit_acount, 823 })) 824 .await 825 .assert_ok_json::<TransferResponse>(); 826 server 827 .post("/taler-wire-gateway/admin/add-mapped") 828 .json(&json!({ 829 "amount": amount, 830 "authorization_pub": auth_pub, 831 "debit_account": debit_acount, 832 })) 833 .await 834 .assert_ok_json::<TransferResponse>(); 835 } 836 5 => { 837 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 838 let account_pub = EddsaPublicKey::rand(); 839 server 840 .post("/taler-prepared-transfer/registration") 841 .json(&json!({ 842 "credit_amount": format!("{currency}:0.0{i}"), 843 "type": "kyc", 844 "alg": "EdDSA", 845 "account_pub": account_pub, 846 "authorization_pub": auth_pub, 847 "authorization_sig": eddsa_sign(&key_pair, account_pub.as_ref()), 848 "recurrent": true 849 })) 850 .await 851 .assert_ok_json::<RegistrationResponse>(); 852 } 853 nb => unreachable!("unexpected state {nb}"), 854 }, 855 0, 856 async |_| {}, 857 ) 858 .await; 859 } 860 861 /// Test standard behavior of the admin add incoming endpoints 862 pub async fn admin_add_incoming_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool) { 863 let currency = &get_currency(server).await; 864 add_incoming_routine(server, currency, IncomingType::reserve, debit_acount).await; 865 add_incoming_routine(server, currency, IncomingType::map, debit_acount).await; 866 if kyc { 867 add_incoming_routine(server, currency, IncomingType::kyc, debit_acount).await; 868 } 869 } 870 871 #[derive(Debug, PartialEq, Eq)] 872 pub enum Status { 873 Simple, 874 Pending, 875 Bounced, 876 Incomplete, 877 Reserve(EddsaPublicKey), 878 Kyc(EddsaPublicKey), 879 } 880 881 /// Test standard registration behavior of the registration endpoints 882 pub async fn registration_routine<F1: Future<Output = Vec<Status>>>( 883 server: &Router, 884 account: &PaytoURI, 885 mut in_status: impl FnMut() -> F1, 886 ) { 887 pub use Status::*; 888 let mut check_in = async |state: &[Status]| { 889 let current = in_status().await; 890 assert_eq!(state, current); 891 }; 892 893 let currency = &get_currency(server).await; 894 let amount = amount(format!("{currency}:42")); 895 let key_pair1 = Ed25519KeyPair::generate().unwrap(); 896 let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap(); 897 let req = json!({ 898 "credit_amount": amount, 899 "type": "reserve", 900 "alg": "EdDSA", 901 "account_pub": auth_pub1, 902 "authorization_pub": auth_pub1, 903 "authorization_sig": eddsa_sign(&key_pair1, auth_pub1.as_ref()), 904 "recurrent": false 905 }); 906 907 let register = async |auth_pub: &EddsaPublicKey| { 908 server 909 .post("/taler-wire-gateway/admin/add-mapped") 910 .json(&json!({ 911 "amount": format!("{currency}:42"), 912 "authorization_pub": auth_pub, 913 "debit_account": account, 914 })) 915 .await 916 }; 917 918 /* ----- Registration ----- */ 919 let routine = async |ty: TransferType, 920 account_pub: &EddsaPublicKey, 921 recurrent: bool, 922 fmt: IncomingType| { 923 let req = json!(req + { 924 "type": ty, 925 "account_pub": account_pub, 926 "authorization_pub": auth_pub1, 927 "authorization_sig": eddsa_sign(&key_pair1, account_pub.as_ref()), 928 "recurrent": recurrent 929 }); 930 // Valid 931 let res = server 932 .post("/taler-prepared-transfer/registration") 933 .json(&req) 934 .await 935 .assert_ok_json::<RegistrationResponse>(); 936 937 // Idempotent 938 assert_eq!( 939 res, 940 server 941 .post("/taler-prepared-transfer/registration") 942 .json(&req) 943 .await 944 .assert_ok_json::<RegistrationResponse>() 945 ); 946 947 assert!(!res.subjects.is_empty()); 948 949 for sub in res.subjects { 950 if let TransferSubject::Simple { subject, .. } = sub { 951 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1)); 952 }; 953 } 954 }; 955 for ty in [TransferType::reserve, TransferType::kyc] { 956 routine(ty, &auth_pub1, false, ty.into()).await; 957 routine(ty, &auth_pub1, true, IncomingType::map).await; 958 } 959 960 let acc_pub1 = EddsaPublicKey::rand(); 961 for ty in [TransferType::reserve, TransferType::kyc] { 962 routine(ty, &acc_pub1, false, IncomingType::map).await; 963 routine(ty, &acc_pub1, true, IncomingType::map).await; 964 } 965 966 // Bad signature 967 server 968 .post("/taler-prepared-transfer/registration") 969 .json(&json!(req + { 970 "authorization_sig": eddsa_sign(&key_pair1, "lol".as_bytes()), 971 })) 972 .await 973 .assert_error(ErrorCode::BANK_BAD_SIGNATURE); 974 975 // Reserve pub reuse 976 server 977 .post("/taler-prepared-transfer/registration") 978 .json(&json!(req + { 979 "account_pub": acc_pub1, 980 "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()), 981 })) 982 .await 983 .assert_ok_json::<RegistrationResponse>(); 984 { 985 let key_pair = Ed25519KeyPair::generate().unwrap(); 986 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 987 server 988 .post("/taler-prepared-transfer/registration") 989 .json(&json!(req + { 990 "account_pub": acc_pub1, 991 "authorization_pub": auth_pub, 992 "authorization_sig": eddsa_sign(&key_pair, acc_pub1.as_ref()), 993 })) 994 .await 995 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT); 996 } 997 998 // Non recurrent accept one then bounce 999 server 1000 .post("/taler-prepared-transfer/registration") 1001 .json(&json!(req + { 1002 "account_pub": acc_pub1, 1003 "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()), 1004 })) 1005 .await 1006 .assert_ok_json::<RegistrationResponse>(); 1007 register(&auth_pub1) 1008 .await 1009 .assert_ok_json::<TransferResponse>(); 1010 check_in(&[Reserve(acc_pub1.clone())]).await; 1011 register(&auth_pub1) 1012 .await 1013 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED); 1014 1015 // Again without using mapping 1016 let acc_pub2 = EddsaPublicKey::rand(); 1017 server 1018 .post("/taler-prepared-transfer/registration") 1019 .json(&json!(req + { 1020 "account_pub": acc_pub2, 1021 "authorization_sig": eddsa_sign(&key_pair1, acc_pub2.as_ref()), 1022 })) 1023 .await 1024 .assert_ok_json::<RegistrationResponse>(); 1025 server 1026 .post("/taler-wire-gateway/admin/add-incoming") 1027 .json(&json!({ 1028 "amount": amount, 1029 "reserve_pub": acc_pub2, 1030 "debit_account": account, 1031 })) 1032 .await 1033 .assert_ok(); 1034 register(&auth_pub1) 1035 .await 1036 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED); 1037 check_in(&[Reserve(acc_pub1.clone()), Reserve(acc_pub2.clone())]).await; 1038 1039 // Recurrent accept one and delay others 1040 let acc_pub3 = EddsaPublicKey::rand(); 1041 server 1042 .post("/taler-prepared-transfer/registration") 1043 .json(&json!(req + { 1044 "account_pub": acc_pub3, 1045 "authorization_sig": eddsa_sign(&key_pair1, acc_pub3.as_ref()), 1046 "recurrent": true 1047 })) 1048 .await 1049 .assert_ok_json::<RegistrationResponse>(); 1050 for _ in 0..5 { 1051 register(&auth_pub1) 1052 .await 1053 .assert_ok_json::<TransferResponse>(); 1054 } 1055 check_in(&[ 1056 Reserve(acc_pub1.clone()), 1057 Reserve(acc_pub2.clone()), 1058 Reserve(acc_pub3.clone()), 1059 Pending, 1060 Pending, 1061 Pending, 1062 Pending, 1063 ]) 1064 .await; 1065 1066 // Complete pending on recurrent update 1067 let acc_pub4 = EddsaPublicKey::rand(); 1068 server 1069 .post("/taler-prepared-transfer/registration") 1070 .json(&json!(req + { 1071 "type": "kyc", 1072 "account_pub": acc_pub4, 1073 "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()), 1074 "recurrent": true 1075 })) 1076 .await 1077 .assert_ok_json::<RegistrationResponse>(); 1078 server 1079 .post("/taler-prepared-transfer/registration") 1080 .json(&json!(req + { 1081 "account_pub": acc_pub4, 1082 "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()), 1083 "recurrent": true 1084 })) 1085 .await 1086 .assert_ok_json::<RegistrationResponse>(); 1087 check_in(&[ 1088 Reserve(acc_pub1.clone()), 1089 Reserve(acc_pub2.clone()), 1090 Reserve(acc_pub3.clone()), 1091 Kyc(acc_pub4.clone()), 1092 Reserve(acc_pub4.clone()), 1093 Pending, 1094 Pending, 1095 ]) 1096 .await; 1097 1098 // Kyc key reuse keep pending ones 1099 server 1100 .post("/taler-wire-gateway/admin/add-kycauth") 1101 .json(&json!({ 1102 "amount": amount, 1103 "account_pub": acc_pub4, 1104 "debit_account": account, 1105 })) 1106 .await 1107 .assert_ok_json::<TransferResponse>(); 1108 check_in(&[ 1109 Reserve(acc_pub1.clone()), 1110 Reserve(acc_pub2.clone()), 1111 Reserve(acc_pub3.clone()), 1112 Kyc(acc_pub4.clone()), 1113 Reserve(acc_pub4.clone()), 1114 Pending, 1115 Pending, 1116 Kyc(acc_pub4.clone()), 1117 ]) 1118 .await; 1119 1120 // Switching to non recurrent cancel pending 1121 let auth_pair = Ed25519KeyPair::generate().unwrap(); 1122 let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap(); 1123 server 1124 .post("/taler-prepared-transfer/registration") 1125 .json(&json!(req + { 1126 "account_pub": auth_pub2, 1127 "authorization_pub": auth_pub2, 1128 "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()), 1129 "recurrent": true 1130 })) 1131 .await 1132 .assert_ok_json::<RegistrationResponse>(); 1133 for _ in 0..3 { 1134 register(&auth_pub2) 1135 .await 1136 .assert_ok_json::<TransferResponse>(); 1137 } 1138 check_in(&[ 1139 Reserve(acc_pub1.clone()), 1140 Reserve(acc_pub2.clone()), 1141 Reserve(acc_pub3.clone()), 1142 Kyc(acc_pub4.clone()), 1143 Reserve(acc_pub4.clone()), 1144 Pending, 1145 Pending, 1146 Kyc(acc_pub4.clone()), 1147 Reserve(auth_pub2.clone()), 1148 Pending, 1149 Pending, 1150 ]) 1151 .await; 1152 server 1153 .post("/taler-prepared-transfer/registration") 1154 .json(&json!(req + { 1155 "type": "kyc", 1156 "account_pub": auth_pub2, 1157 "authorization_pub": auth_pub2, 1158 "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()), 1159 "recurrent": false 1160 })) 1161 .await 1162 .assert_ok_json::<RegistrationResponse>(); 1163 check_in(&[ 1164 Reserve(acc_pub1.clone()), 1165 Reserve(acc_pub2.clone()), 1166 Reserve(acc_pub3.clone()), 1167 Kyc(acc_pub4.clone()), 1168 Reserve(acc_pub4.clone()), 1169 Pending, 1170 Pending, 1171 Kyc(acc_pub4.clone()), 1172 Reserve(auth_pub2.clone()), 1173 Bounced, 1174 Bounced, 1175 ]) 1176 .await; 1177 1178 // Recurrent reserve simple subject 1179 let acc_pub5 = EddsaPublicKey::rand(); 1180 server 1181 .post("/taler-prepared-transfer/registration") 1182 .json(&json!(req + { 1183 "type": "reserve", 1184 "account_pub": acc_pub5, 1185 "authorization_pub": auth_pub2, 1186 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1187 "recurrent": true 1188 })) 1189 .await 1190 .assert_ok_json::<RegistrationResponse>(); 1191 server 1192 .post("/taler-wire-gateway/admin/add-incoming") 1193 .json(&json!({ 1194 "amount": amount, 1195 "reserve_pub": acc_pub5, 1196 "debit_account": account, 1197 })) 1198 .await 1199 .assert_ok(); 1200 register(&auth_pub2) 1201 .await 1202 .assert_ok_json::<TransferResponse>(); 1203 check_in(&[ 1204 Reserve(acc_pub1.clone()), 1205 Reserve(acc_pub2.clone()), 1206 Reserve(acc_pub3.clone()), 1207 Kyc(acc_pub4.clone()), 1208 Reserve(acc_pub4.clone()), 1209 Pending, 1210 Pending, 1211 Kyc(acc_pub4.clone()), 1212 Reserve(auth_pub2.clone()), 1213 Bounced, 1214 Bounced, 1215 Reserve(acc_pub5.clone()), 1216 Pending, 1217 ]) 1218 .await; 1219 1220 // Recurrent kyc simple subject 1221 server 1222 .post("/taler-prepared-transfer/registration") 1223 .json(&json!(req + { 1224 "type": "kyc", 1225 "account_pub": acc_pub5, 1226 "authorization_pub": auth_pub2, 1227 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1228 "recurrent": false 1229 })) 1230 .await 1231 .assert_ok_json::<RegistrationResponse>(); 1232 server 1233 .post("/taler-prepared-transfer/registration") 1234 .json(&json!(req + { 1235 "type": "kyc", 1236 "account_pub": acc_pub5, 1237 "authorization_pub": auth_pub2, 1238 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1239 "recurrent": true 1240 })) 1241 .await 1242 .assert_ok_json::<RegistrationResponse>(); 1243 server 1244 .post("/taler-wire-gateway/admin/add-kycauth") 1245 .json(&json!({ 1246 "amount": amount, 1247 "account_pub": acc_pub5, 1248 "debit_account": account, 1249 })) 1250 .await 1251 .assert_ok(); 1252 for _ in 0..2 { 1253 register(&auth_pub2) 1254 .await 1255 .assert_ok_json::<TransferResponse>(); 1256 } 1257 check_in(&[ 1258 Reserve(acc_pub1.clone()), 1259 Reserve(acc_pub2.clone()), 1260 Reserve(acc_pub3.clone()), 1261 Kyc(acc_pub4.clone()), 1262 Reserve(acc_pub4.clone()), 1263 Pending, 1264 Pending, 1265 Kyc(acc_pub4.clone()), 1266 Reserve(auth_pub2.clone()), 1267 Bounced, 1268 Bounced, 1269 Reserve(acc_pub5.clone()), 1270 Bounced, 1271 Kyc(acc_pub5.clone()), 1272 Pending, 1273 Pending, 1274 ]) 1275 .await; 1276 1277 /* ----- Unregistration ----- */ 1278 let now = Timestamp::now().to_string(); 1279 let un_req = json!({ 1280 "timestamp": now, 1281 "authorization_pub": auth_pub2, 1282 "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()), 1283 }); 1284 1285 // Delete 1286 server 1287 .delete("/taler-prepared-transfer/registration") 1288 .json(&un_req) 1289 .await 1290 .assert_no_content(); 1291 1292 // Check bounce pending on deletion 1293 1294 check_in(&[ 1295 Reserve(acc_pub1.clone()), 1296 Reserve(acc_pub2.clone()), 1297 Reserve(acc_pub3.clone()), 1298 Kyc(acc_pub4.clone()), 1299 Reserve(acc_pub4.clone()), 1300 Pending, 1301 Pending, 1302 Kyc(acc_pub4.clone()), 1303 Reserve(auth_pub2.clone()), 1304 Bounced, 1305 Bounced, 1306 Reserve(acc_pub5.clone()), 1307 Bounced, 1308 Kyc(acc_pub5.clone()), 1309 Bounced, 1310 Bounced, 1311 ]) 1312 .await; 1313 1314 // Idempotent 1315 server 1316 .delete("/taler-prepared-transfer/registration") 1317 .json(&un_req) 1318 .await 1319 .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND); 1320 1321 // Bad signature 1322 server 1323 .delete("/taler-prepared-transfer/registration") 1324 .json(&json!(un_req + { 1325 "authorization_sig": eddsa_sign(&auth_pair, "lol".as_bytes()), 1326 })) 1327 .await 1328 .assert_error(ErrorCode::BANK_BAD_SIGNATURE); 1329 1330 // Old timestamp 1331 let now = (Timestamp::now() - SignedDuration::from_mins(10)).to_string(); 1332 server 1333 .delete("/taler-prepared-transfer/registration") 1334 .json(&json!({ 1335 "timestamp": now, 1336 "authorization_pub": auth_pub2, 1337 "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()), 1338 })) 1339 .await 1340 .assert_error(ErrorCode::BANK_OLD_TIMESTAMP); 1341 1342 /* ----- API ----- */ 1343 1344 let history: Vec<_> = server 1345 .get("/taler-wire-gateway/history/incoming?limit=20") 1346 .await 1347 .assert_ok_json::<IncomingHistory>() 1348 .incoming_transactions 1349 .into_iter() 1350 .map(|tx| { 1351 let (acc_pub, auth_pub, auth_sig) = match tx { 1352 IncomingBankTransaction::Reserve { 1353 reserve_pub, 1354 authorization_pub, 1355 authorization_sig, 1356 .. 1357 } => (reserve_pub, authorization_pub, authorization_sig), 1358 IncomingBankTransaction::Wad { .. } => unreachable!(), 1359 IncomingBankTransaction::Kyc { 1360 account_pub, 1361 authorization_pub, 1362 authorization_sig, 1363 .. 1364 } => (account_pub, authorization_pub, authorization_sig), 1365 }; 1366 if let Some(auth_pub) = &auth_pub { 1367 assert!(check_eddsa_signature( 1368 auth_pub, 1369 acc_pub.as_ref(), 1370 &auth_sig.unwrap() 1371 )); 1372 } else { 1373 assert!(auth_sig.is_none()) 1374 } 1375 (acc_pub, auth_pub) 1376 }) 1377 .collect(); 1378 assert_eq!( 1379 history, 1380 [ 1381 (acc_pub1.clone(), Some(auth_pub1.clone())), 1382 (acc_pub2.clone(), None), 1383 (acc_pub3.clone(), Some(auth_pub1.clone())), 1384 (acc_pub4.clone(), Some(auth_pub1.clone())), 1385 (acc_pub4.clone(), Some(auth_pub1.clone())), 1386 (acc_pub4.clone(), None), 1387 (auth_pub2.clone(), Some(auth_pub2.clone())), 1388 (acc_pub5.clone(), None), 1389 (acc_pub5.clone(), None), 1390 ] 1391 ) 1392 }