transfer.rs (4004B)
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 PreparedTransferConfig, RegistrationRequest, RegistrationResponse, SubjectFormat, 30 Unregistration, 31 }, 32 error_code::ErrorCode, 33 }; 34 35 use super::TalerApi; 36 use crate::{ 37 constants::PREPARED_TRANSFER_API_VERSION, 38 crypto::check_eddsa_signature, 39 error::{ApiResult, failure, failure_code}, 40 json::Req, 41 }; 42 43 pub trait PreparedTransfer: TalerApi { 44 fn supported_formats(&self) -> &[SubjectFormat]; 45 fn registration( 46 &self, 47 req: RegistrationRequest, 48 ) -> impl std::future::Future<Output = ApiResult<RegistrationResponse>> + Send; 49 fn unregistration( 50 &self, 51 req: Unregistration, 52 ) -> impl std::future::Future<Output = ApiResult<()>> + Send; 53 } 54 55 pub fn router<I: PreparedTransfer>(state: Arc<I>) -> Router { 56 Router::new() 57 .route( 58 "/registration", 59 post( 60 async |State(state): State<Arc<I>>, Req(req): Req<RegistrationRequest>| { 61 state.check_currency(&req.credit_amount)?; 62 if !check_eddsa_signature( 63 &req.authorization_pub, 64 req.account_pub.as_ref(), 65 &req.authorization_sig, 66 ) { 67 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 68 } 69 let res = state.registration(req).await?; 70 ApiResult::Ok(Json(res)) 71 }, 72 ) 73 .delete( 74 async |State(state): State<Arc<I>>, Req(req): Req<Unregistration>| { 75 let timestamp = Timestamp::from_str(&req.timestamp).map_err(|e| { 76 failure(ErrorCode::GENERIC_JSON_INVALID, e.to_string()) 77 .with_path("timestamp") 78 })?; 79 if timestamp.duration_until(Timestamp::now()) > SignedDuration::from_mins(5) { 80 return Err(failure_code(ErrorCode::BANK_OLD_TIMESTAMP)); 81 } 82 83 if !check_eddsa_signature( 84 &req.authorization_pub, 85 req.timestamp.as_ref(), 86 &req.authorization_sig, 87 ) { 88 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 89 } 90 state.unregistration(req).await?; 91 ApiResult::Ok(StatusCode::NO_CONTENT) 92 }, 93 ), 94 ) 95 .route( 96 "/config", 97 get(async |State(state): State<Arc<I>>| { 98 Json(PreparedTransferConfig { 99 name: "taler-prepared-transfer", 100 version: PREPARED_TRANSFER_API_VERSION, 101 currency: state.currency(), 102 implementation: Some(state.implementation()), 103 supported_formats: state.supported_formats().to_vec(), 104 }) 105 .into_response() 106 }), 107 ) 108 .with_state(state) 109 }