transfer.rs (4059B)
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 ) 74 .route( 75 "/unregistration", 76 post( 77 async |State(state): State<Arc<I>>, Req(req): Req<Unregistration>| { 78 let timestamp = Timestamp::from_str(&req.timestamp).map_err(|e| { 79 failure(ErrorCode::GENERIC_JSON_INVALID, e.to_string()) 80 .with_path("timestamp") 81 })?; 82 if timestamp.duration_until(Timestamp::now()) > SignedDuration::from_mins(5) { 83 return Err(failure_code(ErrorCode::BANK_OLD_TIMESTAMP)); 84 } 85 86 if !check_eddsa_signature( 87 &req.authorization_pub, 88 req.timestamp.as_ref(), 89 &req.authorization_sig, 90 ) { 91 return Err(failure_code(ErrorCode::BANK_BAD_SIGNATURE)); 92 } 93 state.unregistration(req).await?; 94 ApiResult::Ok(StatusCode::NO_CONTENT) 95 }, 96 ), 97 ) 98 .route( 99 "/config", 100 get(async |State(state): State<Arc<I>>| { 101 Json(PreparedTransferConfig { 102 name: "taler-prepared-transfer", 103 version: PREPARED_TRANSFER_API_VERSION, 104 currency: state.currency(), 105 implementation: Some(state.implementation()), 106 supported_formats: state.supported_formats().to_vec(), 107 }) 108 .into_response() 109 }), 110 ) 111 .with_state(state) 112 }