Config.kt (7313B)
1 /* 2 * This file is part of LibEuFin. 3 * Copyright (C) 2024, 2025, 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 package tech.libeufin.nexus 21 22 import tech.libeufin.common.* 23 import tech.libeufin.common.db.DatabaseConfig 24 import tech.libeufin.nexus.db.Database 25 import tech.libeufin.ebics.EbicsKeysConfig 26 import tech.libeufin.ebics.EbicsSetupConfig 27 import tech.libeufin.ebics.EbicsHostConfig 28 import java.nio.file.Path 29 import java.time.Instant 30 import org.slf4j.Logger 31 import org.slf4j.LoggerFactory 32 33 private val logger: Logger = LoggerFactory.getLogger("libeufin-config") 34 35 val NEXUS_CONFIG_SOURCE = ConfigSource("libeufin", "libeufin-nexus", "libeufin-nexus") 36 37 data class NexusIngestConfig( 38 val accountType: AccountType, 39 val ignoreTransactionsBefore: Instant, 40 val ignoreBouncesBefore: Instant, 41 val restrictionPaytoRegex: Regex?, 42 val bounceDeduceFee: Boolean, 43 val bounceFee: TalerAmount 44 ) { 45 companion object { 46 fun default(accountType: AccountType, currency: String = "KUDOS") 47 = NexusIngestConfig(accountType, Instant.MIN, Instant.MIN, null, false, TalerAmount.zero(currency)) 48 } 49 } 50 51 class NexusFetchConfig(config: TalerConfig, currency: String) { 52 private val section = config.section("nexus-fetch") 53 val frequency = section.duration("frequency").require() 54 val frequencyRaw = section.string("frequency").require() 55 val checkpointTime = section.time("checkpoint_time_of_day").require() 56 val ignoreTransactionsBefore = section.date("ignore_transactions_before").default(Instant.MIN) 57 val ignoreBouncesBefore = section.date("ignore_bounces_before").default(Instant.MIN) 58 val restrictionPaytoRegex = section.regex("restriction_payto_regex").orNull() 59 val bounceDeduceFee = section.boolean("bounce_deduce_fee").default(false) 60 val bounceFee = section.amount("bounce_fee", currency).default(TalerAmount.zero(currency)) 61 } 62 63 class NexusSubmitConfig(config: TalerConfig) { 64 private val section = config.section("nexus-submit") 65 val frequency = section.duration("frequency").require() 66 val frequencyRaw = section.string("frequency").require() 67 val requireAck = section.boolean("manual_ack").default(false) 68 } 69 70 class NexusSetupConfig(config: TalerConfig): EbicsSetupConfig { 71 private val section = config.section("nexus-setup") 72 override val bankAuthPubKey = section.hex("bank_authentication_pub_key_hash").orNull() 73 override val bankEncPubKey = section.hex("bank_encryption_pub_key_hash").orNull() 74 } 75 76 class NexusHostConfig(sect: TalerConfigSection): EbicsHostConfig { 77 /** The bank base URL */ 78 override val baseUrl = sect.string("host_base_url").require() 79 /** The bank EBICS host ID */ 80 override val hostId = sect.string("host_id").require() 81 /** EBICS user ID */ 82 override val userId = sect.string("user_id").require() 83 /** EBICS partner ID */ 84 override val partnerId = sect.string("partner_id").require() 85 } 86 87 class NexusEbicsConfig( 88 sect: TalerConfigSection, 89 ): EbicsKeysConfig { 90 val host by lazy { NexusHostConfig(sect) } 91 /** Bank account metadata */ 92 val account = IbanAccountMetadata( 93 iban = sect.iban("iban").require(), 94 bic = sect.string("bic").require(), 95 name = sect.string("name").require() 96 ) 97 val qrIban = sect.iban("qr_iban").orNull() 98 /** Bank account payto */ 99 val payto = IbanPayto.build(account.iban.toString(), account.bic, account.name) 100 101 val dialect = sect.map("bank_dialect", "bank dialect", mapOf( 102 "postfinance" to Dialect.postfinance, 103 "gls" to Dialect.gls, 104 "maerki_baumann" to Dialect.maerki_baumann, 105 "valiant" to Dialect.valiant, 106 "raiffeisen" to Dialect.raiffeisen, 107 )).require() 108 109 /** Path where we store the bank public keys */ 110 override val bankPublicKeysPath = sect.path("bank_public_keys_file").require() 111 /** Path where we store our private keys */ 112 override val clientPrivateKeysPath = sect.path("client_private_keys_file").require() 113 } 114 115 class ApiConfig(section: TalerConfigSection) { 116 val authMethod = section.requireAuthMethod() 117 } 118 119 /** Configuration for libeufin-nexus */ 120 class NexusConfig internal constructor (val cfg: TalerConfig) { 121 private val sect = cfg.section("nexus-ebics") 122 123 val dbCfg by lazy { cfg.dbConfig() } 124 val serverCfg by lazy { 125 cfg.loadServerConfig("nexus-httpd") 126 } 127 128 /** The bank's currency */ 129 val currency = sect.string("currency").require() 130 131 val accountType = sect.map("account_type", "account type", mapOf( 132 "normal" to AccountType.normal, 133 "exchange" to AccountType.exchange 134 )).require() 135 136 val fetch by lazy { NexusFetchConfig(cfg, currency) } 137 val submit by lazy { NexusSubmitConfig(cfg) } 138 val ebics by lazy { NexusEbicsConfig(sect) } 139 val setup by lazy { NexusSetupConfig(cfg) } 140 141 val ingest get() = NexusIngestConfig( 142 accountType, 143 fetch.ignoreTransactionsBefore, 144 fetch.ignoreBouncesBefore, 145 fetch.restrictionPaytoRegex, 146 fetch.bounceDeduceFee, 147 fetch.bounceFee 148 ) 149 150 val wireGatewayApiCfg = cfg.section("nexus-httpd-wire-gateway-api").apiConf() 151 val revenueApiCfg = cfg.section("nexus-httpd-revenue-api").apiConf() 152 val observabilityApiCfg = cfg.section("nexus-httpd-observability-api").apiConf() 153 } 154 155 fun NexusConfig.checkCurrency(amount: TalerAmount) { 156 if (amount.currency != currency) throw badRequest( 157 "Wrong currency: expected $currency got ${amount.currency}", 158 TalerErrorCode.GENERIC_CURRENCY_MISMATCH 159 ) 160 } 161 162 private fun TalerConfigSection.apiConf(): ApiConfig? { 163 val enabled = boolean("enabled").require() 164 return if (enabled) { 165 return ApiConfig(this) 166 } else { 167 null 168 } 169 } 170 171 enum class AccountType { 172 normal, 173 exchange 174 } 175 176 private fun TalerConfig.dbConfig(): DatabaseConfig { 177 val sect = section("libeufin-nexusdb-postgres") 178 val configOption = sect.string("config") 179 return DatabaseConfig( 180 dbConnStr = configOption.orNull() ?: section("nexus-postgres").string("config").orNull() ?: configOption.require(), 181 sqlDir = sect.path("sql_dir").require() 182 ) 183 } 184 185 /** Load nexus config at [configPath] */ 186 fun nexusConfig(configPath: Path?): NexusConfig { 187 val config = NEXUS_CONFIG_SOURCE.fromFile(configPath) 188 return NexusConfig(config) 189 } 190 191 /** Load nexus db config at [configPath] */ 192 fun dbConfig(configPath: Path?): DatabaseConfig = 193 NEXUS_CONFIG_SOURCE.fromFile(configPath).dbConfig() 194 195 /** Run [lambda] with access to a database conn pool */ 196 suspend fun NexusConfig.withDb(lambda: suspend (Database, NexusConfig) -> Unit) { 197 Database(dbCfg, currency).use { lambda(it, this) } 198 }