routine.rs (38166B)
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, F: Future<Output = ()>>( 102 server: &Router, 103 url: &str, 104 mut register: impl FnMut(usize) -> F, 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 async fn check_history_trigger<T: Page, F: Future<Output = ()>>( 149 server: &Router, 150 url: &str, 151 lambda: impl FnOnce() -> F, 152 ) { 153 // Check history is following specs 154 macro_rules! assert_history { 155 ($args:expr, $size:expr) => { 156 async { 157 server 158 .get(&format!("{url}?{}", $args)) 159 .await 160 .assert_ids::<T>($size) 161 } 162 }; 163 } 164 // Get latest registered id 165 let latest_id = latest_id::<T>(server, url).await; 166 tokio::join!( 167 // Check polling succeed 168 assert_time( 169 100..400, 170 assert_history!( 171 format_args!("limit=2&offset={latest_id}&timeout_ms=1000"), 172 1 173 ) 174 ), 175 assert_time( 176 200..500, 177 assert_history!( 178 format_args!("limit=1&offset={}&timeout_ms=200", latest_id + 3), 179 0 180 ) 181 ), 182 async { 183 sleep(Duration::from_millis(100)).await; 184 lambda().await 185 } 186 ); 187 } 188 189 async fn check_history_in_trigger<F: Future<Output = ()>>( 190 server: &Router, 191 lambda: impl FnOnce() -> F, 192 ) { 193 check_history_trigger::<IncomingHistory, _>( 194 server, 195 "/taler-wire-gateway/history/incoming", 196 lambda, 197 ) 198 .await; 199 } 200 201 pub async fn routine_history<T: Page, FR: Future<Output = ()>, FI: Future<Output = ()>>( 202 server: &Router, 203 url: &str, 204 nb_register: usize, 205 mut register: impl FnMut(usize) -> FR, 206 nb_ignore: usize, 207 mut ignore: impl FnMut(usize) -> FI, 208 ) { 209 // Check history is following specs 210 macro_rules! assert_history { 211 ($args:expr, $size:expr) => { 212 async { 213 server 214 .get(&format!("{url}?{}", $args)) 215 .await 216 .assert_ids::<T>($size) 217 } 218 }; 219 } 220 // Get latest registered id 221 let latest_id = async || assert_history!("limit=-1", 1).await[0]; 222 223 // Check error when no transactions 224 assert_history!("limit=7".to_owned(), 0).await; 225 226 let mut register_iter = (0..nb_register).peekable(); 227 let mut ignore_iter = (0..nb_ignore).peekable(); 228 while register_iter.peek().is_some() || ignore_iter.peek().is_some() { 229 if let Some(idx) = register_iter.next() { 230 register(idx).await 231 } 232 if let Some(idx) = ignore_iter.next() { 233 ignore(idx).await 234 } 235 } 236 let nb_total = nb_register + nb_ignore; 237 238 // Check ignored 239 assert_history!(format_args!("limit={nb_total}"), nb_register).await; 240 // Check skip ignored 241 assert_history!(format_args!("limit={nb_register}"), nb_register).await; 242 243 // Check no polling when we cannot have more transactions 244 assert_time( 245 0..200, 246 assert_history!( 247 format_args!("limit=-{}&timeout_ms=1000", nb_register + 1), 248 nb_register 249 ), 250 ) 251 .await; 252 // Check no polling when already find transactions even if less than delta 253 assert_time( 254 0..200, 255 assert_history!( 256 format_args!("limit={}&timeout_ms=1000", nb_register + 1), 257 nb_register 258 ), 259 ) 260 .await; 261 262 // Check polling 263 let id = latest_id().await; 264 tokio::join!( 265 // Check polling succeed 266 assert_time( 267 100..400, 268 assert_history!(format_args!("limit=2&offset={id}&timeout_ms=1000"), 1) 269 ), 270 assert_time( 271 200..500, 272 assert_history!( 273 format_args!( 274 "limit=1&offset={}&timeout_ms=200", 275 id as usize + nb_total * 3 276 ), 277 0 278 ) 279 ), 280 async { 281 sleep(Duration::from_millis(100)).await; 282 register(0).await 283 } 284 ); 285 286 // Test triggers 287 for i in 0..nb_register { 288 let id = latest_id().await; 289 tokio::join!( 290 // Check polling succeed 291 assert_time( 292 100..400, 293 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=1000"), 1) 294 ), 295 async { 296 sleep(Duration::from_millis(100)).await; 297 register(i).await 298 } 299 ); 300 } 301 302 // Test doesn't trigger 303 let id = latest_id().await; 304 tokio::join!( 305 // Check polling succeed 306 assert_time( 307 200..500, 308 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=200"), 0) 309 ), 310 async { 311 sleep(Duration::from_millis(100)).await; 312 for i in 0..nb_ignore { 313 ignore(i).await 314 } 315 } 316 ); 317 318 routine_pagination::<T, _>(server, url, register).await; 319 } 320 321 impl TestResponse { 322 #[track_caller] 323 fn assert_ids<T: Page>(&self, size: usize) -> Vec<i64> { 324 if size == 0 { 325 self.assert_no_content(); 326 return vec![]; 327 } 328 let body = self.assert_ok_json::<T>(); 329 let page = body.ids(); 330 let params = self.query::<PageParams>().check(1024).unwrap(); 331 332 // testing the size is like expected 333 assert_eq!(size, page.len(), "bad page length: {page:?}"); 334 if params.limit < 0 { 335 // testing that the first id is at most the 'offset' query param. 336 assert!( 337 params 338 .offset 339 .map(|offset| page[0] <= offset) 340 .unwrap_or(true), 341 "bad page offset: {params:?} {page:?}" 342 ); 343 // testing that the id decreases. 344 assert!( 345 page.as_slice().is_sorted_by(|a, b| a > b), 346 "bad page order: {page:?}" 347 ) 348 } else { 349 // testing that the first id is at least the 'offset' query param. 350 assert!( 351 params 352 .offset 353 .map(|offset| page[0] >= offset) 354 .unwrap_or(true), 355 "bad page offset: {params:?} {page:?}" 356 ); 357 // testing that the id increases. 358 assert!(page.as_slice().is_sorted(), "bad page order: {page:?}") 359 } 360 page 361 } 362 } 363 364 // Get currency from config 365 async fn get_wire_currency(server: &Router) -> String { 366 let config = server 367 .get("/taler-wire-gateway/config") 368 .await 369 .assert_ok_json::<serde_json::Value>(); 370 let currency = config["currency"].as_str().unwrap(); 371 currency.to_owned() 372 } 373 374 /// Test standard behavior of the transfer endpoints 375 pub async fn transfer_routine( 376 server: &Router, 377 default_status: TransferState, 378 credit_account: &PaytoURI, 379 ) { 380 let currency = &get_wire_currency(server).await; 381 let default_amount = amount(format!("{currency}:42")); 382 let request_uid = HashCode::rand(); 383 let wtid = ShortHashCode::rand(); 384 let transfer_request = json!({ 385 "request_uid": request_uid, 386 "amount": default_amount, 387 "exchange_base_url": "http://exchange.taler/", 388 "wtid": wtid, 389 "credit_account": credit_account, 390 }); 391 392 // Check empty db 393 { 394 server 395 .get("/taler-wire-gateway/transfers") 396 .await 397 .assert_no_content(); 398 server 399 .get(&format!( 400 "/taler-wire-gateway/transfers?status={}", 401 default_status.as_ref() 402 )) 403 .await 404 .assert_no_content(); 405 } 406 407 // TODO check subject formatting 408 409 let routine = async |req: &TransferRequest| { 410 // Check OK 411 let first = server 412 .post("/taler-wire-gateway/transfer") 413 .json(&req) 414 .await 415 .assert_ok_json::<TransferResponse>(); 416 // Check idempotent 417 let second = server 418 .post("/taler-wire-gateway/transfer") 419 .json(&req) 420 .await 421 .assert_ok_json::<TransferResponse>(); 422 assert_eq!(first.row_id, second.row_id); 423 assert_eq!(first.timestamp, second.timestamp); 424 425 // Check by id 426 let tx = server 427 .get(&format!("/taler-wire-gateway/transfers/{}", first.row_id)) 428 .await 429 .assert_ok_json::<TransferStatus>(); 430 assert_eq!(default_status, tx.status); 431 assert_eq!(default_amount, tx.amount); 432 assert_eq!("http://exchange.taler/", tx.origin_exchange_url); 433 assert_eq!(req.wtid, tx.wtid); 434 assert_eq!(first.timestamp, tx.timestamp); 435 assert_eq!(req.metadata, tx.metadata); 436 assert_eq!(credit_account, &tx.credit_account); 437 438 // Check page 439 let list = server 440 .get("/taler-wire-gateway/transfers?limit=-1") 441 .await 442 .assert_ok_json::<TransferList>(); 443 let tx = &list.transfers[0]; 444 assert_eq!(first.row_id, tx.row_id); 445 assert_eq!(default_status, tx.status); 446 assert_eq!(default_amount, tx.amount); 447 assert_eq!(first.timestamp, tx.timestamp); 448 assert_eq!(credit_account, &tx.credit_account); 449 }; 450 451 let req = TransferRequest { 452 request_uid, 453 amount: default_amount.clone(), 454 exchange_base_url: url("http://exchange.taler/"), 455 metadata: None, 456 wtid, 457 credit_account: credit_account.clone(), 458 }; 459 // Simple 460 routine(&req).await; 461 // With metadata 462 routine(&TransferRequest { 463 request_uid: HashCode::rand(), 464 wtid: ShortHashCode::rand(), 465 metadata: Some("test:medatata".into()), 466 ..req 467 }) 468 .await; 469 470 // Check create transfer errors 471 { 472 // Check request uid reuse 473 server 474 .post("/taler-wire-gateway/transfer") 475 .json(&json!(transfer_request + { 476 "wtid": ShortHashCode::rand() 477 })) 478 .await 479 .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED); 480 // Check wtid reuse 481 server 482 .post("/taler-wire-gateway/transfer") 483 .json(&json!(transfer_request + { 484 "request_uid": HashCode::rand(), 485 })) 486 .await 487 .assert_error(ErrorCode::BANK_TRANSFER_WTID_REUSED); 488 489 // Check currency mismatch 490 server 491 .post("/taler-wire-gateway/transfer") 492 .json(&json!(transfer_request + { 493 "amount": "BAD:42" 494 })) 495 .await 496 .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH); 497 498 // Malformed metadata 499 for metadata in ["bad_id", "bad id", "bad@id.com", &"A".repeat(41)] { 500 server 501 .post("/taler-wire-gateway/transfer") 502 .json(&json!(transfer_request + { 503 "metadata": metadata 504 })) 505 .await 506 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 507 } 508 } 509 510 // Check transfer by id errors 511 { 512 // Check unknown transaction 513 server 514 .get("/taler-wire-gateway/transfers/42") 515 .await 516 .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND); 517 } 518 519 // Check transfer page 520 { 521 for _ in 0..4 { 522 server 523 .post("/taler-wire-gateway/transfer") 524 .json(&json!(transfer_request + { 525 "request_uid": HashCode::rand(), 526 "wtid": ShortHashCode::rand(), 527 })) 528 .await 529 .assert_ok_json::<TransferResponse>(); 530 } 531 { 532 let list = server 533 .get("/taler-wire-gateway/transfers") 534 .await 535 .assert_ok_json::<TransferList>(); 536 assert_eq!(list.transfers.len(), 6); 537 assert_eq!( 538 list, 539 server 540 .get(&format!( 541 "/taler-wire-gateway/transfers?status={}", 542 default_status.as_ref() 543 )) 544 .await 545 .assert_ok_json::<TransferList>() 546 ) 547 } 548 549 // Pagination test 550 routine_pagination::<TransferList, _>(server, "/taler-wire-gateway/transfers", async |i| { 551 server 552 .post("/taler-wire-gateway/transfer") 553 .json(&json!({ 554 "request_uid": HashCode::rand(), 555 "amount": amount(format!("{currency}:0.0{i}")), 556 "exchange_base_url": url("http://exchange.taler"), 557 "wtid": ShortHashCode::rand(), 558 "credit_account": credit_account, 559 })) 560 .await 561 .assert_ok_json::<TransferResponse>(); 562 }) 563 .await; 564 } 565 } 566 567 async fn add_incoming_routine( 568 server: &Router, 569 currency: &str, 570 kind: IncomingType, 571 debit_acount: &PaytoURI, 572 ) { 573 let (path, key) = match kind { 574 IncomingType::reserve => ("/taler-wire-gateway/admin/add-incoming", "reserve_pub"), 575 IncomingType::kyc => ("/taler-wire-gateway/admin/add-kycauth", "account_pub"), 576 IncomingType::map => unreachable!(), 577 }; 578 let valid_req = json!({ 579 "amount": format!("{currency}:44"), 580 key: EddsaPublicKey::rand(), 581 "debit_account": debit_acount, 582 }); 583 584 // Check OK 585 server.post(path).json(&valid_req).await.assert_ok(); 586 587 match kind { 588 IncomingType::reserve => { 589 // Trigger conflict due to reused reserve_pub 590 server 591 .post(path) 592 .json(&json!(valid_req + { 593 "amount": format!("{currency}:44.1"), 594 })) 595 .await 596 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT) 597 } 598 IncomingType::kyc => { 599 // Non conflict on reuse 600 server.post(path).json(&valid_req).await.assert_ok(); 601 } 602 IncomingType::map => unreachable!(), 603 } 604 605 // Currency mismatch 606 server 607 .post(path) 608 .json(&json!(valid_req + { 609 "amount": "BAD:33" 610 })) 611 .await 612 .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH); 613 614 // Bad BASE32 reserve_pub 615 server 616 .post(path) 617 .json(&json!(valid_req + { 618 key: "I love chocolate" 619 })) 620 .await 621 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 622 623 server 624 .post(path) 625 .json(&json!(valid_req + { 626 key: Base32::<31>::rand() 627 })) 628 .await 629 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 630 631 // Bad payto kind 632 server 633 .post(path) 634 .json(&json!(valid_req + { 635 "debit_account": "http://email@test.com" 636 })) 637 .await 638 .assert_error(ErrorCode::GENERIC_JSON_INVALID); 639 } 640 641 /// Test standard behavior of the revenue endpoints 642 pub async fn revenue_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool) { 643 let currency = &get_wire_currency(server).await; 644 645 routine_history::<RevenueIncomingHistory, _, _>( 646 server, 647 "/taler-revenue/history", 648 2, 649 async |i| { 650 if i % 2 == 0 || !kyc { 651 server 652 .post("/taler-wire-gateway/admin/add-incoming") 653 .json(&json!({ 654 "amount": format!("{currency}:0.0{i}"), 655 "reserve_pub": EddsaPublicKey::rand(), 656 "debit_account": debit_acount, 657 })) 658 .await 659 .assert_ok_json::<TransferResponse>(); 660 } else { 661 server 662 .post("/taler-wire-gateway/admin/add-kycauth") 663 .json(&json!({ 664 "amount": format!("{currency}:0.0{i}"), 665 "account_pub": EddsaPublicKey::rand(), 666 "debit_account": debit_acount, 667 })) 668 .await 669 .assert_ok_json::<TransferResponse>(); 670 } 671 }, 672 0, 673 async |_| {}, 674 ) 675 .await; 676 } 677 678 /// Test standard behavior of the admin add incoming endpoints 679 pub async fn admin_add_incoming_routine(server: &Router, debit_acount: &PaytoURI, kyc: bool) { 680 let currency = &get_wire_currency(server).await; 681 682 // History 683 // TODO check non taler some are ignored 684 routine_history::<IncomingHistory, _, _>( 685 server, 686 "/taler-wire-gateway/history/incoming", 687 2, 688 async |i| { 689 if i % 2 == 0 || !kyc { 690 server 691 .post("/taler-wire-gateway/admin/add-incoming") 692 .json(&json!({ 693 "amount": format!("{currency}:0.0{i}"), 694 "reserve_pub": EddsaPublicKey::rand(), 695 "debit_account": debit_acount, 696 })) 697 .await 698 .assert_ok_json::<TransferResponse>(); 699 } else { 700 server 701 .post("/taler-wire-gateway/admin/add-kycauth") 702 .json(&json!({ 703 "amount": format!("{currency}:0.0{i}"), 704 "account_pub": EddsaPublicKey::rand(), 705 "debit_account": debit_acount, 706 })) 707 .await 708 .assert_ok_json::<TransferResponse>(); 709 } 710 }, 711 0, 712 async |_| {}, 713 ) 714 .await; 715 // Add incoming reserve 716 add_incoming_routine(server, currency, IncomingType::reserve, debit_acount).await; 717 if kyc { 718 // Add incoming kyc 719 add_incoming_routine(server, currency, IncomingType::kyc, debit_acount).await; 720 } 721 } 722 723 // Get currency from config 724 async fn get_transfer_currency(server: &Router) -> String { 725 let config = server 726 .get("/taler-wire-transfer-gateway/config") 727 .await 728 .assert_ok_json::<serde_json::Value>(); 729 let currency = config["currency"].as_str().unwrap(); 730 currency.to_owned() 731 } 732 733 #[derive(Debug, PartialEq, Eq)] 734 pub enum Status { 735 Simple, 736 Pending, 737 Bounced, 738 Incomplete, 739 Reserve(EddsaPublicKey), 740 Kyc(EddsaPublicKey), 741 } 742 743 /// Test standard registration behavior of the registration endpoints 744 pub async fn registration_routine<F1: Future<Output = Vec<Status>>, F2: Future<Output = ()>>( 745 server: &Router, 746 account: &PaytoURI, 747 mut in_status: impl FnMut() -> F1, 748 mut register: impl FnMut(&EddsaPublicKey) -> F2, 749 ) { 750 pub use Status::*; 751 let mut check_in = async |state: &[Status]| { 752 let current = in_status().await; 753 assert_eq!(state, current); 754 }; 755 756 let currency = &get_transfer_currency(server).await; 757 let amount = amount(format!("{currency}:42")); 758 let key_pair1 = Ed25519KeyPair::generate().unwrap(); 759 let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap(); 760 let req = json!({ 761 "credit_amount": amount, 762 "type": "reserve", 763 "alg": "EdDSA", 764 "account_pub": auth_pub1, 765 "authorization_pub": auth_pub1, 766 "authorization_sig": eddsa_sign(&key_pair1, auth_pub1.as_ref()), 767 "recurrent": false 768 }); 769 770 /* ----- Registration ----- */ 771 let routine = async |ty: TransferType, 772 account_pub: &EddsaPublicKey, 773 recurrent: bool, 774 fmt: IncomingType| { 775 let req = json!(req + { 776 "type": ty, 777 "account_pub": account_pub, 778 "authorization_pub": auth_pub1, 779 "authorization_sig": eddsa_sign(&key_pair1, account_pub.as_ref()), 780 "recurrent": recurrent 781 }); 782 // Valid 783 let res = server 784 .post("/taler-wire-transfer-gateway/registration") 785 .json(&req) 786 .await 787 .assert_ok_json::<RegistrationResponse>(); 788 789 // Idempotent 790 assert_eq!( 791 res, 792 server 793 .post("/taler-wire-transfer-gateway/registration") 794 .json(&req) 795 .await 796 .assert_ok_json::<RegistrationResponse>() 797 ); 798 799 assert!(!res.subjects.is_empty()); 800 801 for sub in res.subjects { 802 if let TransferSubject::Simple { subject, .. } = sub { 803 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1)); 804 }; 805 } 806 }; 807 for ty in [TransferType::reserve, TransferType::kyc] { 808 routine(ty, &auth_pub1, false, ty.into()).await; 809 routine(ty, &auth_pub1, true, IncomingType::map).await; 810 } 811 812 let acc_pub1 = EddsaPublicKey::rand(); 813 for ty in [TransferType::reserve, TransferType::kyc] { 814 routine(ty, &acc_pub1, false, IncomingType::map).await; 815 routine(ty, &acc_pub1, true, IncomingType::map).await; 816 } 817 818 // Bad signature 819 server 820 .post("/taler-wire-transfer-gateway/registration") 821 .json(&json!(req + { 822 "authorization_sig": eddsa_sign(&key_pair1, "lol".as_bytes()), 823 })) 824 .await 825 .assert_error(ErrorCode::BANK_BAD_SIGNATURE); 826 827 // Reserve pub reuse 828 server 829 .post("/taler-wire-transfer-gateway/registration") 830 .json(&json!(req + { 831 "account_pub": acc_pub1, 832 "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()), 833 })) 834 .await 835 .assert_ok_json::<RegistrationResponse>(); 836 { 837 let key_pair = Ed25519KeyPair::generate().unwrap(); 838 let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap(); 839 server 840 .post("/taler-wire-transfer-gateway/registration") 841 .json(&json!(req + { 842 "account_pub": acc_pub1, 843 "authorization_pub": auth_pub, 844 "authorization_sig": eddsa_sign(&key_pair, acc_pub1.as_ref()), 845 })) 846 .await 847 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT); 848 } 849 850 // Non recurrent accept one then bounce 851 server 852 .post("/taler-wire-transfer-gateway/registration") 853 .json(&json!(req + { 854 "account_pub": acc_pub1, 855 "authorization_sig": eddsa_sign(&key_pair1, acc_pub1.as_ref()), 856 })) 857 .await 858 .assert_ok_json::<RegistrationResponse>(); 859 check_history_in_trigger(server, async || register(&auth_pub1).await).await; 860 check_in(&[Reserve(acc_pub1.clone())]).await; 861 register(&auth_pub1).await; 862 check_in(&[Reserve(acc_pub1.clone()), Bounced]).await; 863 864 // Again without using mapping 865 let acc_pub2 = EddsaPublicKey::rand(); 866 server 867 .post("/taler-wire-transfer-gateway/registration") 868 .json(&json!(req + { 869 "account_pub": acc_pub2, 870 "authorization_sig": eddsa_sign(&key_pair1, acc_pub2.as_ref()), 871 })) 872 .await 873 .assert_ok_json::<RegistrationResponse>(); 874 server 875 .post("/taler-wire-gateway/admin/add-incoming") 876 .json(&json!({ 877 "amount": amount, 878 "reserve_pub": acc_pub2, 879 "debit_account": account, 880 })) 881 .await 882 .assert_ok(); 883 register(&auth_pub1).await; 884 check_in(&[ 885 Reserve(acc_pub1.clone()), 886 Bounced, 887 Reserve(acc_pub2.clone()), 888 Bounced, 889 ]) 890 .await; 891 892 // Recurrent accept one and delay others 893 let acc_pub3 = EddsaPublicKey::rand(); 894 server 895 .post("/taler-wire-transfer-gateway/registration") 896 .json(&json!(req + { 897 "account_pub": acc_pub3, 898 "authorization_sig": eddsa_sign(&key_pair1, acc_pub3.as_ref()), 899 "recurrent": true 900 })) 901 .await 902 .assert_ok_json::<RegistrationResponse>(); 903 for _ in 0..5 { 904 register(&auth_pub1).await; 905 } 906 check_in(&[ 907 Reserve(acc_pub1.clone()), 908 Bounced, 909 Reserve(acc_pub2.clone()), 910 Bounced, 911 Reserve(acc_pub3.clone()), 912 Pending, 913 Pending, 914 Pending, 915 Pending, 916 ]) 917 .await; 918 919 // Complete pending on recurrent update 920 let acc_pub4 = EddsaPublicKey::rand(); 921 server 922 .post("/taler-wire-transfer-gateway/registration") 923 .json(&json!(req + { 924 "type": "kyc", 925 "account_pub": acc_pub4, 926 "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()), 927 "recurrent": true 928 })) 929 .await 930 .assert_ok_json::<RegistrationResponse>(); 931 check_history_in_trigger(server, async || { 932 server 933 .post("/taler-wire-transfer-gateway/registration") 934 .json(&json!(req + { 935 "account_pub": acc_pub4, 936 "authorization_sig": eddsa_sign(&key_pair1, acc_pub4.as_ref()), 937 "recurrent": true 938 })) 939 .await 940 .assert_ok_json::<RegistrationResponse>(); 941 }) 942 .await; 943 check_in(&[ 944 Reserve(acc_pub1.clone()), 945 Bounced, 946 Reserve(acc_pub2.clone()), 947 Bounced, 948 Reserve(acc_pub3.clone()), 949 Kyc(acc_pub4.clone()), 950 Reserve(acc_pub4.clone()), 951 Pending, 952 Pending, 953 ]) 954 .await; 955 956 // Kyc key reuse keep pending ones 957 server 958 .post("/taler-wire-gateway/admin/add-kycauth") 959 .json(&json!({ 960 "amount": amount, 961 "account_pub": acc_pub4, 962 "debit_account": account, 963 })) 964 .await 965 .assert_ok_json::<TransferResponse>(); 966 check_in(&[ 967 Reserve(acc_pub1.clone()), 968 Bounced, 969 Reserve(acc_pub2.clone()), 970 Bounced, 971 Reserve(acc_pub3.clone()), 972 Kyc(acc_pub4.clone()), 973 Reserve(acc_pub4.clone()), 974 Pending, 975 Pending, 976 Kyc(acc_pub4.clone()), 977 ]) 978 .await; 979 980 // Switching to non recurrent cancel pending 981 let auth_pair = Ed25519KeyPair::generate().unwrap(); 982 let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap(); 983 server 984 .post("/taler-wire-transfer-gateway/registration") 985 .json(&json!(req + { 986 "account_pub": auth_pub2, 987 "authorization_pub": auth_pub2, 988 "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()), 989 "recurrent": true 990 })) 991 .await 992 .assert_ok_json::<RegistrationResponse>(); 993 for _ in 0..3 { 994 register(&auth_pub2).await; 995 } 996 check_in(&[ 997 Reserve(acc_pub1.clone()), 998 Bounced, 999 Reserve(acc_pub2.clone()), 1000 Bounced, 1001 Reserve(acc_pub3.clone()), 1002 Kyc(acc_pub4.clone()), 1003 Reserve(acc_pub4.clone()), 1004 Pending, 1005 Pending, 1006 Kyc(acc_pub4.clone()), 1007 Reserve(auth_pub2.clone()), 1008 Pending, 1009 Pending, 1010 ]) 1011 .await; 1012 server 1013 .post("/taler-wire-transfer-gateway/registration") 1014 .json(&json!(req + { 1015 "type": "kyc", 1016 "account_pub": auth_pub2, 1017 "authorization_pub": auth_pub2, 1018 "authorization_sig": eddsa_sign(&auth_pair, auth_pub2.as_ref()), 1019 "recurrent": false 1020 })) 1021 .await 1022 .assert_ok_json::<RegistrationResponse>(); 1023 check_in(&[ 1024 Reserve(acc_pub1.clone()), 1025 Bounced, 1026 Reserve(acc_pub2.clone()), 1027 Bounced, 1028 Reserve(acc_pub3.clone()), 1029 Kyc(acc_pub4.clone()), 1030 Reserve(acc_pub4.clone()), 1031 Pending, 1032 Pending, 1033 Kyc(acc_pub4.clone()), 1034 Reserve(auth_pub2.clone()), 1035 Bounced, 1036 Bounced, 1037 ]) 1038 .await; 1039 1040 // Recurrent reserve simple subject 1041 let acc_pub5 = EddsaPublicKey::rand(); 1042 server 1043 .post("/taler-wire-transfer-gateway/registration") 1044 .json(&json!(req + { 1045 "type": "reserve", 1046 "account_pub": acc_pub5, 1047 "authorization_pub": auth_pub2, 1048 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1049 "recurrent": true 1050 })) 1051 .await 1052 .assert_ok_json::<RegistrationResponse>(); 1053 server 1054 .post("/taler-wire-gateway/admin/add-incoming") 1055 .json(&json!({ 1056 "amount": amount, 1057 "reserve_pub": acc_pub5, 1058 "debit_account": account, 1059 })) 1060 .await 1061 .assert_ok(); 1062 register(&auth_pub2).await; 1063 check_in(&[ 1064 Reserve(acc_pub1.clone()), 1065 Bounced, 1066 Reserve(acc_pub2.clone()), 1067 Bounced, 1068 Reserve(acc_pub3.clone()), 1069 Kyc(acc_pub4.clone()), 1070 Reserve(acc_pub4.clone()), 1071 Pending, 1072 Pending, 1073 Kyc(acc_pub4.clone()), 1074 Reserve(auth_pub2.clone()), 1075 Bounced, 1076 Bounced, 1077 Reserve(acc_pub5.clone()), 1078 Pending, 1079 ]) 1080 .await; 1081 1082 // Recurrent kyc simple subject 1083 server 1084 .post("/taler-wire-transfer-gateway/registration") 1085 .json(&json!(req + { 1086 "type": "kyc", 1087 "account_pub": acc_pub5, 1088 "authorization_pub": auth_pub2, 1089 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1090 "recurrent": false 1091 })) 1092 .await 1093 .assert_ok_json::<RegistrationResponse>(); 1094 server 1095 .post("/taler-wire-transfer-gateway/registration") 1096 .json(&json!(req + { 1097 "type": "kyc", 1098 "account_pub": acc_pub5, 1099 "authorization_pub": auth_pub2, 1100 "authorization_sig": eddsa_sign(&auth_pair, acc_pub5.as_ref()), 1101 "recurrent": true 1102 })) 1103 .await 1104 .assert_ok_json::<RegistrationResponse>(); 1105 server 1106 .post("/taler-wire-gateway/admin/add-kycauth") 1107 .json(&json!({ 1108 "amount": amount, 1109 "account_pub": acc_pub5, 1110 "debit_account": account, 1111 })) 1112 .await 1113 .assert_ok(); 1114 register(&auth_pub2).await; 1115 register(&auth_pub2).await; 1116 check_in(&[ 1117 Reserve(acc_pub1.clone()), 1118 Bounced, 1119 Reserve(acc_pub2.clone()), 1120 Bounced, 1121 Reserve(acc_pub3.clone()), 1122 Kyc(acc_pub4.clone()), 1123 Reserve(acc_pub4.clone()), 1124 Pending, 1125 Pending, 1126 Kyc(acc_pub4.clone()), 1127 Reserve(auth_pub2.clone()), 1128 Bounced, 1129 Bounced, 1130 Reserve(acc_pub5.clone()), 1131 Bounced, 1132 Kyc(acc_pub5.clone()), 1133 Pending, 1134 Pending, 1135 ]) 1136 .await; 1137 1138 /* ----- Unregistration ----- */ 1139 let now = Timestamp::now().to_string(); 1140 let un_req = json!({ 1141 "timestamp": now, 1142 "authorization_pub": auth_pub2, 1143 "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()), 1144 }); 1145 1146 // Delete 1147 server 1148 .delete("/taler-wire-transfer-gateway/registration") 1149 .json(&un_req) 1150 .await 1151 .assert_no_content(); 1152 1153 // Check bounce pending on deletion 1154 1155 check_in(&[ 1156 Reserve(acc_pub1.clone()), 1157 Bounced, 1158 Reserve(acc_pub2.clone()), 1159 Bounced, 1160 Reserve(acc_pub3.clone()), 1161 Kyc(acc_pub4.clone()), 1162 Reserve(acc_pub4.clone()), 1163 Pending, 1164 Pending, 1165 Kyc(acc_pub4.clone()), 1166 Reserve(auth_pub2.clone()), 1167 Bounced, 1168 Bounced, 1169 Reserve(acc_pub5.clone()), 1170 Bounced, 1171 Kyc(acc_pub5.clone()), 1172 Bounced, 1173 Bounced, 1174 ]) 1175 .await; 1176 1177 // Idempotent 1178 server 1179 .delete("/taler-wire-transfer-gateway/registration") 1180 .json(&un_req) 1181 .await 1182 .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND); 1183 1184 // Bad signature 1185 server 1186 .delete("/taler-wire-transfer-gateway/registration") 1187 .json(&json!(un_req + { 1188 "authorization_sig": eddsa_sign(&auth_pair, "lol".as_bytes()), 1189 })) 1190 .await 1191 .assert_error(ErrorCode::BANK_BAD_SIGNATURE); 1192 1193 // Old timestamp 1194 let now = (Timestamp::now() - SignedDuration::from_mins(10)).to_string(); 1195 server 1196 .delete("/taler-wire-transfer-gateway/registration") 1197 .json(&json!({ 1198 "timestamp": now, 1199 "authorization_pub": auth_pub2, 1200 "authorization_sig": eddsa_sign(&auth_pair, now.as_bytes()), 1201 })) 1202 .await 1203 .assert_error(ErrorCode::BANK_OLD_TIMESTAMP); 1204 1205 /* ----- API ----- */ 1206 1207 let history: Vec<_> = server 1208 .get("/taler-wire-gateway/history/incoming?limit=20") 1209 .await 1210 .assert_ok_json::<IncomingHistory>() 1211 .incoming_transactions 1212 .into_iter() 1213 .map(|tx| { 1214 let (acc_pub, auth_pub, auth_sig) = match tx { 1215 IncomingBankTransaction::Reserve { 1216 reserve_pub, 1217 authorization_pub, 1218 authorization_sig, 1219 .. 1220 } => (reserve_pub, authorization_pub, authorization_sig), 1221 IncomingBankTransaction::Wad { .. } => unreachable!(), 1222 IncomingBankTransaction::Kyc { 1223 account_pub, 1224 authorization_pub, 1225 authorization_sig, 1226 .. 1227 } => (account_pub, authorization_pub, authorization_sig), 1228 }; 1229 if let Some(auth_pub) = &auth_pub { 1230 assert!(check_eddsa_signature( 1231 auth_pub, 1232 acc_pub.as_ref(), 1233 &auth_sig.unwrap() 1234 )); 1235 } else { 1236 assert!(auth_sig.is_none()) 1237 } 1238 (acc_pub, auth_pub) 1239 }) 1240 .collect(); 1241 dbg!(&history); 1242 assert_eq!( 1243 history, 1244 [ 1245 (acc_pub1.clone(), Some(auth_pub1.clone())), 1246 (acc_pub2.clone(), None), 1247 (acc_pub3.clone(), Some(auth_pub1.clone())), 1248 (acc_pub4.clone(), Some(auth_pub1.clone())), 1249 (acc_pub4.clone(), Some(auth_pub1.clone())), 1250 (acc_pub4.clone(), None), 1251 (auth_pub2.clone(), Some(auth_pub2.clone())), 1252 (acc_pub5.clone(), None), 1253 (acc_pub5.clone(), None), 1254 ] 1255 ) 1256 }