gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[libeufin] branch master updated (236b29f9 -> fb40741b)


From: gnunet
Subject: [libeufin] branch master updated (236b29f9 -> fb40741b)
Date: Sun, 24 Sep 2023 14:18:54 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a change to branch master
in repository libeufin.

    from 236b29f9 -fix WIRE_GATEWAY_URL to match new design
     new 1ce3c4d4 refactor file structure
     new fb40741b refactoring, adapt to core bank API withdrawal change

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../kotlin/tech/libeufin/bank/Authentication.kt    |  39 ++
 .../libeufin/bank/{types.kt => BankMessages.kt}    |  63 ++-
 .../tech/libeufin/bank/CorebankApiHandlers.kt      | 456 +++++++++++++++++++++
 ...rationHandlers.kt => IntegrationApiHandlers.kt} |   0
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |  37 +-
 ...atewayHandlers.kt => WireGatewayApiHandlers.kt} |   9 +-
 .../tech/libeufin/bank/accountsMgmtHandlers.kt     | 168 --------
 bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 290 ++++++-------
 .../kotlin/tech/libeufin/bank/talerWebHandlers.kt  | 177 --------
 .../kotlin/tech/libeufin/bank/tokenHandlers.kt     | 105 -----
 .../tech/libeufin/bank/transactionsHandlers.kt     | 136 ------
 contrib/libeufin-bank.sample.conf                  |   2 +-
 contrib/wallet-core                                |   2 +-
 util/src/main/kotlin/HTTP.kt                       |  11 +-
 14 files changed, 669 insertions(+), 826 deletions(-)
 create mode 100644 bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
 rename bank/src/main/kotlin/tech/libeufin/bank/{types.kt => BankMessages.kt} 
(94%)
 create mode 100644 
bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
 rename bank/src/main/kotlin/tech/libeufin/bank/{talerIntegrationHandlers.kt => 
IntegrationApiHandlers.kt} (100%)
 rename bank/src/main/kotlin/tech/libeufin/bank/{talerWireGatewayHandlers.kt => 
WireGatewayApiHandlers.kt} (96%)
 delete mode 100644 
bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
 delete mode 100644 bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
 delete mode 100644 bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
 delete mode 100644 
bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
new file mode 100644
index 00000000..6054877e
--- /dev/null
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Authentication.kt
@@ -0,0 +1,39 @@
+package tech.libeufin.bank
+
+import io.ktor.http.*
+import io.ktor.server.application.*
+import net.taler.common.errorcodes.TalerErrorCode
+import tech.libeufin.util.getAuthorizationDetails
+import tech.libeufin.util.getAuthorizationRawHeader
+
+/**
+ * This function tries to authenticate the call according
+ * to the scheme that is mentioned in the Authorization header.
+ * The allowed schemes are either 'HTTP basic auth' or 'bearer token'.
+ *
+ * requiredScope can be either "readonly" or "readwrite".
+ *
+ * Returns the authenticated customer, or null if they failed.
+ */
+fun ApplicationCall.authenticateBankRequest(db: Database, requiredScope: 
TokenScope): Customer? {
+    // Extracting the Authorization header.
+    val header = getAuthorizationRawHeader(this.request) ?: throw badRequest(
+        "Authorization header not found.",
+        TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
+    )
+    val authDetails = getAuthorizationDetails(header) ?: throw badRequest(
+        "Authorization is invalid.",
+        TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
+    )
+    return when (authDetails.scheme) {
+        "Basic" -> doBasicAuth(db, authDetails.content)
+        "Bearer" -> doTokenAuth(db, authDetails.content, requiredScope)
+        else -> throw LibeufinBankException(
+            httpStatus = HttpStatusCode.Unauthorized,
+            talerError = TalerError(
+                code = TalerErrorCode.TALER_EC_GENERIC_UNAUTHORIZED.code,
+                hint = "Authorization method wrong or not supported."
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/types.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
similarity index 94%
rename from bank/src/main/kotlin/tech/libeufin/bank/types.kt
rename to bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
index 86c5dbf7..b12292e3 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/types.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/BankMessages.kt
@@ -23,17 +23,20 @@ import io.ktor.http.*
 import io.ktor.server.application.*
 import kotlinx.serialization.Contextual
 import kotlinx.serialization.Serializable
-import java.io.Serial
 import java.util.*
 
-// Allowed lengths for fractional digits in amounts.
+/**
+ * Allowed lengths for fractional digits in amounts.
+ */
 enum class FracDigits(howMany: Int) {
     TWO(2),
     EIGHT(8)
 }
 
 
-// It contains the number of microseconds since the Epoch.
+/**
+ * Timestamp containing the number of seconds since epoch.
+ */
 @Serializable
 data class Timestamp(
     val t_s: Long // FIXME (?): not supporting "never" at the moment.
@@ -86,9 +89,10 @@ data class RegisterAccountRequest(
     val internal_payto_uri: String? = null
 )
 
-/* Internal representation of relative times.  The
-* "forever" case is represented with Long.MAX_VALUE.
-*/
+/**
+ * Internal representation of relative times.  The
+ * "forever" case is represented with Long.MAX_VALUE.
+ */
 data class RelativeTime(
     val d_us: Long
 )
@@ -341,19 +345,30 @@ data class Config(
     val fiat_currency: String? = null
 )
 
-// GET /accounts/$USERNAME response.
+@Serializable
+data class Balance(
+    // FIXME: Should not be a string
+    val amount: String,
+    // FIXME: Should not be a string
+    val credit_debit_indicator: String,
+)
+
+/**
+ * GET /accounts/$USERNAME response.
+ */
 @Serializable
 data class AccountData(
     val name: String,
-    val balance: String,
+    val balance: Balance,
     val payto_uri: String,
     val debit_threshold: String,
     val contact_data: ChallengeContactData? = null,
     val cashout_payto_uri: String? = null,
-    val has_debit: Boolean
 )
 
-// Type of POST /transactions
+/**
+ * Response type of corebank API transaction initiation.
+ */
 @Serializable
 data class BankAccountTransactionCreate(
     val payto_uri: String,
@@ -457,7 +472,9 @@ data class TalerIntegrationConfigResponse(
     val currency: String
 )
 
-// Withdrawal status as spec'd in the Taler Integration API.
+/**
+ * Withdrawal status as specified in the Taler Integration API.
+ */
 @Serializable
 data class BankWithdrawalOperationStatus(
     // Indicates whether the withdrawal was aborted.
@@ -493,7 +510,9 @@ data class BankWithdrawalOperationStatus(
     val wire_types: MutableList<String> = mutableListOf("iban")
 )
 
-// Selection request on a Taler withdrawal.
+/**
+ * Selection request on a Taler withdrawal.
+ */
 @Serializable
 data class BankWithdrawalOperationPostRequest(
     val reserve_pub: String,
@@ -521,7 +540,9 @@ data class AddIncomingRequest(
     val debit_account: String
 )
 
-// Response to /admin/add-incoming
+/**
+ * Response to /admin/add-incoming
+ */
 @Serializable
 data class AddIncomingResponse(
     val timestamp: Long,
@@ -535,14 +556,18 @@ data class TWGConfigResponse(
     val currency: String
 )
 
-// Response of a TWG /history/incoming call.
+/**
+ * Response of a TWG /history/incoming call.
+ */
 @Serializable
 data class IncomingHistory(
     val incoming_transactions: MutableList<IncomingReserveTransaction> = 
mutableListOf(),
     val credit_account: String // Receiver's Payto URI.
 )
 
-// TWG's incoming payment record.
+/**
+ * TWG's incoming payment record.
+ */
 @Serializable
 data class IncomingReserveTransaction(
     val type: String = "RESERVE",
@@ -553,7 +578,9 @@ data class IncomingReserveTransaction(
     val reserve_pub: String
 )
 
-// TWG's request to pay a merchant.
+/**
+ * TWG's request to pay a merchant.
+ */
 @Serializable
 data class TransferRequest(
     val request_uid: String,
@@ -564,7 +591,9 @@ data class TransferRequest(
     val credit_account: String
 )
 
-// TWG's response to merchant payouts
+/**
+ * TWG's response to merchant payouts
+ */
 @Serializable
 data class TransferResponse(
     val timestamp: Long,
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
new file mode 100644
index 00000000..bfae12c4
--- /dev/null
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -0,0 +1,456 @@
+package tech.libeufin.bank
+
+import io.ktor.http.*
+import io.ktor.server.application.*
+import io.ktor.server.request.*
+import io.ktor.server.response.*
+import io.ktor.server.routing.*
+import net.taler.common.errorcodes.TalerErrorCode
+import net.taler.wallet.crypto.Base32Crockford
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import tech.libeufin.util.*
+import java.util.*
+
+private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
+
+/**
+ * This function collects all the /accounts handlers that
+ * create, update, delete, show bank accounts.  No histories
+ * and wire transfers should belong here.
+ */
+fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
+
+    delete("/accounts/{USERNAME}/token") {
+        throw internalServerError("Token deletion not implemented.")
+    }
+
+    post("/accounts/{USERNAME}/token") {
+        val customer = call.authenticateBankRequest(db, 
TokenScope.refreshable) ?: throw unauthorized("Authentication failed")
+        val endpointOwner = call.maybeUriComponent("USERNAME")
+        if (customer.login != endpointOwner) throw forbidden(
+            "User has no rights on this enpoint", TalerErrorCode.TALER_EC_END 
// FIXME: need generic forbidden
+        )
+        val maybeAuthToken = call.getAuthToken()
+        val req = call.receive<TokenRequest>()
+        /**
+         * This block checks permissions ONLY IF the call was authenticated
+         * with a token.  Basic auth gets always granted.
+         */
+        if (maybeAuthToken != null) {
+            val tokenBytes = Base32Crockford.decode(maybeAuthToken)
+            val refreshingToken = db.bearerTokenGet(tokenBytes) ?: throw 
internalServerError(
+                "Token used to auth not found in the database!"
+            )
+            if (refreshingToken.scope == TokenScope.readonly && req.scope == 
TokenScope.readwrite) throw forbidden(
+                "Cannot generate RW token from RO", 
TalerErrorCode.TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT
+            )
+        }
+        val tokenBytes = ByteArray(32).apply {
+            Random().nextBytes(this)
+        }
+        val maxDurationTime: Long = ctx.maxAuthTokenDurationUs
+        if (req.duration != null && req.duration.d_us > maxDurationTime) throw 
forbidden(
+            "Token duration bigger than bank's limit",
+            // FIXME: define new EC for this case.
+            TalerErrorCode.TALER_EC_END
+        )
+        val tokenDurationUs = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION_US
+        val customerDbRow = customer.dbRowId ?: throw internalServerError(
+            "Could not get customer '${customer.login}' database row ID"
+        )
+        val expirationTimestampUs: Long = getNowUs() + tokenDurationUs
+        if (expirationTimestampUs < tokenDurationUs) throw badRequest(
+            "Token duration caused arithmetic overflow",
+            // FIXME: need dedicate EC (?)
+            talerErrorCode = TalerErrorCode.TALER_EC_END
+        )
+        val token = BearerToken(
+            bankCustomer = customerDbRow,
+            content = tokenBytes,
+            creationTime = expirationTimestampUs,
+            expirationTime = expirationTimestampUs,
+            scope = req.scope,
+            isRefreshable = req.refreshable
+        )
+        if (!db.bearerTokenCreate(token)) throw internalServerError("Failed at 
inserting new token in the database")
+        call.respond(
+            TokenSuccessResponse(
+                access_token = Base32Crockford.encode(tokenBytes), expiration 
= Timestamp(
+                    t_s = expirationTimestampUs / 1000000L
+                )
+            )
+        )
+        return@post
+    }
+
+    post("/accounts") {
+        // check if only admin is allowed to create new accounts
+        if (ctx.restrictRegistration) {
+            val customer: Customer? = call.authenticateBankRequest(db, 
TokenScope.readwrite)
+            if (customer == null || customer.login != "admin") throw 
LibeufinBankException(
+                httpStatus = HttpStatusCode.Unauthorized, talerError = 
TalerError(
+                    code = TalerErrorCode.TALER_EC_BANK_LOGIN_FAILED.code,
+                    hint = "Either 'admin' not authenticated or an ordinary 
user tried this operation."
+                )
+            )
+        }
+        // auth passed, proceed with activity.
+        val req = call.receive<RegisterAccountRequest>()
+        // Prohibit reserved usernames:
+        if (req.username == "admin" || req.username == "bank") throw 
LibeufinBankException(
+            httpStatus = HttpStatusCode.Conflict, talerError = TalerError(
+                code = GENERIC_UNDEFINED, // FIXME: this waits GANA.
+                hint = "Username '${req.username}' is reserved."
+            )
+        )
+        // Checking idempotency.
+        val maybeCustomerExists = db.customerGetFromLogin(req.username)
+        // Can be null if previous call crashed before completion.
+        val maybeHasBankAccount = maybeCustomerExists.run {
+            if (this == null) return@run null
+            db.bankAccountGetFromOwnerId(this.expectRowId())
+        }
+        if (maybeCustomerExists != null && maybeHasBankAccount != null) {
+            logger.debug("Registering username was found: 
${maybeCustomerExists.login}")
+            // Checking _all_ the details are the same.
+            val isIdentic =
+                maybeCustomerExists.name == req.name && 
maybeCustomerExists.email == req.challenge_contact_data?.email && 
maybeCustomerExists.phone == req.challenge_contact_data?.phone && 
maybeCustomerExists.cashoutPayto == req.cashout_payto_uri && CryptoUtil.checkpw(
+                    req.password,
+                    maybeCustomerExists.passwordHash
+                ) && maybeHasBankAccount.isPublic == req.is_public && 
maybeHasBankAccount.isTalerExchange == req.is_taler_exchange && 
maybeHasBankAccount.internalPaytoUri == req.internal_payto_uri
+            if (isIdentic) {
+                call.respond(HttpStatusCode.Created)
+                return@post
+            }
+            throw LibeufinBankException(
+                httpStatus = HttpStatusCode.Conflict, talerError = TalerError(
+                    code = GENERIC_UNDEFINED, // GANA needs this.
+                    hint = "Idempotency check failed."
+                )
+            )
+        }
+        // From here: fresh user being added.
+        val newCustomer = Customer(
+            login = req.username,
+            name = req.name,
+            email = req.challenge_contact_data?.email,
+            phone = req.challenge_contact_data?.phone,
+            cashoutPayto = req.cashout_payto_uri,
+            // Following could be gone, if included in cashout_payto_uri
+            cashoutCurrency = ctx.cashoutCurrency,
+            passwordHash = CryptoUtil.hashpw(req.password),
+        )
+        val newCustomerRowId = db.customerCreate(newCustomer)
+            ?: throw internalServerError("New customer INSERT failed despite 
the previous checks")/* Crashing here won't break data consistency between 
customers
+         * and bank accounts, because of the idempotency.  Client will
+         * just have to retry.  */
+        val maxDebt = ctx.defaultCustomerDebtLimit
+        val newBankAccount = BankAccount(
+            hasDebt = false,
+            internalPaytoUri = req.internal_payto_uri ?: genIbanPaytoUri(),
+            owningCustomerId = newCustomerRowId,
+            isPublic = req.is_public,
+            isTalerExchange = req.is_taler_exchange,
+            maxDebt = maxDebt
+        )
+        val newBankAccountId = db.bankAccountCreate(newBankAccount)
+            ?: throw internalServerError("Could not INSERT bank account 
despite all the checks.")
+
+        /**
+         * The new account got created, now optionally award the registration
+         * bonus to it.  The configuration gets either a Taler amount (of the
+         * bonus), or null if no bonus is meant to be awarded.
+         */
+        val bonusAmount = if (ctx.registrationBonusEnabled) 
ctx.registrationBonus else null
+        if (bonusAmount != null) {
+            val adminCustomer =
+                db.customerGetFromLogin("admin") ?: throw 
internalServerError("Admin customer not found")
+            val adminBankAccount = 
db.bankAccountGetFromOwnerId(adminCustomer.expectRowId())
+                ?: throw internalServerError("Admin bank account not found")
+            val adminPaysBonus = BankInternalTransaction(
+                creditorAccountId = newBankAccountId,
+                debtorAccountId = adminBankAccount.expectRowId(),
+                amount = bonusAmount,
+                subject = "Registration bonus.",
+                transactionDate = getNowUs()
+            )
+            when (db.bankTransactionCreate(adminPaysBonus)) {
+                Database.BankTransactionResult.NO_CREDITOR -> throw 
internalServerError("Bonus impossible: creditor not found, despite its recent 
creation.")
+
+                Database.BankTransactionResult.NO_DEBTOR -> throw 
internalServerError("Bonus impossible: admin not found.")
+
+                Database.BankTransactionResult.CONFLICT -> throw 
internalServerError("Bonus impossible: admin has insufficient balance.")
+
+                Database.BankTransactionResult.SUCCESS -> {/* continue the 
execution */
+                }
+            }
+        }
+        call.respond(HttpStatusCode.Created)
+        return@post
+    }
+
+    get("/accounts/{USERNAME}") {
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized("Login failed")
+        val resourceName = call.maybeUriComponent("USERNAME") ?: throw 
badRequest(
+            hint = "No username found in the URI", talerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
+        )
+        // Checking resource name only if Basic auth was used.
+        // Successful tokens do not need this check, they just pass.
+        if (((c.login != resourceName) && (c.login != "admin")) && 
(call.getAuthToken() == null)) throw forbidden("No rights on the resource.")
+        val customerData = db.customerGetFromLogin(c.login)
+            ?: throw internalServerError("Customer '${c.login} despite being 
authenticated.'")
+        val customerInternalId = customerData.dbRowId
+            ?: throw internalServerError("Customer '${c.login} had no row ID 
despite it was found in the database.'")
+        val bankAccountData = db.bankAccountGetFromOwnerId(customerInternalId)
+            ?: throw internalServerError("Customer '${c.login} had no bank 
account despite they are customer.'")
+        val balance = Balance(
+            amount = bankAccountData.balance.toString(),
+            credit_debit_indicator = if (bankAccountData.hasDebt) { "debit" } 
else { "credit" }
+        )
+        call.respond(
+            AccountData(
+                name = customerData.name,
+                balance = balance,
+                debit_threshold = bankAccountData.maxDebt.toString(),
+                payto_uri = bankAccountData.internalPaytoUri,
+                contact_data = ChallengeContactData(
+                    email = customerData.email, phone = customerData.phone
+                ),
+                cashout_payto_uri = customerData.cashoutPayto,
+            )
+        )
+        return@get
+    }
+
+    post("/accounts/{USERNAME}/withdrawals") {
+        val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: 
throw unauthorized()
+        // Admin not allowed to withdraw in the name of customers:
+        val accountName = call.expectUriComponent("USERNAME")
+        if (c.login != accountName) throw unauthorized("User ${c.login} not 
allowed to withdraw for account '${accountName}'")
+        val req = call.receive<BankAccountCreateWithdrawalRequest>()
+        // Checking that the user has enough funds.
+        val b = db.bankAccountGetFromOwnerId(c.expectRowId())
+            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
+        val withdrawalAmount = parseTalerAmount(req.amount)
+        if (!isBalanceEnough(
+                balance = b.expectBalance(), due = withdrawalAmount, maxDebt = 
b.maxDebt, hasBalanceDebt = b.hasDebt
+            )
+        ) throw forbidden(
+            hint = "Insufficient funds to withdraw with Taler",
+            talerErrorCode = TalerErrorCode.TALER_EC_NONE // FIXME: need EC.
+        )
+        // Auth and funds passed, create the operation now!
+        val opId = UUID.randomUUID()
+        if (!db.talerWithdrawalCreate(
+                opId, b.expectRowId(), withdrawalAmount
+            )
+        ) throw internalServerError("Bank failed at creating the withdraw 
operation.")
+
+        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())
+            )
+        )
+        return@post
+    }
+
+    get("/withdrawals/{withdrawal_id}") {
+        val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
+        call.respond(
+            BankAccountGetWithdrawalResponse(
+                amount = op.amount.toString(),
+                aborted = op.aborted,
+                confirmation_done = op.confirmationDone,
+                selection_done = op.selectionDone,
+                selected_exchange_account = op.selectedExchangePayto,
+                selected_reserve_pub = op.reservePub
+            )
+        )
+        return@get
+    }
+
+    post("/withdrawals/{withdrawal_id}/abort") {
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
+        // Admin allowed to abort.
+        if (!call.getResourceName("USERNAME").canI(c)) throw forbidden()
+        val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
+        // Idempotency:
+        if (op.aborted) {
+            call.respondText("{}", ContentType.Application.Json)
+            return@post
+        }
+        // Op is found, it'll now fail only if previously confirmed (DB 
checks).
+        if (!db.talerWithdrawalAbort(op.withdrawalUuid)) throw conflict(
+            hint = "Cannot abort confirmed withdrawal", talerEc = 
TalerErrorCode.TALER_EC_END
+        )
+        call.respondText("{}", ContentType.Application.Json)
+        return@post
+    }
+
+    post("/withdrawals/{withdrawal_id}/confirm") {
+        val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: 
throw unauthorized()
+        // No admin allowed.
+        if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) 
throw forbidden()
+        val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
+        // Checking idempotency:
+        if (op.confirmationDone) {
+            call.respondText("{}", ContentType.Application.Json)
+            return@post
+        }
+        if (op.aborted) throw conflict(
+            hint = "Cannot confirm an aborted withdrawal", talerEc = 
TalerErrorCode.TALER_EC_BANK_CONFIRM_ABORT_CONFLICT
+        )
+        // Checking that reserve GOT indeed selected.
+        if (!op.selectionDone) throw LibeufinBankException(
+            httpStatus = HttpStatusCode.UnprocessableEntity, talerError = 
TalerError(
+                hint = "Cannot confirm an unselected withdrawal", code = 
TalerErrorCode.TALER_EC_END.code
+            )
+        )/* Confirmation conditions are all met, now put the operation
+         * to the selected state _and_ wire the funds to the exchange.
+         * Note: 'when' helps not to omit more result codes, should more
+         * be added.
+         */
+        when (db.talerWithdrawalConfirm(op.withdrawalUuid, getNowUs())) {
+            WithdrawalConfirmationResult.BALANCE_INSUFFICIENT -> throw 
conflict(
+                "Insufficient funds", TalerErrorCode.TALER_EC_END // FIXME: 
define EC for this.
+            )
+
+            WithdrawalConfirmationResult.OP_NOT_FOUND ->
+                /**
+                 * Despite previous checks, the database _still_ did not
+                 * find the withdrawal operation, that's on the bank.
+                 */
+                throw internalServerError("Withdrawal operation 
(${op.withdrawalUuid}) not found")
+
+            WithdrawalConfirmationResult.EXCHANGE_NOT_FOUND ->
+                /**
+                 * That can happen because the bank did not check the exchange
+                 * exists when POST /withdrawals happened, or because the 
exchange
+                 * bank account got removed before this confirmation.
+                 */
+                throw conflict(
+                    hint = "Exchange to withdraw from not found", talerEc = 
TalerErrorCode.TALER_EC_END // FIXME
+                )
+
+            WithdrawalConfirmationResult.CONFLICT -> throw 
internalServerError("Bank didn't check for idempotency")
+
+            WithdrawalConfirmationResult.SUCCESS -> call.respondText(
+                "{}", ContentType.Application.Json
+            )
+        }
+        return@post
+    }
+
+    get("/accounts/{USERNAME}/transactions") {
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
+        val resourceName = call.expectUriComponent("USERNAME")
+        if (c.login != resourceName && c.login != "admin") throw forbidden()
+        // Collecting params.
+        val historyParams = getHistoryParams(call.request)
+        // Making the query.
+        val bankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
+            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
+        val bankAccountId = bankAccount.expectRowId()
+        val history: List<BankAccountTransaction> = 
db.bankTransactionGetHistory(
+            start = historyParams.start, delta = historyParams.delta, 
bankAccountId = bankAccountId
+        )
+        val res = BankAccountTransactionsResponse(transactions = 
mutableListOf())
+        history.forEach {
+            res.transactions.add(
+                BankAccountTransactionInfo(
+                    debtor_payto_uri = it.debtorPaytoUri,
+                    creditor_payto_uri = it.creditorPaytoUri,
+                    subject = it.subject,
+                    amount = it.amount.toString(),
+                    direction = it.direction,
+                    date = it.transactionDate,
+                    row_id = it.dbRowId ?: throw internalServerError(
+                        "Transaction timestamped with '${it.transactionDate}' 
did not have row ID"
+                    )
+                )
+            )
+        }
+        call.respond(res)
+        return@get
+    }
+
+    // Creates a bank transaction.
+    post("/accounts/{USERNAME}/transactions") {
+        val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: 
throw unauthorized()
+        val resourceName = call.expectUriComponent("USERNAME")
+        // admin has no rights here.
+        if ((c.login != resourceName) && (call.getAuthToken() == null)) throw 
forbidden()
+        val txData = call.receive<BankAccountTransactionCreate>()
+        // FIXME: make payto parser IBAN-agnostic?
+        val payto = parsePayto(txData.payto_uri) ?: throw badRequest("Invalid 
creditor Payto")
+        val paytoWithoutParams = "payto://iban/${payto.bic}/${payto.iban}"
+        val subject = payto.message ?: throw badRequest("Wire transfer lacks 
subject")
+        val debtorId = c.dbRowId ?: throw internalServerError("Debtor database 
ID not found")
+        // This performs already a SELECT on the bank account,
+        // like the wire transfer will do as well later!
+        val creditorCustomerData = 
db.bankAccountGetFromInternalPayto(paytoWithoutParams) ?: throw notFound(
+            "Creditor account not found", TalerErrorCode.TALER_EC_END // 
FIXME: define this EC.
+        )
+        val amount = parseTalerAmount(txData.amount)
+        if (amount.currency != ctx.currency) throw badRequest(
+            "Wrong currency: ${amount.currency}", talerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
+        )
+        val dbInstructions = BankInternalTransaction(
+            debtorAccountId = debtorId,
+            creditorAccountId = creditorCustomerData.owningCustomerId,
+            subject = subject,
+            amount = amount,
+            transactionDate = getNowUs()
+        )
+        val res = db.bankTransactionCreate(dbInstructions)
+        when (res) {
+            Database.BankTransactionResult.CONFLICT -> throw conflict(
+                "Insufficient funds", TalerErrorCode.TALER_EC_END // FIXME: 
need bank 'insufficient funds' EC.
+            )
+
+            Database.BankTransactionResult.NO_CREDITOR -> throw 
internalServerError("Creditor not found despite previous checks.")
+
+            Database.BankTransactionResult.NO_DEBTOR -> throw 
internalServerError("Debtor not found despite the request was authenticated.")
+
+            Database.BankTransactionResult.SUCCESS -> 
call.respond(HttpStatusCode.OK)
+        }
+        return@post
+    }
+
+    get("/accounts/{USERNAME}/transactions/{T_ID}") {
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
+        val accountOwner = call.expectUriComponent("USERNAME")
+        // auth ok, check rights.
+        if (c.login != "admin" && c.login != accountOwner) throw forbidden()
+        // rights ok, check tx exists.
+        val tId = call.expectUriComponent("T_ID")
+        val txRowId = try {
+            tId.toLong()
+        } catch (e: Exception) {
+            logger.error(e.message)
+            throw badRequest("TRANSACTION_ID is not a number: ${tId}")
+        }
+        val customerRowId = c.dbRowId ?: throw 
internalServerError("Authenticated client lacks database entry")
+        val tx = db.bankTransactionGetFromInternalId(txRowId) ?: throw 
notFound(
+            "Bank transaction '$tId' not found", TalerErrorCode.TALER_EC_NONE 
// FIXME: need def.
+        )
+        val customerBankAccount = db.bankAccountGetFromOwnerId(customerRowId)
+            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
+        if (tx.bankAccountId != customerBankAccount.bankAccountId) throw 
forbidden("Client has no rights over the bank transaction: $tId")
+        // auth and rights, respond.
+        call.respond(
+            BankAccountTransactionInfo(
+                amount = 
"${tx.amount.currency}:${tx.amount.value}.${tx.amount.frac}",
+                creditor_payto_uri = tx.creditorPaytoUri,
+                debtor_payto_uri = tx.debtorPaytoUri,
+                date = tx.transactionDate,
+                direction = tx.direction,
+                subject = tx.subject,
+                row_id = txRowId
+            )
+        )
+        return@get
+    }
+}
\ No newline at end of file
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerIntegrationHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
similarity index 100%
rename from bank/src/main/kotlin/tech/libeufin/bank/talerIntegrationHandlers.kt
rename to bank/src/main/kotlin/tech/libeufin/bank/IntegrationApiHandlers.kt
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index 61ea1227..cb4bb7f9 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -165,38 +165,6 @@ object TalerAmountSerializer : KSerializer<TalerAmount> {
     }
 }
 
-/**
- * This function tries to authenticate the call according
- * to the scheme that is mentioned in the Authorization header.
- * The allowed schemes are either 'HTTP basic auth' or 'bearer token'.
- *
- * requiredScope can be either "readonly" or "readwrite".
- *
- * Returns the authenticated customer, or null if they failed.
- */
-fun ApplicationCall.myAuth(db: Database, requiredScope: TokenScope): Customer? 
{
-    // Extracting the Authorization header.
-    val header = getAuthorizationRawHeader(this.request) ?: throw badRequest(
-        "Authorization header not found.",
-        TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
-    )
-    val authDetails = getAuthorizationDetails(header) ?: throw badRequest(
-        "Authorization is invalid.",
-        TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
-    )
-    return when (authDetails.scheme) {
-        "Basic" -> doBasicAuth(db, authDetails.content)
-        "Bearer" -> doTokenAuth(db, authDetails.content, requiredScope)
-        else -> throw LibeufinBankException(
-            httpStatus = HttpStatusCode.Unauthorized,
-            talerError = TalerError(
-                code = TalerErrorCode.TALER_EC_GENERIC_UNAUTHORIZED.code,
-                hint = "Authorization method wrong or not supported."
-            )
-        )
-    }
-}
-
 
 /**
  * Set up web server handlers for the Taler corebank API.
@@ -311,9 +279,6 @@ fun Application.corebankWebApp(db: Database, ctx: 
BankApplicationContext) {
             return@get
         }
         this.accountsMgmtHandlers(db, ctx)
-        this.tokenHandlers(db, ctx)
-        this.transactionsHandlers(db, ctx)
-        this.talerWebHandlers(db)
         this.talerIntegrationHandlers(db, ctx)
         this.talerWireGatewayHandlers(db, ctx)
     }
@@ -411,7 +376,7 @@ fun readBankApplicationContextFromConfig(cfg: TalerConfig): 
BankApplicationConte
 
 class ServeBank : CliktCommand("Run libeufin-bank HTTP server", name = 
"serve") {
     private val configFile by option(
-        "--config",
+        "--config", "-c",
         help = "set the configuration file"
     )
     init {
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
similarity index 96%
rename from bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
rename to bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
index 619e5fc8..dda207b5 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
@@ -34,8 +34,9 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
         call.respond(TWGConfigResponse(currency = ctx.currency))
         return@get
     }
+
     get("/accounts/{USERNAME}/taler-wire-gateway/history/incoming") {
-        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
+        val c = call.authenticateBankRequest(db, TokenScope.readonly) ?: throw 
unauthorized()
         if (!call.getResourceName("USERNAME").canI(c, withAdmin = true)) throw 
forbidden()
         val params = getHistoryParams(call.request)
         val bankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
@@ -66,8 +67,9 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
         call.respond(resp)
         return@get
     }
+
     post("/accounts/{USERNAME}/taler-wire-gateway/transfer") {
-        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: 
throw unauthorized()
         if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) 
throw forbidden()
         val req = call.receive<TransferRequest>()
         // Checking for idempotency.
@@ -120,8 +122,9 @@ fun Routing.talerWireGatewayHandlers(db: Database, ctx: 
BankApplicationContext)
         ))
         return@post
     }
+
     post("/accounts/{USERNAME}/taler-wire-gateway/admin/add-incoming") {
-        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
+        val c = call.authenticateBankRequest(db, TokenScope.readwrite) ?: 
throw unauthorized()
         if (!call.getResourceName("USERNAME").canI(c, withAdmin = false)) 
throw forbidden()
         val req = call.receive<AddIncomingRequest>()
         val amount = parseTalerAmount(req.amount)
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
deleted file mode 100644
index ef6d66e3..00000000
--- a/bank/src/main/kotlin/tech/libeufin/bank/accountsMgmtHandlers.kt
+++ /dev/null
@@ -1,168 +0,0 @@
-package tech.libeufin.bank
-
-import io.ktor.http.*
-import io.ktor.server.application.*
-import io.ktor.server.request.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-import net.taler.common.errorcodes.TalerErrorCode
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import tech.libeufin.util.CryptoUtil
-import tech.libeufin.util.getNowUs
-import tech.libeufin.util.maybeUriComponent
-
-private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
-
-/**
- * This function collects all the /accounts handlers that
- * create, update, delete, show bank accounts.  No histories
- * and wire transfers should belong here.
- */
-fun Routing.accountsMgmtHandlers(db: Database, ctx: BankApplicationContext) {
-    post("/accounts") {
-        // check if only admin is allowed to create new accounts
-        if (ctx.restrictRegistration) {
-            val customer: Customer? = call.myAuth(db, TokenScope.readwrite)
-            if (customer == null || customer.login != "admin")
-                throw LibeufinBankException(
-                    httpStatus = HttpStatusCode.Unauthorized,
-                    talerError = TalerError(
-                        code = TalerErrorCode.TALER_EC_BANK_LOGIN_FAILED.code,
-                        hint = "Either 'admin' not authenticated or an 
ordinary user tried this operation."
-                    )
-                )
-        }
-        // auth passed, proceed with activity.
-        val req = call.receive<RegisterAccountRequest>()
-        // Prohibit reserved usernames:
-        if (req.username == "admin" || req.username == "bank")
-            throw LibeufinBankException(
-                httpStatus = HttpStatusCode.Conflict,
-                talerError = TalerError(
-                    code = GENERIC_UNDEFINED, // FIXME: this waits GANA.
-                    hint = "Username '${req.username}' is reserved."
-                )
-            )
-        // Checking idempotency.
-        val maybeCustomerExists = db.customerGetFromLogin(req.username)
-        // Can be null if previous call crashed before completion.
-        val maybeHasBankAccount = maybeCustomerExists.run {
-            if (this == null) return@run null
-            db.bankAccountGetFromOwnerId(this.expectRowId())
-        }
-        if (maybeCustomerExists != null && maybeHasBankAccount != null) {
-            tech.libeufin.bank.logger.debug("Registering username was found: 
${maybeCustomerExists.login}")
-            // Checking _all_ the details are the same.
-            val isIdentic =
-                maybeCustomerExists.name == req.name &&
-                        maybeCustomerExists.email == 
req.challenge_contact_data?.email &&
-                        maybeCustomerExists.phone == 
req.challenge_contact_data?.phone &&
-                        maybeCustomerExists.cashoutPayto == 
req.cashout_payto_uri &&
-                        CryptoUtil.checkpw(req.password, 
maybeCustomerExists.passwordHash) &&
-                        maybeHasBankAccount.isPublic == req.is_public &&
-                        maybeHasBankAccount.isTalerExchange == 
req.is_taler_exchange &&
-                        maybeHasBankAccount.internalPaytoUri == 
req.internal_payto_uri
-            if (isIdentic) {
-                call.respond(HttpStatusCode.Created)
-                return@post
-            }
-            throw LibeufinBankException(
-                httpStatus = HttpStatusCode.Conflict,
-                talerError = TalerError(
-                    code = GENERIC_UNDEFINED, // GANA needs this.
-                    hint = "Idempotency check failed."
-                )
-            )
-        }
-        // From here: fresh user being added.
-        val newCustomer = Customer(
-            login = req.username,
-            name = req.name,
-            email = req.challenge_contact_data?.email,
-            phone = req.challenge_contact_data?.phone,
-            cashoutPayto = req.cashout_payto_uri,
-            // Following could be gone, if included in cashout_payto_uri
-            cashoutCurrency = ctx.cashoutCurrency,
-            passwordHash = CryptoUtil.hashpw(req.password),
-        )
-        val newCustomerRowId = db.customerCreate(newCustomer)
-            ?: throw internalServerError("New customer INSERT failed despite 
the previous checks")
-        /* Crashing here won't break data consistency between customers
-         * and bank accounts, because of the idempotency.  Client will
-         * just have to retry.  */
-        val maxDebt = ctx.defaultCustomerDebtLimit
-        val newBankAccount = BankAccount(
-            hasDebt = false,
-            internalPaytoUri = req.internal_payto_uri ?: genIbanPaytoUri(),
-            owningCustomerId = newCustomerRowId,
-            isPublic = req.is_public,
-            isTalerExchange = req.is_taler_exchange,
-            maxDebt = maxDebt
-        )
-        val newBankAccountId = db.bankAccountCreate(newBankAccount)
-            ?: throw internalServerError("Could not INSERT bank account 
despite all the checks.")
-
-        /**
-         * The new account got created, now optionally award the registration
-         * bonus to it.  The configuration gets either a Taler amount (of the
-         * bonus), or null if no bonus is meant to be awarded.
-         */
-        val bonusAmount = if (ctx.registrationBonusEnabled) 
ctx.registrationBonus else null
-        if (bonusAmount != null) {
-            val adminCustomer = db.customerGetFromLogin("admin")
-                ?: throw internalServerError("Admin customer not found")
-            val adminBankAccount = 
db.bankAccountGetFromOwnerId(adminCustomer.expectRowId())
-                ?: throw internalServerError("Admin bank account not found")
-            val adminPaysBonus = BankInternalTransaction(
-                creditorAccountId = newBankAccountId,
-                debtorAccountId = adminBankAccount.expectRowId(),
-                amount = bonusAmount,
-                subject = "Registration bonus.",
-                transactionDate = getNowUs()
-            )
-            when(db.bankTransactionCreate(adminPaysBonus)) {
-                Database.BankTransactionResult.NO_CREDITOR ->
-                    throw internalServerError("Bonus impossible: creditor not 
found, despite its recent creation.")
-                Database.BankTransactionResult.NO_DEBTOR ->
-                    throw internalServerError("Bonus impossible: admin not 
found.")
-                Database.BankTransactionResult.CONFLICT ->
-                    throw internalServerError("Bonus impossible: admin has 
insufficient balance.")
-                Database.BankTransactionResult.SUCCESS -> {/* continue the 
execution */}
-            }
-        }
-        call.respond(HttpStatusCode.Created)
-        return@post
-    }
-    get("/accounts/{USERNAME}") {
-        val c = call.myAuth(db, TokenScope.readonly) ?: throw 
unauthorized("Login failed")
-        val resourceName = call.maybeUriComponent("USERNAME") ?: throw 
badRequest(
-            hint = "No username found in the URI",
-            talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
-        )
-        // Checking resource name only if Basic auth was used.
-        // Successful tokens do not need this check, they just pass.
-        if (
-            ((c.login != resourceName)
-            && (c.login != "admin"))
-            && (call.getAuthToken() == null)
-            )
-            throw forbidden("No rights on the resource.")
-        val customerData = db.customerGetFromLogin(c.login) ?: throw 
internalServerError("Customer '${c.login} despite being authenticated.'")
-        val customerInternalId = customerData.dbRowId ?: throw 
internalServerError("Customer '${c.login} had no row ID despite it was found in 
the database.'")
-        val bankAccountData = db.bankAccountGetFromOwnerId(customerInternalId) 
?: throw internalServerError("Customer '${c.login} had no bank account despite 
they are customer.'")
-        call.respond(AccountData(
-            name = customerData.name,
-            balance = bankAccountData.balance.toString(),
-            debit_threshold = bankAccountData.maxDebt.toString(),
-            payto_uri = bankAccountData.internalPaytoUri,
-            contact_data = ChallengeContactData(
-                email = customerData.email,
-                phone = customerData.phone
-            ),
-            cashout_payto_uri = customerData.cashoutPayto,
-            has_debit = bankAccountData.hasDebt
-        ))
-        return@get
-    }
-}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
index dd7d8027..9969f32c 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt
@@ -20,7 +20,6 @@
 package tech.libeufin.bank
 
 import io.ktor.http.*
-import io.ktor.http.cio.*
 import io.ktor.server.application.*
 import io.ktor.server.plugins.*
 import io.ktor.server.request.*
@@ -30,10 +29,8 @@ import net.taler.wallet.crypto.Base32Crockford
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
 import tech.libeufin.util.*
-import java.lang.NumberFormatException
 import java.net.URL
 import java.util.*
-import kotlin.system.exitProcess
 
 const val FRACTION_BASE = 100000000
 
@@ -41,23 +38,20 @@ private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.helpers
 
 fun ApplicationCall.expectUriComponent(componentName: String) =
     this.maybeUriComponent(componentName) ?: throw badRequest(
-        hint = "No username found in the URI",
-        talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
-)
+        hint = "No username found in the URI", talerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_PARAMETER_MISSING
+    )
+
 // Get the auth token (stripped of the bearer-token:-prefix)
 // IF the call was authenticated with it.
 fun ApplicationCall.getAuthToken(): String? {
     val h = getAuthorizationRawHeader(this.request) ?: return null
     val authDetails = getAuthorizationDetails(h) ?: throw badRequest(
-        "Authorization header is malformed.",
+        "Authorization header is malformed.", 
TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
+    )
+    if (authDetails.scheme == "Bearer") return 
splitBearerToken(authDetails.content) ?: throw throw badRequest(
+        "Authorization header is malformed (could not strip the prefix from 
Bearer token).",
         TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
     )
-    if (authDetails.scheme == "Bearer")
-        return splitBearerToken(authDetails.content) ?: throw
-        throw badRequest(
-            "Authorization header is malformed (could not strip the prefix 
from Bearer token).",
-            TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
-        )
     return null // Not a Bearer token case.
 }
 
@@ -76,14 +70,12 @@ fun doBasicAuth(db: Database, encodedCredentials: String): 
Customer? {
          */
         limit = 2
     )
-    if (userAndPassSplit.size != 2)
-        throw LibeufinBankException(
-            httpStatus = HttpStatusCode.BadRequest,
-            talerError = TalerError(
-                code = 
TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED.code,
-                "Malformed Basic auth credentials found in the Authorization 
header."
-            )
+    if (userAndPassSplit.size != 2) throw LibeufinBankException(
+        httpStatus = HttpStatusCode.BadRequest, talerError = TalerError(
+            code = TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED.code,
+            "Malformed Basic auth credentials found in the Authorization 
header."
         )
+    )
     val login = userAndPassSplit[0]
     val plainPassword = userAndPassSplit[1]
     val maybeCustomer = db.customerGetFromLogin(login) ?: throw unauthorized()
@@ -111,15 +103,13 @@ fun doTokenAuth(
     requiredScope: TokenScope,
 ): Customer? {
     val bareToken = splitBearerToken(token) ?: throw badRequest(
-        "Bearer token malformed",
-        talerErrorCode = TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
+        "Bearer token malformed", talerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
     )
     val tokenBytes = try {
         Base32Crockford.decode(bareToken)
     } catch (e: Exception) {
         throw badRequest(
-            e.message,
-            TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
+            e.message, TalerErrorCode.TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED
         )
     }
     val maybeToken: BearerToken? = db.bearerTokenGet(tokenBytes)
@@ -140,87 +130,67 @@ fun doTokenAuth(
         return null
     }
     // Getting the related username.
-    return db.customerGetFromRowId(maybeToken.bankCustomer)
-        ?: throw LibeufinBankException(
-            httpStatus = HttpStatusCode.InternalServerError,
-            talerError = TalerError(
-                code = 
TalerErrorCode.TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE.code,
-                hint = "Customer not found, despite token mentions it.",
-            ))
+    return db.customerGetFromRowId(maybeToken.bankCustomer) ?: throw 
LibeufinBankException(
+        httpStatus = HttpStatusCode.InternalServerError, talerError = 
TalerError(
+            code = 
TalerErrorCode.TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE.code,
+            hint = "Customer not found, despite token mentions it.",
+        )
+    )
 }
 
 fun forbidden(
     hint: String = "No rights on the resource",
     // FIXME: create a 'generic forbidden' Taler EC.
     talerErrorCode: TalerErrorCode = TalerErrorCode.TALER_EC_END
-): LibeufinBankException =
-    LibeufinBankException(
-        httpStatus = HttpStatusCode.Forbidden,
-        talerError = TalerError(
-            code = talerErrorCode.code,
-            hint = hint
-        )
+): LibeufinBankException = LibeufinBankException(
+    httpStatus = HttpStatusCode.Forbidden, talerError = TalerError(
+        code = talerErrorCode.code, hint = hint
     )
+)
 
-fun unauthorized(hint: String = "Login failed"): LibeufinBankException =
-    LibeufinBankException(
-        httpStatus = HttpStatusCode.Unauthorized,
-        talerError = TalerError(
-            code = TalerErrorCode.TALER_EC_BANK_LOGIN_FAILED.code,
-            hint = hint
-        )
+fun unauthorized(hint: String = "Login failed"): LibeufinBankException = 
LibeufinBankException(
+    httpStatus = HttpStatusCode.Unauthorized, talerError = TalerError(
+        code = TalerErrorCode.TALER_EC_BANK_LOGIN_FAILED.code, hint = hint
     )
-fun internalServerError(hint: String?): LibeufinBankException =
-    LibeufinBankException(
-        httpStatus = HttpStatusCode.InternalServerError,
-        talerError = TalerError(
-            code = 
TalerErrorCode.TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE.code,
-            hint = hint
-        )
+)
+
+fun internalServerError(hint: String?): LibeufinBankException = 
LibeufinBankException(
+    httpStatus = HttpStatusCode.InternalServerError, talerError = TalerError(
+        code = 
TalerErrorCode.TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE.code, hint = hint
     )
+)
 
 
 fun notFound(
-    hint: String?,
-    talerEc: TalerErrorCode
-): LibeufinBankException =
-    LibeufinBankException(
-        httpStatus = HttpStatusCode.NotFound,
-        talerError = TalerError(
-            code = talerEc.code,
-            hint = hint
-        )
+    hint: String?, talerEc: TalerErrorCode
+): LibeufinBankException = LibeufinBankException(
+    httpStatus = HttpStatusCode.NotFound, talerError = TalerError(
+        code = talerEc.code, hint = hint
     )
+)
 
 fun conflict(
-    hint: String?,
-    talerEc: TalerErrorCode
-): LibeufinBankException =
-    LibeufinBankException(
-        httpStatus = HttpStatusCode.Conflict,
-        talerError = TalerError(
-            code = talerEc.code,
-            hint = hint
-        )
+    hint: String?, talerEc: TalerErrorCode
+): LibeufinBankException = LibeufinBankException(
+    httpStatus = HttpStatusCode.Conflict, talerError = TalerError(
+        code = talerEc.code, hint = hint
     )
+)
+
 fun badRequest(
-    hint: String? = null,
-    talerErrorCode: TalerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID
-): LibeufinBankException =
-    LibeufinBankException(
-        httpStatus = HttpStatusCode.BadRequest,
-        talerError = TalerError(
-            code = talerErrorCode.code,
-            hint = hint
-        )
+    hint: String? = null, talerErrorCode: TalerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_JSON_INVALID
+): LibeufinBankException = LibeufinBankException(
+    httpStatus = HttpStatusCode.BadRequest, talerError = TalerError(
+        code = talerErrorCode.code, hint = hint
     )
+)
+
 // Generates a new Payto-URI with IBAN scheme.
 fun genIbanPaytoUri(): String = "payto://iban/SANDBOXX/${getIban()}"
 
 // Parses Taler amount, returning null if the input is invalid.
 fun parseTalerAmount2(
-    amount: String,
-    fracDigits: FracDigits
+    amount: String, fracDigits: FracDigits
 ): TalerAmount? {
     val format = when (fracDigits) {
         FracDigits.TWO -> "^([A-Z]+):([0-9]+)(\\.[0-9][0-9]?)?$"
@@ -246,11 +216,10 @@ fun parseTalerAmount2(
         return null
     }
     return TalerAmount(
-        value = value,
-        frac = fraction,
-        currency = match.destructured.component1()
+        value = value, frac = fraction, currency = 
match.destructured.component1()
     )
 }
+
 /**
  * This helper takes the serialized version of a Taler Amount
  * type and parses it into Libeufin's internal representation.
@@ -260,16 +229,13 @@ fun parseTalerAmount2(
  * responded to the client.
  */
 fun parseTalerAmount(
-    amount: String,
-    fracDigits: FracDigits = FracDigits.EIGHT
+    amount: String, fracDigits: FracDigits = FracDigits.EIGHT
 ): TalerAmount {
-    val maybeAmount = parseTalerAmount2(amount, fracDigits)
-        ?: throw LibeufinBankException(
-            httpStatus = HttpStatusCode.BadRequest,
-            talerError = TalerError(
-                code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code,
-                hint = "Invalid amount: $amount"
-            ))
+    val maybeAmount = parseTalerAmount2(amount, fracDigits) ?: throw 
LibeufinBankException(
+        httpStatus = HttpStatusCode.BadRequest, talerError = TalerError(
+            code = TalerErrorCode.TALER_EC_BANK_BAD_FORMAT_AMOUNT.code, hint = 
"Invalid amount: $amount"
+        )
+    )
     return maybeAmount
 }
 
@@ -278,9 +244,7 @@ private fun normalizeAmount(amt: TalerAmount): TalerAmount {
         val normalValue = amt.value + (amt.frac / FRACTION_BASE)
         val normalFrac = amt.frac % FRACTION_BASE
         return TalerAmount(
-            value = normalValue,
-            frac = normalFrac,
-            currency = amt.currency
+            value = normalValue, frac = normalFrac, currency = amt.currency
         )
     }
     return amt
@@ -289,22 +253,19 @@ private fun normalizeAmount(amt: TalerAmount): 
TalerAmount {
 
 // Adds two amounts and returns the normalized version.
 private fun amountAdd(first: TalerAmount, second: TalerAmount): TalerAmount {
-    if (first.currency != second.currency)
-        throw badRequest(
-            "Currency mismatch, balance '${first.currency}', price 
'${second.currency}'",
-            TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
-        )
+    if (first.currency != second.currency) throw badRequest(
+        "Currency mismatch, balance '${first.currency}', price 
'${second.currency}'",
+        TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
+    )
     val valueAdd = first.value + second.value
-    if (valueAdd < first.value)
-        throw badRequest("Amount value overflowed")
+    if (valueAdd < first.value) throw badRequest("Amount value overflowed")
     val fracAdd = first.frac + second.frac
-    if (fracAdd < first.frac)
-        throw badRequest("Amount fraction overflowed")
-    return normalizeAmount(TalerAmount(
-        value = valueAdd,
-        frac = fracAdd,
-        currency = first.currency
-    ))
+    if (fracAdd < first.frac) throw badRequest("Amount fraction overflowed")
+    return normalizeAmount(
+        TalerAmount(
+            value = valueAdd, frac = fracAdd, currency = first.currency
+        )
+    )
 }
 
 /**
@@ -315,20 +276,13 @@ private fun amountAdd(first: TalerAmount, second: 
TalerAmount): TalerAmount {
  * the database.
  */
 fun isBalanceEnough(
-    balance: TalerAmount,
-    due: TalerAmount,
-    maxDebt: TalerAmount,
-    hasBalanceDebt: Boolean
+    balance: TalerAmount, due: TalerAmount, maxDebt: TalerAmount, 
hasBalanceDebt: Boolean
 ): Boolean {
     val normalMaxDebt = normalizeAmount(maxDebt) // Very unlikely to be needed.
     if (hasBalanceDebt) {
         val chargedBalance = amountAdd(balance, due)
         if (chargedBalance.value > normalMaxDebt.value) return false // max 
debt surpassed
-        if (
-            (chargedBalance.value == normalMaxDebt.value) &&
-            (chargedBalance.frac > maxDebt.frac)
-            )
-            return false
+        if ((chargedBalance.value == normalMaxDebt.value) && 
(chargedBalance.frac > maxDebt.frac)) return false
         return true
     }
     /**
@@ -336,19 +290,17 @@ fun isBalanceEnough(
      * block calculates how much debt the balance would get, should a
      * subtraction of 'due' occur.
      */
-    if (balance.currency != due.currency)
-        throw badRequest(
-            "Currency mismatch, balance '${balance.currency}', due 
'${due.currency}'",
-            TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
-        )
+    if (balance.currency != due.currency) throw badRequest(
+        "Currency mismatch, balance '${balance.currency}', due 
'${due.currency}'",
+        TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
+    )
     val valueDiff = if (balance.value < due.value) due.value - balance.value 
else 0L
     val fracDiff = if (balance.frac < due.frac) due.frac - balance.frac else 0
     // Getting the normalized version of such diff.
     val normalDiff = normalizeAmount(TalerAmount(valueDiff, fracDiff, 
balance.currency))
     // Failing if the normalized diff surpasses the max debt.
     if (normalDiff.value > normalMaxDebt.value) return false
-    if ((normalDiff.value == normalMaxDebt.value) &&
-        (normalDiff.frac > normalMaxDebt.frac)) return false
+    if ((normalDiff.value == normalMaxDebt.value) && (normalDiff.frac > 
normalMaxDebt.frac)) return false
     return true
 }
 
@@ -360,47 +312,41 @@ fun isBalanceEnough(
  *
  *      https://$BANK_URL/taler-integration
  */
-fun getTalerWithdrawUri(baseUrl: String, woId: String) =
-    url {
-        val baseUrlObj = URL(baseUrl)
-        protocol = URLProtocol(
-            name = "taler".plus(if (baseUrlObj.protocol.lowercase() == "http") 
"+http" else ""),
-            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 ""
-            )
+fun getTalerWithdrawUri(baseUrl: String, woId: String) = url {
+    val baseUrlObj = URL(baseUrl)
+    protocol = URLProtocol(
+        name = "taler".plus(if (baseUrlObj.protocol.lowercase() == "http") 
"+http" else ""), 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)
-        }
-        pathSegments.add("taler-integration/${woId}")
-        this.appendPathSegments(pathSegments)
+    )
+    // Removing potential double slashes.
+    baseUrlObj.path.split("/").forEach {
+        if (it.isNotEmpty()) pathSegments.add(it)
     }
+    pathSegments.add("taler-integration/${woId}")
+    this.appendPathSegments(pathSegments)
+}
 
 // Builds a withdrawal confirm URL.
 fun getWithdrawalConfirmUrl(
-    baseUrl: String,
-    wopId: String,
-    username: String
-    ) =
-    url {
-        val baseUrlObj = URL(baseUrl)
-        protocol = URLProtocol(name = baseUrlObj.protocol, defaultPort = -1)
-        host = baseUrlObj.host
-        // Removing potential double slashes:
-        baseUrlObj.path.split("/").forEach {
-            this.appendPathSegments(it)
-        }
-        // Completing the endpoint:
-        
this.appendPathSegments("accounts/${username}/withdrawals/${wopId}/confirm")
+    baseUrl: String, wopId: String, username: String
+) = url {
+    val baseUrlObj = URL(baseUrl)
+    protocol = URLProtocol(name = baseUrlObj.protocol, defaultPort = -1)
+    host = baseUrlObj.host
+    // Removing potential double slashes:
+    baseUrlObj.path.split("/").forEach {
+        this.appendPathSegments(it)
     }
+    // Completing the endpoint:
+    
this.appendPathSegments("accounts/${username}/withdrawals/${wopId}/confirm")
+}
 
 
 /**
@@ -417,24 +363,23 @@ fun getWithdrawal(db: Database, opIdParam: String): 
TalerWithdrawalOperation {
         logger.error(e.message)
         throw badRequest("withdrawal_id query parameter was malformed")
     }
-    val op = db.talerWithdrawalGet(opId)
-        ?: throw notFound(
-            hint = "Withdrawal operation $opIdParam not found",
-            talerEc = TalerErrorCode.TALER_EC_END
-        )
+    val op = db.talerWithdrawalGet(opId) ?: throw notFound(
+        hint = "Withdrawal operation $opIdParam not found", talerEc = 
TalerErrorCode.TALER_EC_END
+    )
     return op
 }
 
 data class HistoryParams(
-    val delta: Long,
-    val start: Long
+    val delta: Long, val start: Long
 )
+
 /**
  * Extracts the query parameters from "history-like" endpoints,
  * providing the defaults according to the API specification.
  */
 fun getHistoryParams(req: ApplicationRequest): HistoryParams {
-    val deltaParam: String = req.queryParameters["delta"] ?: throw 
MissingRequestParameterException(parameterName = "delta")
+    val deltaParam: String =
+        req.queryParameters["delta"] ?: throw 
MissingRequestParameterException(parameterName = "delta")
     val delta: Long = try {
         deltaParam.toLong()
     } catch (e: Exception) {
@@ -473,8 +418,7 @@ fun maybeCreateAdminAccount(db: Database, ctx: 
BankApplicationContext): Boolean
              * Hashing the password helps to avoid the "password not hashed"
              * error, in case the admin tries to authenticate.
              */
-            passwordHash = CryptoUtil.hashpw(String(pwBuf, Charsets.UTF_8)),
-            name = "Bank administrator"
+            passwordHash = CryptoUtil.hashpw(String(pwBuf, Charsets.UTF_8)), 
name = "Bank administrator"
         )
         val rowId = db.customerCreate(adminCustomer)
         if (rowId == null) {
@@ -482,9 +426,7 @@ fun maybeCreateAdminAccount(db: Database, ctx: 
BankApplicationContext): Boolean
             return false
         }
         rowId
-    }
-    else
-        maybeAdminCustomer.expectRowId()
+    } else maybeAdminCustomer.expectRowId()
     val maybeAdminBankAccount = db.bankAccountGetFromOwnerId(adminCustomerId)
     if (maybeAdminBankAccount == null) {
         logger.info("Creating admin bank account")
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
deleted file mode 100644
index 258bc005..00000000
--- a/bank/src/main/kotlin/tech/libeufin/bank/talerWebHandlers.kt
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2019 Stanisci and Dold.
-
- * LibEuFin is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation; either version 3, or
- * (at your option) any later version.
-
- * LibEuFin is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
- * Public License for more details.
-
- * You should have received a copy of the GNU Affero General Public
- * License along with LibEuFin; see the file COPYING.  If not, see
- * <http://www.gnu.org/licenses/>
- */
-
-/* This file contains all the Taler handlers that do NOT
- * communicate with wallets, therefore any handler that serves
- * to SPAs or CLI HTTP clients.
- */
-
-package tech.libeufin.bank
-
-import io.ktor.http.*
-import io.ktor.server.application.*
-import io.ktor.server.plugins.*
-import io.ktor.server.request.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
-import tech.libeufin.util.getBaseUrl
-import tech.libeufin.util.getNowUs
-import java.util.*
-
-fun Routing.talerWebHandlers(db: Database) {
-    post("/accounts/{USERNAME}/withdrawals") {
-        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
-        // Admin not allowed to withdraw in the name of customers:
-        val accountName = call.expectUriComponent("USERNAME")
-        if (c.login != accountName)
-            throw unauthorized("User ${c.login} not allowed to withdraw for 
account '${accountName}'")
-        val req = call.receive<BankAccountCreateWithdrawalRequest>()
-        // Checking that the user has enough funds.
-        val b = db.bankAccountGetFromOwnerId(c.expectRowId())
-            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
-        val withdrawalAmount = parseTalerAmount(req.amount)
-        if (
-            !isBalanceEnough(
-                balance = b.expectBalance(),
-                due = withdrawalAmount,
-                maxDebt = b.maxDebt,
-                hasBalanceDebt = b.hasDebt
-            ))
-            throw forbidden(
-                hint = "Insufficient funds to withdraw with Taler",
-                talerErrorCode = TalerErrorCode.TALER_EC_NONE // FIXME: need 
EC.
-            )
-        // Auth and funds passed, create the operation now!
-        val opId = UUID.randomUUID()
-        if(
-            !db.talerWithdrawalCreate(
-                opId,
-                b.expectRowId(),
-                withdrawalAmount
-            )
-        )
-            throw internalServerError("Bank failed at creating the withdraw 
operation.")
-
-        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())
-        ))
-        return@post
-    }
-    get("/accounts/{USERNAME}/withdrawals/{withdrawal_id}") {
-        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
-        val accountName = call.expectUriComponent("USERNAME")
-        // Admin allowed to see the details
-        if (c.login != accountName && c.login != "admin") throw forbidden()
-        // Permissions passed, get the information.
-        val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
-        call.respond(BankAccountGetWithdrawalResponse(
-            amount = op.amount.toString(),
-            aborted = op.aborted,
-            confirmation_done = op.confirmationDone,
-            selection_done = op.selectionDone,
-            selected_exchange_account = op.selectedExchangePayto,
-            selected_reserve_pub = op.reservePub
-        ))
-        return@get
-    }
-    post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/abort") {
-        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
-        // Admin allowed to abort.
-        if (!call.getResourceName("USERNAME").canI(c)) throw forbidden()
-        val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
-        // Idempotency:
-        if (op.aborted) {
-            call.respondText("{}", ContentType.Application.Json)
-            return@post
-        }
-        // Op is found, it'll now fail only if previously confirmed (DB 
checks).
-        if (!db.talerWithdrawalAbort(op.withdrawalUuid)) throw conflict(
-            hint = "Cannot abort confirmed withdrawal",
-            talerEc = TalerErrorCode.TALER_EC_END
-        )
-        call.respondText("{}", ContentType.Application.Json)
-        return@post
-    }
-    post("/accounts/{USERNAME}/withdrawals/{withdrawal_id}/confirm") {
-        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
-        // No admin allowed.
-        if(!call.getResourceName("USERNAME").canI(c, withAdmin = false)) throw 
forbidden()
-        val op = getWithdrawal(db, call.expectUriComponent("withdrawal_id"))
-        // Checking idempotency:
-        if (op.confirmationDone) {
-            call.respondText("{}", ContentType.Application.Json)
-            return@post
-        }
-        if (op.aborted)
-            throw conflict(
-                hint = "Cannot confirm an aborted withdrawal",
-                talerEc = TalerErrorCode.TALER_EC_BANK_CONFIRM_ABORT_CONFLICT
-            )
-        // Checking that reserve GOT indeed selected.
-        if (!op.selectionDone)
-            throw LibeufinBankException(
-                httpStatus = HttpStatusCode.UnprocessableEntity,
-                talerError = TalerError(
-                    hint = "Cannot confirm an unselected withdrawal",
-                    code = TalerErrorCode.TALER_EC_END.code
-            ))
-        /* Confirmation conditions are all met, now put the operation
-         * to the selected state _and_ wire the funds to the exchange.
-         * Note: 'when' helps not to omit more result codes, should more
-         * be added.
-         */
-        when (db.talerWithdrawalConfirm(op.withdrawalUuid, getNowUs())) {
-            WithdrawalConfirmationResult.BALANCE_INSUFFICIENT ->
-                throw conflict(
-                    "Insufficient funds",
-                    TalerErrorCode.TALER_EC_END // FIXME: define EC for this.
-                )
-            WithdrawalConfirmationResult.OP_NOT_FOUND ->
-                /**
-                 * Despite previous checks, the database _still_ did not
-                 * find the withdrawal operation, that's on the bank.
-                 */
-                throw internalServerError("Withdrawal operation 
(${op.withdrawalUuid}) not found")
-            WithdrawalConfirmationResult.EXCHANGE_NOT_FOUND ->
-                /**
-                 * That can happen because the bank did not check the exchange
-                 * exists when POST /withdrawals happened, or because the 
exchange
-                 * bank account got removed before this confirmation.
-                 */
-                throw conflict(
-                    hint = "Exchange to withdraw from not found",
-                    talerEc = TalerErrorCode.TALER_EC_END // FIXME
-                )
-            WithdrawalConfirmationResult.CONFLICT ->
-                throw internalServerError("Bank didn't check for idempotency")
-            WithdrawalConfirmationResult.SUCCESS ->
-                call.respondText(
-                    "{}",
-                    ContentType.Application.Json
-                )
-        }
-        return@post
-    }
-}
-
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
deleted file mode 100644
index 218651d3..00000000
--- a/bank/src/main/kotlin/tech/libeufin/bank/tokenHandlers.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * This file is part of LibEuFin.
- * Copyright (C) 2019 Stanisci and Dold.
-
- * LibEuFin is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation; either version 3, or
- * (at your option) any later version.
-
- * LibEuFin is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
- * Public License for more details.
-
- * You should have received a copy of the GNU Affero General Public
- * License along with LibEuFin; see the file COPYING.  If not, see
- * <http://www.gnu.org/licenses/>
- */
-
-package tech.libeufin.bank
-
-import io.ktor.server.application.*
-import io.ktor.server.request.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-import net.taler.common.errorcodes.TalerErrorCode
-import net.taler.wallet.crypto.Base32Crockford
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import tech.libeufin.util.maybeUriComponent
-import tech.libeufin.util.getNowUs
-
-private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.accountsMgmtHandlers")
-
-fun Routing.tokenHandlers(db: Database, ctx: BankApplicationContext) {
-    delete("/accounts/{USERNAME}/token") {
-        throw internalServerError("Token deletion not implemented.")
-    }
-    post("/accounts/{USERNAME}/token") {
-        val customer = call.myAuth(db, TokenScope.refreshable) ?: throw 
unauthorized("Authentication failed")
-        val endpointOwner = call.maybeUriComponent("USERNAME")
-        if (customer.login != endpointOwner)
-            throw forbidden(
-                "User has no rights on this enpoint",
-                TalerErrorCode.TALER_EC_END // FIXME: need generic forbidden
-            )
-        val maybeAuthToken = call.getAuthToken()
-        val req = call.receive<TokenRequest>()
-        /**
-         * This block checks permissions ONLY IF the call was authenticated
-         * with a token.  Basic auth gets always granted.
-         */
-        if (maybeAuthToken != null) {
-            val tokenBytes = Base32Crockford.decode(maybeAuthToken)
-            val refreshingToken = db.bearerTokenGet(tokenBytes) ?: throw 
internalServerError(
-                "Token used to auth not found in the database!"
-            )
-            if (refreshingToken.scope == TokenScope.readonly && req.scope == 
TokenScope.readwrite)
-                throw forbidden(
-                    "Cannot generate RW token from RO",
-                    
TalerErrorCode.TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT
-                )
-        }
-        val tokenBytes = ByteArray(32).apply {
-            java.util.Random().nextBytes(this)
-        }
-        val maxDurationTime: Long = ctx.maxAuthTokenDurationUs
-        if (req.duration != null && req.duration.d_us > maxDurationTime)
-            throw forbidden(
-                "Token duration bigger than bank's limit",
-                // FIXME: define new EC for this case.
-                TalerErrorCode.TALER_EC_END
-            )
-        val tokenDurationUs  = req.duration?.d_us ?: TOKEN_DEFAULT_DURATION_US
-        val customerDbRow = customer.dbRowId ?: throw internalServerError(
-            "Could not get customer '${customer.login}' database row ID"
-        )
-        val expirationTimestampUs: Long = getNowUs() + tokenDurationUs
-        if (expirationTimestampUs < tokenDurationUs)
-            throw badRequest(
-                "Token duration caused arithmetic overflow",
-                // FIXME: need dedicate EC (?)
-                talerErrorCode = TalerErrorCode.TALER_EC_END
-            )
-        val token = BearerToken(
-            bankCustomer = customerDbRow,
-            content = tokenBytes,
-            creationTime = expirationTimestampUs,
-            expirationTime = expirationTimestampUs,
-            scope = req.scope,
-            isRefreshable = req.refreshable
-        )
-        if (!db.bearerTokenCreate(token))
-            throw internalServerError("Failed at inserting new token in the 
database")
-        call.respond(
-            TokenSuccessResponse(
-                access_token = Base32Crockford.encode(tokenBytes),
-                expiration = Timestamp(
-                    t_s = expirationTimestampUs / 1000000L
-                )
-            )
-        )
-        return@post
-    }
-}
\ No newline at end of file
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt
deleted file mode 100644
index 64266110..00000000
--- a/bank/src/main/kotlin/tech/libeufin/bank/transactionsHandlers.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-package tech.libeufin.bank
-
-import io.ktor.http.*
-import io.ktor.server.application.*
-import io.ktor.server.plugins.*
-import io.ktor.server.request.*
-import io.ktor.server.response.*
-import io.ktor.server.routing.*
-import net.taler.common.errorcodes.TalerErrorCode
-import org.slf4j.Logger
-import org.slf4j.LoggerFactory
-import tech.libeufin.util.getNowUs
-import tech.libeufin.util.parsePayto
-import kotlin.math.abs
-
-
-private val logger: Logger = 
LoggerFactory.getLogger("tech.libeufin.bank.transactionHandlers")
-
-fun Routing.transactionsHandlers(db: Database, ctx: BankApplicationContext) {
-    get("/accounts/{USERNAME}/transactions") {
-        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
-        val resourceName = call.expectUriComponent("USERNAME")
-        if (c.login != resourceName && c.login != "admin") throw forbidden()
-        // Collecting params.
-        val historyParams = getHistoryParams(call.request)
-        // Making the query.
-        val bankAccount = db.bankAccountGetFromOwnerId(c.expectRowId())
-            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
-        val bankAccountId = bankAccount.expectRowId()
-        val history: List<BankAccountTransaction> = 
db.bankTransactionGetHistory(
-            start = historyParams.start,
-            delta = historyParams.delta,
-            bankAccountId = bankAccountId
-        )
-        val res = BankAccountTransactionsResponse(transactions = 
mutableListOf())
-        history.forEach {
-            res.transactions.add(BankAccountTransactionInfo(
-                debtor_payto_uri = it.debtorPaytoUri,
-                creditor_payto_uri = it.creditorPaytoUri,
-                subject = it.subject,
-                amount = it.amount.toString(),
-                direction = it.direction,
-                date = it.transactionDate,
-                row_id = it.dbRowId ?: throw internalServerError(
-                    "Transaction timestamped with '${it.transactionDate}' did 
not have row ID"
-                )
-            ))
-        }
-        call.respond(res)
-        return@get
-    }
-    // Creates a bank transaction.
-    post("/accounts/{USERNAME}/transactions") {
-        val c = call.myAuth(db, TokenScope.readwrite) ?: throw unauthorized()
-        val resourceName = call.expectUriComponent("USERNAME")
-        // admin has no rights here.
-        if ((c.login != resourceName) && (call.getAuthToken() == null))
-            throw forbidden()
-        val txData = call.receive<BankAccountTransactionCreate>()
-        // FIXME: make payto parser IBAN-agnostic?
-        val payto = parsePayto(txData.payto_uri) ?: throw badRequest("Invalid 
creditor Payto")
-        val paytoWithoutParams = "payto://iban/${payto.bic}/${payto.iban}"
-        val subject = payto.message ?: throw badRequest("Wire transfer lacks 
subject")
-        val debtorId = c.dbRowId ?: throw internalServerError("Debtor database 
ID not found")
-        // This performs already a SELECT on the bank account,
-        // like the wire transfer will do as well later!
-        val creditorCustomerData = 
db.bankAccountGetFromInternalPayto(paytoWithoutParams)
-            ?: throw notFound(
-                "Creditor account not found",
-                TalerErrorCode.TALER_EC_END // FIXME: define this EC.
-            )
-        val amount = parseTalerAmount(txData.amount)
-        if (amount.currency != ctx.currency)
-            throw badRequest(
-                "Wrong currency: ${amount.currency}",
-                talerErrorCode = 
TalerErrorCode.TALER_EC_GENERIC_CURRENCY_MISMATCH
-            )
-        val dbInstructions = BankInternalTransaction(
-            debtorAccountId = debtorId,
-            creditorAccountId = creditorCustomerData.owningCustomerId,
-            subject = subject,
-            amount = amount,
-            transactionDate = getNowUs()
-        )
-        val res = db.bankTransactionCreate(dbInstructions)
-        when(res) {
-            Database.BankTransactionResult.CONFLICT ->
-                throw conflict(
-                    "Insufficient funds",
-                    TalerErrorCode.TALER_EC_END // FIXME: need bank 
'insufficient funds' EC.
-                )
-            Database.BankTransactionResult.NO_CREDITOR ->
-                throw internalServerError("Creditor not found despite previous 
checks.")
-            Database.BankTransactionResult.NO_DEBTOR ->
-                throw internalServerError("Debtor not found despite the 
request was authenticated.")
-            Database.BankTransactionResult.SUCCESS -> 
call.respond(HttpStatusCode.OK)
-        }
-        return@post
-    }
-    get("/accounts/{USERNAME}/transactions/{T_ID}") {
-        val c = call.myAuth(db, TokenScope.readonly) ?: throw unauthorized()
-        val accountOwner = call.expectUriComponent("USERNAME")
-        // auth ok, check rights.
-        if (c.login != "admin" && c.login != accountOwner)
-            throw forbidden()
-        // rights ok, check tx exists.
-        val tId = call.expectUriComponent("T_ID")
-        val txRowId = try {
-            tId.toLong()
-        } catch (e: Exception) {
-            logger.error(e.message)
-            throw badRequest("TRANSACTION_ID is not a number: ${tId}")
-        }
-        val customerRowId = c.dbRowId ?: throw 
internalServerError("Authenticated client lacks database entry")
-        val tx = db.bankTransactionGetFromInternalId(txRowId)
-            ?: throw notFound(
-                "Bank transaction '$tId' not found",
-                TalerErrorCode.TALER_EC_NONE // FIXME: need def.
-            )
-        val customerBankAccount = db.bankAccountGetFromOwnerId(customerRowId)
-            ?: throw internalServerError("Customer '${c.login}' lacks bank 
account.")
-        if (tx.bankAccountId != customerBankAccount.bankAccountId)
-            throw forbidden("Client has no rights over the bank transaction: 
$tId")
-        // auth and rights, respond.
-        call.respond(BankAccountTransactionInfo(
-            amount = 
"${tx.amount.currency}:${tx.amount.value}.${tx.amount.frac}",
-            creditor_payto_uri = tx.creditorPaytoUri,
-            debtor_payto_uri = tx.debtorPaytoUri,
-            date = tx.transactionDate,
-            direction = tx.direction,
-            subject = tx.subject,
-            row_id = txRowId
-        ))
-        return@get
-    }
-}
\ No newline at end of file
diff --git a/contrib/libeufin-bank.sample.conf 
b/contrib/libeufin-bank.sample.conf
index a50fef97..00317b8e 100644
--- a/contrib/libeufin-bank.sample.conf
+++ b/contrib/libeufin-bank.sample.conf
@@ -1,5 +1,5 @@
 [libeufin-bank]
-currency = KUDOS
+CURRENCY = KUDOS
 DEFAULT_CUSTOMER_DEBT_LIMIT = KUDOS:200
 DEFAULT_ADMIN_DEBT_LIMIT = KUDOS:2000
 REGISTRATION_BONUS = KUDOS:100
diff --git a/contrib/wallet-core b/contrib/wallet-core
index 7079bce1..9e2d95b3 160000
--- a/contrib/wallet-core
+++ b/contrib/wallet-core
@@ -1 +1 @@
-Subproject commit 7079bce1ad2640e44561f56b46d5f00758df8e5d
+Subproject commit 9e2d95b39723a038eb714d723ac0910a5bf596e2
diff --git a/util/src/main/kotlin/HTTP.kt b/util/src/main/kotlin/HTTP.kt
index ac3a51bc..b1e0fc02 100644
--- a/util/src/main/kotlin/HTTP.kt
+++ b/util/src/main/kotlin/HTTP.kt
@@ -3,9 +3,7 @@ package tech.libeufin.util
 import io.ktor.http.*
 import io.ktor.server.application.*
 import io.ktor.server.request.*
-import io.ktor.server.response.*
 import io.ktor.server.util.*
-import io.ktor.util.*
 import logger
 
 // Get the base URL of a request, returns null if any problem occurs.
@@ -22,13 +20,13 @@ fun ApplicationRequest.getBaseUrl(): String? {
             prefix += "/"
         URLBuilder(
             protocol = URLProtocol(
-                name = this.headers.get("X-Forwarded-Proto") ?: run {
+                name = this.headers["X-Forwarded-Proto"] ?: run {
                     logger.error("Reverse proxy did not define 
X-Forwarded-Proto")
                     return null
                 },
                 defaultPort = -1 // Port must be specified with 
X-Forwarded-Host.
             ),
-            host = this.headers.get("X-Forwarded-Host") ?: run {
+            host = this.headers["X-Forwarded-Host"] ?: run {
                 logger.error("Reverse proxy did not define X-Forwarded-Host")
                 return null
             }
@@ -46,10 +44,6 @@ fun ApplicationRequest.getBaseUrl(): String? {
     }
 }
 
-/**
- * Get the URI (path's) component or throw Internal server error.
- * @param component the name of the URI component to return.
- */
 fun ApplicationCall.maybeUriComponent(name: String): String? {
     val ret: String? = this.parameters[name]
     if (ret == null) {
@@ -77,6 +71,7 @@ data class AuthorizationDetails(
     val scheme: String,
     val content: String
 )
+
 // Returns the authorization scheme mentioned in the Auth header,
 // or null if that could not be found.
 fun getAuthorizationDetails(authorizationHeader: String): 
AuthorizationDetails? {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]