diff options
author | Antoine A <> | 2024-02-07 19:02:27 +0100 |
---|---|---|
committer | Antoine A <> | 2024-02-07 19:02:27 +0100 |
commit | 4ddcada57dc252c4ccdb79d573228e25bc2811ed (patch) | |
tree | c920b9f61ec17231d06308844f26b16da6429157 /bank | |
parent | e3a62fa46e75cff879b59725165490943d0d0c1f (diff) | |
download | libeufin-4ddcada57dc252c4ccdb79d573228e25bc2811ed.tar.gz libeufin-4ddcada57dc252c4ccdb79d573228e25bc2811ed.tar.bz2 libeufin-4ddcada57dc252c4ccdb79d573228e25bc2811ed.zip |
Generate withdraw URI using request base url
Diffstat (limited to 'bank')
-rw-r--r-- | bank/build.gradle | 1 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt | 27 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Config.kt | 8 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 4 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 2 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 37 | ||||
-rw-r--r-- | bank/src/test/kotlin/BankIntegrationApiTest.kt | 32 | ||||
-rw-r--r-- | bank/src/test/kotlin/CoreBankApiTest.kt | 4 |
8 files changed, 34 insertions, 81 deletions
diff --git a/bank/build.gradle b/bank/build.gradle index 1e9558ba..75f46bf8 100644 --- a/bank/build.gradle +++ b/bank/build.gradle @@ -33,6 +33,7 @@ dependencies { implementation("io.ktor:ktor-server-status-pages:$ktor_version") implementation("io.ktor:ktor-server-netty:$ktor_version") implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version") + implementation("io.ktor:ktor-server-forwarded-header:$ktor_version") // UNIX domain sockets support (used to connect to PostgreSQL) implementation("com.kohlschutter.junixsocket:junixsocket-core:$junixsocket_version") diff --git a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt index 3440bd54..757fc0b3 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/BankIntegrationApi.kt @@ -49,24 +49,19 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { ) call.respond(op.copy( suggested_exchange = ctx.suggestedWithdrawalExchange, - confirm_transfer_url = ctx.spaCaptchaURL?.run { - getWithdrawalConfirmUrl( - baseUrl = this, - wopId = uuid - ) - } + confirm_transfer_url = if (op.status == WithdrawalStatus.selected) call.request.withdrawConfirmUrl(uuid) else null )) } post("/taler-integration/withdrawal-operation/{wopid}") { - val opId = call.uuidParameter("wopid") + val uuid = call.uuidParameter("wopid") val req = call.receive<BankWithdrawalOperationPostRequest>() val res = db.withdrawal.setDetails( - opId, req.selected_exchange, req.reserve_pub + uuid, req.selected_exchange, req.reserve_pub ) when (res) { is WithdrawalSelectionResult.UnknownOperation -> throw notFound( - "Withdrawal operation $opId not found", + "Withdrawal operation '$uuid' not found", TalerErrorCode.BANK_TRANSACTION_NOT_FOUND ) is WithdrawalSelectionResult.AlreadySelected -> throw conflict( @@ -86,25 +81,19 @@ fun Routing.bankIntegrationApi(db: Database, ctx: BankConfig) { TalerErrorCode.BANK_ACCOUNT_IS_NOT_EXCHANGE ) is WithdrawalSelectionResult.Success -> { - val confirmUrl: String? = if (ctx.spaCaptchaURL !== null && res.status == WithdrawalStatus.selected) { - getWithdrawalConfirmUrl( - baseUrl = ctx.spaCaptchaURL, - wopId = opId - ) - } else null call.respond(BankWithdrawalOperationPostResponse( transfer_done = res.status == WithdrawalStatus.confirmed, status = res.status, - confirm_transfer_url = confirmUrl + confirm_transfer_url = if (res.status == WithdrawalStatus.selected) call.request.withdrawConfirmUrl(uuid) else null )) } } } post("/taler-integration/withdrawal-operation/{wopid}/abort") { - val opId = call.uuidParameter("wopid") - when (db.withdrawal.abort(opId)) { + val uuid = call.uuidParameter("wopid") + when (db.withdrawal.abort(uuid)) { AbortResult.UnknownOperation -> throw notFound( - "Withdrawal operation $opId not found", + "Withdrawal operation '$uuid' not found", TalerErrorCode.BANK_TRANSACTION_NOT_FOUND ) AbortResult.AlreadyConfirmed -> throw conflict( diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt index 8ec6bc8d..2c15f43b 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Config.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Config.kt @@ -42,13 +42,6 @@ data class BankConfig( val defaultDebtLimit: TalerAmount, val registrationBonus: TalerAmount, val suggestedWithdrawalExchange: String?, - /** - * URL where the user should be redirected to complete the captcha. - * It can contain the substring "{woid}" that is going to be replaced - * with the withdrawal operation id and should point where the bank - * SPA is located. - */ - val spaCaptchaURL: String?, val allowConversion: Boolean, val fiatCurrency: String?, val fiatCurrencySpec: CurrencySpecification?, @@ -142,7 +135,6 @@ fun TalerConfig.loadBankConfig(): BankConfig { defaultDebtLimit = amount("libeufin-bank", "default_debt_limit", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), registrationBonus = amount("libeufin-bank", "registration_bonus", regionalCurrency) ?: TalerAmount(0, 0, regionalCurrency), suggestedWithdrawalExchange = lookupString("libeufin-bank", "suggested_withdrawal_exchange"), - spaCaptchaURL = lookupString("libeufin-bank", "spa_captcha_url"), spaPath = lookupPath("libeufin-bank", "spa"), allowConversion = allowConversion, fiatCurrency = fiatCurrency, diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt index f1c5cc94..6dab5571 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -503,12 +503,10 @@ private fun Routing.coreBankWithdrawalApi(db: Database, ctx: BankConfig) { TalerErrorCode.BANK_UNALLOWED_DEBIT ) WithdrawalCreationResult.Success -> { - val bankBaseUrl = call.request.getBaseUrl() - ?: throw internalServerError("Bank could not find its own base URL") call.respond( BankAccountCreateWithdrawalResponse( withdrawal_id = opId.toString(), - taler_withdraw_uri = getTalerWithdrawUri(bankBaseUrl, opId.toString()) + taler_withdraw_uri = call.request.talerWithdrawUri(opId) ) ) } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt index cee9d822..65efa43b 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -36,6 +36,7 @@ import io.ktor.server.plugins.callloging.* import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.cors.routing.* import io.ktor.server.plugins.statuspages.* +import io.ktor.server.plugins.forwardedheaders.* import io.ktor.server.request.* import io.ktor.server.response.* import io.ktor.server.routing.* @@ -120,6 +121,7 @@ fun Application.corebankWebApp(db: Database, ctx: BankConfig) { } } } + install(XForwardedHeaders) install(CORS) { anyHost() allowHeader(HttpHeaders.Authorization) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt index 24dc4168..e0ea90d5 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt @@ -59,32 +59,29 @@ suspend fun ApplicationCall.bankInfo(db: Database, ctx: BankPaytoCtx): BankInfo * * https://$BANK_URL/taler-integration */ -fun getTalerWithdrawUri(baseUrl: String, woId: String) = url { - val baseUrlObj = URI(baseUrl).toURL() +fun ApplicationRequest.talerWithdrawUri(id: UUID) = url { protocol = URLProtocol( - name = "taler".plus(if (baseUrlObj.protocol.lowercase() == "http") "+http" else ""), defaultPort = -1 + name = if (origin.scheme == "http") "taler+http" else "taler", + defaultPort = -1 ) host = "withdraw" - val pathSegments = mutableListOf( - // adds the hostname(+port) of the actual bank that will serve the withdrawal request. - baseUrlObj.host.plus( - if (baseUrlObj.port != -1) ":${baseUrlObj.port}" - else "" - ) - ) - // Removing potential double slashes. - baseUrlObj.path.split("/").forEach { - if (it.isNotEmpty()) pathSegments.add(it) + appendPathSegments(origin.serverHost) + headers["X-Forward-Prefix"]?.let { + appendPathSegments(it) } - pathSegments.add("taler-integration/${woId}") - this.appendPathSegments(pathSegments) + appendPathSegments("taler-integration", id.toString()) } -// Builds a withdrawal confirm URL. -fun getWithdrawalConfirmUrl( - baseUrl: String, wopId: UUID -): String { - return baseUrl.replace("{woid}", wopId.toString()) +fun ApplicationRequest.withdrawConfirmUrl(id: UUID) = url { + protocol = URLProtocol( + name = origin.scheme, + defaultPort = -1 + ) + host = origin.serverHost + headers["X-Forward-Prefix"]?.let { + appendPathSegments(it) + } + appendEncodedPathSegments("webui", "#", "operation", id.toString()) } fun ApplicationCall.uuidParameter(name: String): UUID { diff --git a/bank/src/test/kotlin/BankIntegrationApiTest.kt b/bank/src/test/kotlin/BankIntegrationApiTest.kt index 73d6a700..90ea13f9 100644 --- a/bank/src/test/kotlin/BankIntegrationApiTest.kt +++ b/bank/src/test/kotlin/BankIntegrationApiTest.kt @@ -99,12 +99,14 @@ class BankIntegrationApiTest { json(req) }.assertOkJson<BankWithdrawalOperationPostResponse> { assertEquals(WithdrawalStatus.selected, it.status) + assertEquals("http://localhost/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/webui/#/operation/$uuid", it.confirm_transfer_url) } // Check already selected client.post("/taler-integration/withdrawal-operation/$uuid") { @@ -188,34 +190,4 @@ class BankIntegrationApiTest { client.postA("/taler-integration/withdrawal-operation/${UUID.randomUUID()}/abort") .assertNotFound(TalerErrorCode.BANK_TRANSACTION_NOT_FOUND) } - - // Testing the generation of taler://withdraw-URIs. - @Test - fun testWithdrawUri() { - // Checking the taler+http://-style. - val withHttp = getTalerWithdrawUri( - "http://example.com", - "my-id" - ) - assertEquals(withHttp, "taler+http://withdraw/example.com/taler-integration/my-id") - // Checking the taler://-style - val onlyTaler = getTalerWithdrawUri( - "https://example.com/", - "my-id" - ) - // Note: this tests as well that no double slashes belong to the result - assertEquals(onlyTaler, "taler://withdraw/example.com/taler-integration/my-id") - // Checking the removal of subsequent slashes - val manySlashes = getTalerWithdrawUri( - "https://www.example.com//////", - "my-id" - ) - assertEquals(manySlashes, "taler://withdraw/www.example.com/taler-integration/my-id") - // Checking with specified port number - val withPort = getTalerWithdrawUri( - "https://www.example.com:9876", - "my-id" - ) - assertEquals(withPort, "taler://withdraw/www.example.com:9876/taler-integration/my-id") - } }
\ No newline at end of file diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt index 4dda7872..1afb05cd 100644 --- a/bank/src/test/kotlin/CoreBankApiTest.kt +++ b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -990,7 +990,9 @@ class CoreBankWithdrawalApiTest { // Check OK client.postA("/accounts/merchant/withdrawals") { json { "amount" to "KUDOS:9.0" } - }.assertOk() + }.assertOkJson<BankAccountCreateWithdrawalResponse> { + assertEquals("taler+http://withdraw/localhost/taler-integration/${it.withdrawal_id}", it.taler_withdraw_uri) + } // Check exchange account client.postA("/accounts/exchange/withdrawals") { |