gnunet-svn
[Top][All Lists]
Advanced

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

[libeufin] 01/02: refactor file structure


From: gnunet
Subject: [libeufin] 01/02: refactor file structure
Date: Sun, 24 Sep 2023 14:18:55 +0200

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

dold pushed a commit to branch master
in repository libeufin.

commit 1ce3c4d4251015283d61654be5196c1a9734ca40
Author: Florian Dold <florian@dold.me>
AuthorDate: Sun Sep 24 13:19:20 2023 +0200

    refactor file structure
---
 .../tech/libeufin/bank/CorebankApiHandlers.kt      | 494 +++++++++++++++++++++
 ...rationHandlers.kt => IntegrationApiHandlers.kt} |   0
 bank/src/main/kotlin/tech/libeufin/bank/Main.kt    |   3 -
 ...atewayHandlers.kt => WireGatewayApiHandlers.kt} |   0
 .../tech/libeufin/bank/accountsMgmtHandlers.kt     | 168 -------
 .../kotlin/tech/libeufin/bank/talerWebHandlers.kt  | 177 --------
 .../kotlin/tech/libeufin/bank/tokenHandlers.kt     | 105 -----
 .../tech/libeufin/bank/transactionsHandlers.kt     | 136 ------
 contrib/wallet-core                                |   2 +-
 9 files changed, 495 insertions(+), 590 deletions(-)

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..49421402
--- /dev/null
+++ b/bank/src/main/kotlin/tech/libeufin/bank/CorebankApiHandlers.kt
@@ -0,0 +1,494 @@
+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.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
+    }
+
+    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
+    }
+
+    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
+    }
+
+    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/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..c01fd67f 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -311,9 +311,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)
     }
diff --git 
a/bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt 
b/bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
similarity index 100%
rename from bank/src/main/kotlin/tech/libeufin/bank/talerWireGatewayHandlers.kt
rename to bank/src/main/kotlin/tech/libeufin/bank/WireGatewayApiHandlers.kt
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/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/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

-- 
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]