config.rs (11009B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2026 Taler Systems S.A. 4 5 * LibEuFin is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation; either version 3, or 8 * (at your option) any later version. 9 10 * LibEuFin is distributed in the hope that it will be useful, but 11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General 13 * Public License for more details. 14 15 * You should have received a copy of the GNU Affero General Public 16 * License along with LibEuFin; see the file COPYING. If not, see 17 * <http://www.gnu.org/licenses/> 18 */ 19 20 use std::{cell::OnceCell, time::Duration}; 21 22 use compact_str::CompactString; 23 use jiff::{ 24 Timestamp, 25 civil::{Date, Time}, 26 }; 27 use libeufin_ebics::{ 28 config::{EbicsHostCfg, EbicsKeysCfg, EbicsSetupCfg}, 29 dialect::Dialect, 30 keys::{BankKeys, ClientKeys}, 31 }; 32 use regex::Regex; 33 use taler_api::{ 34 Serve, 35 config::{ApiCfg, DbCfg}, 36 }; 37 use taler_common::{ 38 config::{CfgErr, Config}, 39 types::{ 40 amount::{Amount, Currency}, 41 iban::IBAN, 42 payto::{BankID, FullIbanPayto}, 43 utils::date_to_utc_ts, 44 }, 45 }; 46 use taler_macros::EnumMeta; 47 48 pub fn parse_db_cfg(cfg: &Config) -> Result<DbCfg, CfgErr> { 49 DbCfg::parse(cfg.section("libeufin-nexusdb-postgres")) 50 } 51 52 pub struct NexusKeysCfg { 53 pub bank: String, 54 pub client: String, 55 } 56 57 impl NexusKeysCfg { 58 pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { 59 let s = cfg.section("nexus-ebics"); 60 Ok(Self { 61 bank: s.path("bank_public_keys_file").require()?, 62 client: s.path("client_private_keys_file").require()?, 63 }) 64 } 65 66 pub fn ebics<'a>(&'a self) -> EbicsKeysCfg<'a> { 67 EbicsKeysCfg { 68 bank: self.bank.as_str(), 69 client: self.client.as_str(), 70 } 71 } 72 } 73 74 #[derive(Clone)] 75 pub struct NexusHostCfg { 76 pub base_url: url::Url, 77 pub unix_path: Option<String>, 78 pub host_id: CompactString, 79 pub user_id: CompactString, 80 pub partner_id: CompactString, 81 } 82 83 impl NexusHostCfg { 84 pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { 85 let s = cfg.section("nexus-ebics"); 86 Ok(Self { 87 base_url: s.url("host_base_url").require()?, 88 unix_path: s.path("UNIXPATH").opt()?, 89 host_id: s.cstr("host_id").require()?, 90 user_id: s.cstr("user_id").require()?, 91 partner_id: s.cstr("partner_id").require()?, 92 }) 93 } 94 95 pub fn ebics<'a>(&'a self) -> EbicsHostCfg<'a> { 96 EbicsHostCfg { 97 base_url: self.base_url.as_str(), 98 unix_path: self.unix_path.as_deref(), 99 host_id: &self.host_id, 100 user_id: &self.user_id, 101 partner_id: &self.partner_id, 102 } 103 } 104 } 105 106 #[derive(Debug, Clone, Copy, EnumMeta)] 107 #[enum_meta(Str)] 108 #[allow(non_camel_case_types)] 109 pub enum AccountType { 110 exchange, 111 normal, 112 } 113 114 pub struct NexusIngestCfg { 115 pub account_type: AccountType, 116 pub ignore_txs_before: Timestamp, 117 pub ignore_bounces_before: Timestamp, 118 pub restriction_payto_regex: Option<Regex>, 119 pub bounce_deduce_fee: bool, 120 pub bounce_fee: Amount, 121 pub currency: Currency, 122 } 123 124 impl NexusIngestCfg { 125 pub const fn simple(account_type: AccountType, currency: &Currency) -> Self { 126 Self { 127 account_type, 128 ignore_txs_before: Timestamp::UNIX_EPOCH, 129 ignore_bounces_before: Timestamp::UNIX_EPOCH, 130 restriction_payto_regex: None, 131 bounce_deduce_fee: false, 132 bounce_fee: Amount::zero(currency), 133 currency: Currency::KUDOS, 134 } 135 } 136 } 137 138 pub struct NexusFetchCfg { 139 pub frequency: Duration, 140 pub frequency_raw: String, 141 pub checkpoint_time: Time, 142 pub ignore_txs_before: Timestamp, 143 pub ignore_bounces_before: Timestamp, 144 pub restriction_payto_regex: Option<Regex>, 145 pub bounce_deduce_fee: bool, 146 pub bounce_fee: Amount, 147 } 148 149 impl NexusFetchCfg { 150 pub fn parse(cfg: &Config, currency: &Currency) -> Result<Self, CfgErr> { 151 let s = cfg.section("nexus-fetch"); 152 153 Ok(Self { 154 frequency: s.duration("frequency").require()?, 155 frequency_raw: s.str("frequency").require()?, 156 checkpoint_time: s.time("checkpoint_time_of_day").require()?, 157 ignore_txs_before: date_to_utc_ts( 158 &s.date("ignore_transactions_before").default(Date::ZERO)?, 159 ), 160 ignore_bounces_before: date_to_utc_ts( 161 &s.date("ignore_bounces_before").default(Date::ZERO)?, 162 ), 163 restriction_payto_regex: s.regex("restriction_payto_regex").opt()?, 164 bounce_deduce_fee: s.boolean("bounce_deduce_fee").default(false)?, 165 bounce_fee: s 166 .amount("bounce_fee", currency) 167 .default(Amount::zero(currency))?, 168 }) 169 } 170 } 171 172 pub struct NexusSubmitCfg { 173 pub frequency: Duration, 174 pub frequency_raw: String, 175 pub require_ack: bool, 176 } 177 178 impl NexusSubmitCfg { 179 pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { 180 let s = cfg.section("nexus-submit"); 181 182 Ok(Self { 183 frequency: s.duration("frequency").require()?, 184 frequency_raw: s.str("frequency").require()?, 185 require_ack: s.boolean("manual_ack").default(false)?, 186 }) 187 } 188 } 189 190 #[derive(Debug, Clone)] 191 pub struct NexusEbicsConfig { 192 pub account: FullIbanPayto, 193 pub qr_iban: Option<IBAN>, 194 pub dialect: Dialect, 195 } 196 197 impl NexusEbicsConfig { 198 pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { 199 let s = cfg.section("nexus-ebics"); 200 Ok(Self { 201 account: FullIbanPayto::new( 202 BankID { 203 iban: s.parse("IBAN", "iban").require()?, 204 bic: Some(s.parse("BIC", "bic").require()?), 205 }, 206 &s.str("name").require()?, 207 ), 208 dialect: s.parse("bank dialect", "bank_dialect").require()?, 209 qr_iban: s.parse("IBAN", "qr_iban").opt()?, 210 }) 211 } 212 } 213 214 pub struct NexusSetupConfig { 215 pub enc: Option<Vec<u8>>, 216 pub auth: Option<Vec<u8>>, 217 } 218 219 impl NexusSetupConfig { 220 pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { 221 let s = cfg.section("nexus-setup"); 222 Ok(Self { 223 enc: s.hex("bank_encryption_pub_key_hash").opt()?, 224 auth: s.hex("bank_authentication_pub_key_hash").opt()?, 225 }) 226 } 227 } 228 229 pub struct NexusCfg { 230 pub cfg: Config, 231 pub currency: Currency, 232 pub account_type: AccountType, 233 pub keys: OnceCell<NexusKeysCfg>, 234 pub host: OnceCell<NexusHostCfg>, 235 pub fetch: OnceCell<NexusFetchCfg>, 236 pub submit: OnceCell<NexusSubmitCfg>, 237 pub ebics: OnceCell<NexusEbicsConfig>, 238 pub setup: OnceCell<NexusSetupConfig>, 239 pub wire_cfg: Option<ApiCfg>, 240 pub revenue_cfg: Option<ApiCfg>, 241 pub observability_cfg: Option<ApiCfg>, 242 pub db_cfg: DbCfg, 243 pub serve_cfg: Serve, 244 } 245 246 impl<'a> NexusCfg { 247 pub fn parse(cfg: &Config) -> Result<Self, CfgErr> { 248 let s = cfg.section("nexus-ebics"); 249 Ok(Self { 250 currency: s.currency("currency").require()?, 251 account_type: s.parse("account type", "ACCOUNT_TYPE").require()?, 252 wire_cfg: ApiCfg::parse(cfg.section("nexus-httpd-wire-gateway-api"))?, 253 revenue_cfg: ApiCfg::parse(cfg.section("nexus-httpd-revenue-api"))?, 254 observability_cfg: ApiCfg::parse(cfg.section("nexus-httpd-observability-api"))?, 255 serve_cfg: Serve::parse(&cfg.section("nexus-httpd"))?, 256 keys: OnceCell::new(), 257 host: OnceCell::new(), 258 fetch: OnceCell::new(), 259 submit: OnceCell::new(), 260 ebics: OnceCell::new(), 261 setup: OnceCell::new(), 262 db_cfg: parse_db_cfg(cfg)?, 263 cfg: cfg.clone(), 264 }) 265 } 266 267 pub fn keys(&self) -> Result<&NexusKeysCfg, CfgErr> { 268 // TODO use get_or_try_init when stable 269 if let Some(keys) = self.keys.get() { 270 return Ok(keys); 271 } 272 let keys = NexusKeysCfg::parse(&self.cfg)?; 273 self.keys.set(keys).ok(); 274 Ok(self.keys.get().unwrap()) 275 } 276 277 pub fn host(&self) -> Result<&NexusHostCfg, CfgErr> { 278 // TODO use get_or_try_init when stable 279 if let Some(host) = self.host.get() { 280 return Ok(host); 281 } 282 let host = NexusHostCfg::parse(&self.cfg)?; 283 self.host.set(host).ok(); 284 Ok(self.host.get().unwrap()) 285 } 286 287 pub fn fetch(&self) -> Result<&NexusFetchCfg, CfgErr> { 288 // TODO use get_or_try_init when stable 289 if let Some(fetch) = self.fetch.get() { 290 return Ok(fetch); 291 } 292 let fetch = NexusFetchCfg::parse(&self.cfg, &self.currency)?; 293 self.fetch.set(fetch).ok(); 294 Ok(self.fetch.get().unwrap()) 295 } 296 297 pub fn submit(&self) -> Result<&NexusSubmitCfg, CfgErr> { 298 // TODO use get_or_try_init when stable 299 if let Some(submit) = self.submit.get() { 300 return Ok(submit); 301 } 302 let submit = NexusSubmitCfg::parse(&self.cfg)?; 303 self.submit.set(submit).ok(); 304 Ok(self.submit.get().unwrap()) 305 } 306 307 pub fn ebics(&self) -> Result<&NexusEbicsConfig, CfgErr> { 308 // TODO use get_or_try_init when stable 309 if let Some(ebics) = self.ebics.get() { 310 return Ok(ebics); 311 } 312 let ebics = NexusEbicsConfig::parse(&self.cfg)?; 313 self.ebics.set(ebics).ok(); 314 Ok(self.ebics.get().unwrap()) 315 } 316 317 pub fn setup(&self) -> Result<&NexusSetupConfig, CfgErr> { 318 // TODO use get_or_try_init when stable 319 if let Some(setup) = self.setup.get() { 320 return Ok(setup); 321 } 322 let setup = NexusSetupConfig::parse(&self.cfg)?; 323 self.setup.set(setup).ok(); 324 Ok(self.setup.get().unwrap()) 325 } 326 327 pub fn ingest(&self) -> Result<NexusIngestCfg, CfgErr> { 328 let fetch = self.fetch()?; 329 Ok(NexusIngestCfg { 330 account_type: self.account_type, 331 ignore_txs_before: fetch.ignore_txs_before, 332 ignore_bounces_before: fetch.ignore_bounces_before, 333 restriction_payto_regex: fetch.restriction_payto_regex.clone(), 334 bounce_deduce_fee: fetch.bounce_deduce_fee, 335 bounce_fee: fetch.bounce_fee, 336 currency: self.currency, 337 }) 338 } 339 340 pub fn ebics_setup(&'a self) -> Result<EbicsSetupCfg<'a>, CfgErr> { 341 let setup = self.setup()?; 342 Ok(EbicsSetupCfg { 343 keys: self.keys()?.ebics(), 344 host: self.host()?.ebics(), 345 enc: setup.enc.as_deref(), 346 auth: setup.auth.as_deref(), 347 }) 348 } 349 350 pub fn expect_full_keys(&self) -> Result<(ClientKeys, BankKeys), CfgErr> { 351 libeufin_ebics::keys::expect_full_keys(&self.keys()?.ebics(), "libeufin-nexus ebics-setup") 352 } 353 }