libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit 0a39bbc41c5502dd1305c41ed7e28e174223b0e9
parent e06c066d23e4d9434f488e64480b168f524005f1
Author: Antoine A <>
Date:   Thu, 11 Sep 2025 14:57:31 +0200

bank: make base_url mandatory

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Config.kt | 38+++++++++++++-------------------------
Mbank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt | 4++--
Mbank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 21+++++----------------
Mbank/src/test/kotlin/BankIntegrationApiTest.kt | 4++--
Mbank/src/test/kotlin/CoreBankApiTest.kt | 2+-
Mbank/src/test/kotlin/PaytoTest.kt | 12++++++------
Mcommon/src/main/kotlin/Constants.kt | 1-
Mcommon/src/main/kotlin/TalerCommon.kt | 7+++----
Mcommon/src/test/kotlin/PaytoTest.kt | 3++-
Mcontrib/bank.conf | 5+++--
11 files changed, 38 insertions(+), 61 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -30,7 +30,7 @@ import java.time.Duration data class BankConfig( private val cfg: TalerConfig, val name: String, - val baseUrl: BaseURL?, + val baseUrl: BaseURL, val regionalCurrency: String, val regionalCurrencySpec: CurrencySpecification, val wireTransferFees: TalerAmount, @@ -125,36 +125,24 @@ private fun TalerConfig.loadBankConfig(): BankConfig = section("libeufin-bank"). } } - val (baseUrl, hostname) = run { - val baseUrl = baseURL("base_url").orNull() - val hostname = string("x_taler_bank_payto_hostname").orNull() - if (baseUrl != null && hostname != null) { - if (baseUrl.url.host != hostname) { - throw TalerConfigError.generic("x_taler_bank_payto_hostname must match base_url hostname when both are specified") - } else { - Pair(baseUrl, hostname) - } - } else if (baseUrl != null) { - Pair(baseUrl, baseUrl.url.host) - } else { - Pair(baseUrl, null) - } - } + val baseUrl = baseURL("base_url").require() + val hostname = baseUrl.url.host val method = map("wire_type", "payment target type", mapOf( "iban" to WireMethod.IBAN, "x-taler-bank" to WireMethod.X_TALER_BANK, )).default(WireMethod.IBAN, logger, " defaulting to 'iban' but will fail in a future update") - val payto = when (method) { - WireMethod.IBAN -> BankPaytoCtx( - bic = string("iban_payto_bic").orNull(logger, " will fail in a future update"), - hostname = hostname - ) - WireMethod.X_TALER_BANK -> BankPaytoCtx( - bic = string("iban_payto_bic").orNull(), - hostname = hostname ?: string("x_taler_bank_payto_hostname").orNull(logger, " will fail in a future update") - ) + + if (method == WireMethod.X_TALER_BANK) { + val xhostname = string("x_taler_bank_payto_hostname").orNull() + if (xhostname != null && xhostname != hostname) { + logger.warn("deprecated x_taler_bank_payto_hostname '${xhostname}' does not match base_url hostname '$hostname'") + } } + val payto = BankPaytoCtx( + bic = string("iban_payto_bic").orNull(), + hostname = hostname + ) val pwCrypto = map("pwd_hash_algorithm", "password hash algorithm", mapOf( "bcrypt" to json<PwCrypto.Bcrypt>("pwd_hash_config", "bcrypt JSON config").require() diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/BankIntegrationApi.kt @@ -58,7 +58,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { ) call.respond(op.copy( suggested_exchange = ctx.suggestedWithdrawalExchange, - confirm_transfer_url = if (op.status == WithdrawalStatus.pending || op.status == WithdrawalStatus.selected) call.request.withdrawConfirmUrl(ctx.baseUrl, uuid) else null + confirm_transfer_url = if (op.status == WithdrawalStatus.pending || op.status == WithdrawalStatus.selected) ctx.withdrawConfirmUrl(uuid) else null )) } post("/taler-integration/withdrawal-operation/{wopid}") { @@ -113,7 +113,7 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { call.respond(BankWithdrawalOperationPostResponse( transfer_done = res.status == WithdrawalStatus.confirmed, status = res.status, - confirm_transfer_url = if (res.status == WithdrawalStatus.pending || res.status == WithdrawalStatus.selected) call.request.withdrawConfirmUrl(ctx.baseUrl, uuid) else null + confirm_transfer_url = if (res.status == WithdrawalStatus.pending || res.status == WithdrawalStatus.selected) ctx.withdrawConfirmUrl(uuid) else null )) } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt @@ -574,7 +574,7 @@ private fun Routing.coreBankWithdrawalApi(db: Database, cfg: BankConfig) { call.respond( BankAccountCreateWithdrawalResponse( withdrawal_id = opId.toString(), - taler_withdraw_uri = call.request.talerWithdrawUri(cfg.baseUrl, opId) + taler_withdraw_uri = cfg.talerWithdrawUri(opId) ) ) } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt @@ -37,6 +37,7 @@ import tech.libeufin.bank.auth.pathUsername import tech.libeufin.bank.auth.AUTH_INFO import tech.libeufin.bank.db.AccountDAO.AccountCreationResult import tech.libeufin.bank.db.Database +import tech.libeufin.bank.BankConfig import tech.libeufin.common.* import tech.libeufin.common.api.intercept import java.util.* @@ -67,18 +68,6 @@ suspend fun ApplicationCall.bankInfo(db: Database): BankInfo { return info } -private fun ApplicationRequest.fallbackBase() = BaseURL.parse(url { - protocol = URLProtocol( - name = origin.scheme, - defaultPort = -1 - ) - host = "${origin.serverHost}:${origin.serverPort}" - headers[X_FORWARD_PREFIX]?.let { - appendPathSegments(it) - } - appendPathSegments("") -}) - /** * Builds the taler://withdraw-URI. Such URI will serve the requests * from wallets, when they need to manage the operation. For example, @@ -87,15 +76,15 @@ private fun ApplicationRequest.fallbackBase() = BaseURL.parse(url { * * https://$BANK_URL/taler-integration */ -fun ApplicationRequest.talerWithdrawUri(baseUrl: BaseURL?, id: UUID): String { - val base = (baseUrl ?: fallbackBase()).url +fun BankConfig.talerWithdrawUri(id: UUID): String { + val base = this.baseUrl.url val protocol = if (base.protocol == "http") "taler+http" else "taler" val port = if (base.port != -1) ":${base.port}" else "" return "${protocol}://withdraw/${base.host}${port}${base.path}taler-integration/${id}" } -fun ApplicationRequest.withdrawConfirmUrl(baseUrl: BaseURL?, id: UUID): String { - val base = (baseUrl ?: fallbackBase()).url +fun BankConfig.withdrawConfirmUrl(id: UUID): String { + val base = this.baseUrl.url return "${base}webui/#/operation/${id}" } diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt b/bank/src/test/kotlin/BankIntegrationApiTest.kt @@ -111,14 +111,14 @@ class BankIntegrationApiTest { json(req) }.assertOkJson<BankWithdrawalOperationPostResponse> { assertEquals(WithdrawalStatus.selected, it.status) - assertEquals("http://localhost:80/webui/#/operation/$uuid", it.confirm_transfer_url) + assertEquals("http://localhost:8080/webui/#/operation/$uuid", it.confirm_transfer_url) } // Check idempotence client.post("/taler-integration/withdrawal-operation/$uuid") { json(req) }.assertOkJson<BankWithdrawalOperationPostResponse> { assertEquals(WithdrawalStatus.selected, it.status) - assertEquals("http://localhost:80/webui/#/operation/$uuid", it.confirm_transfer_url) + assertEquals("http://localhost:8080/webui/#/operation/$uuid", it.confirm_transfer_url) } // Check already selected client.post("/taler-integration/withdrawal-operation/$uuid") { diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -1526,7 +1526,7 @@ class CoreBankWithdrawalApiTest { client.postA("/accounts/merchant/withdrawals") { json(valid) }.assertOkJson<BankAccountCreateWithdrawalResponse> { - assertEquals("taler+http://withdraw/localhost:80/taler-integration/${it.withdrawal_id}", it.taler_withdraw_uri) + assertEquals("taler+http://withdraw/localhost:8080/taler-integration/${it.withdrawal_id}", it.taler_withdraw_uri) } } diff --git a/bank/src/test/kotlin/PaytoTest.kt b/bank/src/test/kotlin/PaytoTest.kt @@ -38,7 +38,7 @@ class PaytoTest { "name" to "John" } }.assertOkJson<RegisterAccountResponse> { - assertEquals("payto://x-taler-bank/bank.hostname.test/john?receiver-name=John", it.internal_payto_uri) + assertEquals("payto://x-taler-bank/localhost/john?receiver-name=John", it.internal_payto_uri) } // Bad IBAN payto @@ -56,7 +56,7 @@ class PaytoTest { "username" to "foo" "password" to "foo-password" "name" to "Jane" - "payto_uri" to "payto://x-taler-bank/bank.hostname.test/not-foo" + "payto_uri" to "payto://x-taler-bank/localhost/not-foo" } }.assertBadRequest() // Check Ok @@ -65,10 +65,10 @@ class PaytoTest { "username" to "foo" "password" to "foo-password" "name" to "Jane" - "payto_uri" to "payto://x-taler-bank/bank.hostname.test/foo" + "payto_uri" to "payto://x-taler-bank/localhost/foo" } }.assertOkJson<RegisterAccountResponse> { - assertEquals("payto://x-taler-bank/bank.hostname.test/foo?receiver-name=Jane", it.internal_payto_uri) + assertEquals("payto://x-taler-bank/localhost/foo?receiver-name=Jane", it.internal_payto_uri) } // Check payto canonicalisation @@ -80,8 +80,8 @@ class PaytoTest { client.getA("/accounts/john/transactions/${it.row_id}") .assertOkJson<BankAccountTransactionInfo> { tx -> assertEquals("payout", tx.subject) - assertEquals("payto://x-taler-bank/bank.hostname.test/foo?receiver-name=Jane", tx.creditor_payto_uri) - assertEquals("payto://x-taler-bank/bank.hostname.test/john?receiver-name=John", tx.debtor_payto_uri) + assertEquals("payto://x-taler-bank/localhost/foo?receiver-name=Jane", tx.creditor_payto_uri) + assertEquals("payto://x-taler-bank/localhost/john?receiver-name=John", tx.debtor_payto_uri) assertEquals(TalerAmount("KUDOS:0.3"), tx.amount) } } diff --git a/common/src/main/kotlin/Constants.kt b/common/src/main/kotlin/Constants.kt @@ -31,7 +31,6 @@ const val REVENUE_API_VERSION: String = "1:1:1" // HTTP headers const val X_CHALLENGE_ID: String = "X-Challenge-Id" -const val X_FORWARD_PREFIX: String = "X-Forward-Prefix" // Params const val MAX_PAGE_SIZE: Int = 1024 diff --git a/common/src/main/kotlin/TalerCommon.kt b/common/src/main/kotlin/TalerCommon.kt @@ -313,9 +313,8 @@ sealed class Payto { fun bank(name: String?, ctx: BankPaytoCtx): String = when (this) { is IbanPayto -> IbanPayto.build(iban.toString(), ctx.bic, name) is XTalerBankPayto -> { - val domain = ctx.hostname ?: "localhost" val name = if (name != null) "?receiver-name=${name.encodeURLParameter()}" else "" - "payto://x-taler-bank/$domain/$username$name" + "payto://x-taler-bank/${ctx.hostname}/$username$name" } } @@ -476,8 +475,8 @@ class XTalerBankPayto internal constructor( /** Context specific data necessary to create a bank payto URI from a canonical payto URI */ data class BankPaytoCtx( - val bic: String? = null, - val hostname: String? = null + val bic: String?, + val hostname: String ) diff --git a/common/src/test/kotlin/PaytoTest.kt b/common/src/test/kotlin/PaytoTest.kt @@ -56,7 +56,8 @@ class PaytoTest { @Test fun forms() { val ctx = BankPaytoCtx( - bic = "TESTBIC" + bic = "TESTBIC", + hostname = "test.com" ) val canonical = "payto://iban/CH9300762011623852957" val bank = "payto://iban/TESTBIC/CH9300762011623852957?receiver-name=Name" diff --git a/contrib/bank.conf b/contrib/bank.conf @@ -6,13 +6,14 @@ CURRENCY = # Supported payment target type, this can either be iban or x-taler-bank WIRE_TYPE = -# The advertised base URL -# BASE_URL = +# The bank base URL +BASE_URL = http://localhost:8080/ # Bank BIC used in generated iban payto URI. Required if WIRE_TYPE = iban # IBAN_PAYTO_BIC = # Bank hostname used in generated x-taler-bank payto URI. Required if WIRE_TYPE = x-taler-bank +# Deprecated, BASE_URL hostname is used instead # X_TALER_BANK_PAYTO_HOSTNAME = bank.$FOO.taler.net # Bank display name, used in webui and TAN messages. Default is "Taler Bank"