transfer.rs (3997B)
1 /* 2 This file is part of TALER 3 Copyright (C) 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::{str::FromStr, sync::Arc}; 18 19 use axum::{ 20 Json, Router, 21 extract::State, 22 http::StatusCode, 23 response::IntoResponse as _, 24 routing::{get, post}, 25 }; 26 use jiff::{SignedDuration, Timestamp}; 27 use taler_common::{ 28 api_transfer::{ 29 RegistrationRequest, RegistrationResponse, SubjectFormat, Unregistration, 30 WireTransferConfig, 31 }, 32 error_code::ErrorCode, 33 }; 34 35 use crate::{ 36 constants::WIRE_GATEWAY_API_VERSION, 37 crypto::check_eddsa_signature, 38 error::{ApiResult, failure, failure_code}, 39 json::Req, 40 }; 41 42 use super::TalerApi; 43 44 pub trait WireTransferGateway: TalerApi { 45 fn supported_formats(&self) -> &[SubjectFormat]; 46 fn registration( 47 &self, 48 req: RegistrationRequest, 49 ) -> impl std::future::Future<Output = ApiResult<RegistrationResponse>> + Send; 50 fn unregistration( 51 &self, 52 req: Unregistration, 53 ) -> impl std::future::Future<Output = ApiResult<()>> + Send; 54 } 55 56 pub fn router<I: WireTransferGateway>(state: Arc<I>) -> Router { 57 Router::new() 58 .route( 59 "/registration", 60 post( 61 async |State(state): State<Arc<I>>, Req(req): Req<RegistrationRequest>| { 62 state.check_currency(&req.credit_amount)?; 63 if !check_eddsa_signature( 64 &req.authorization_pub, 65 req.account_pub.as_ref(), 66 &req.authorization_sig, 67 ) { 68 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 69 } 70 let res = state.registration(req).await?; 71 ApiResult::Ok(Json(res)) 72 }, 73 ) 74 .delete( 75 async |State(state): State<Arc<I>>, Req(req): Req<Unregistration>| { 76 let timestamp = Timestamp::from_str(&req.timestamp).map_err(|e| { 77 failure(ErrorCode::GENERIC_JSON_INVALID, e.to_string()) 78 .with_path("timestamp") 79 })?; 80 if timestamp.duration_until(Timestamp::now()) > SignedDuration::from_mins(5) { 81 return Err(failure_code(ErrorCode::BANK_OLD_TIMESTAMP)); 82 } 83 84 if !check_eddsa_signature( 85 &req.authorization_pub, 86 req.timestamp.as_ref(), 87 &req.authorization_sig, 88 ) { 89 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 90 } 91 state.unregistration(req).await?; 92 ApiResult::Ok(StatusCode::NO_CONTENT) 93 }, 94 ), 95 ) 96 .route( 97 "/config", 98 get(async |State(state): State<Arc<I>>| { 99 Json(WireTransferConfig { 100 name: "taler-wire-transfer-gateway", 101 version: WIRE_GATEWAY_API_VERSION, 102 currency: state.currency(), 103 implementation: Some(state.implementation()), 104 supported_formats: state.supported_formats().to_vec(), 105 }) 106 .into_response() 107 }), 108 ) 109 .with_state(state) 110 }