diff options
author | Antoine A <> | 2024-03-13 21:17:00 +0100 |
---|---|---|
committer | Antoine A <> | 2024-03-13 21:17:00 +0100 |
commit | c481ebac54b747ebb56b09c1430755bc4a70f3e5 (patch) | |
tree | b285e1e3f8226709bd5e5de4b3a348a346d899b6 | |
parent | d5d5035d3740104acb38b1b0f95c1305f78bfd95 (diff) | |
download | libeufin-c481ebac54b747ebb56b09c1430755bc4a70f3e5.tar.gz libeufin-c481ebac54b747ebb56b09c1430755bc4a70f3e5.tar.bz2 libeufin-c481ebac54b747ebb56b09c1430755bc4a70f3e5.zip |
Fix account creation idempotency
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/Main.kt | 11 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt | 19 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 27 | ||||
-rw-r--r-- | bank/src/main/kotlin/tech/libeufin/bank/helpers.kt | 3 | ||||
-rw-r--r-- | bank/src/test/kotlin/DatabaseTest.kt | 5 | ||||
-rw-r--r-- | bank/src/test/kotlin/helpers.kt | 24 |
6 files changed, 51 insertions, 38 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt index 6088d71d..ecdc5810 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -274,7 +274,7 @@ class BankDbInit : CliktCommand("Initialize the libeufin-bank database", name = AccountCreationResult.LoginReuse -> {} AccountCreationResult.PayToReuse -> throw Exception("Failed to create admin's account") - AccountCreationResult.Success -> + is AccountCreationResult.Success -> logger.info("Admin's account created") } } @@ -487,18 +487,19 @@ class CreateAccount : CliktCommand( ) } req?.let { - val (result, internalPayto) = createAccount(db, ctx, req, true) + val result = createAccount(db, ctx, req, true) when (result) { AccountCreationResult.BonusBalanceInsufficient -> throw Exception("Insufficient admin funds to grant bonus") AccountCreationResult.LoginReuse -> throw Exception("Account username reuse '${req.username}'") AccountCreationResult.PayToReuse -> - throw Exception("Bank internalPayToUri reuse '$internalPayto'") - AccountCreationResult.Success -> + throw Exception("Bank internalPayToUri reuse") + is AccountCreationResult.Success -> { logger.info("Account '${req.username}' created") + println(result.payto) + } } - println(internalPayto) } } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt index feaa84df..dd6aab4b 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt @@ -145,7 +145,7 @@ suspend fun createAccount( cfg: BankConfig, req: RegisterAccountRequest, isAdmin: Boolean -): Pair<AccountCreationResult, String> { +): AccountCreationResult { // Prohibit reserved usernames: if (RESERVED_ACCOUNTS.contains(req.username)) throw conflict( @@ -209,14 +209,15 @@ suspend fun createAccount( bonus = if (!req.is_taler_exchange) cfg.registrationBonus else TalerAmount(0, 0, cfg.regionalCurrency), tanChannel = req.tan_channel, - checkPaytoIdempotent = req.payto_uri != null + checkPaytoIdempotent = req.payto_uri != null, + ctx = cfg.payto ) // Retry with new IBAN if (res == AccountCreationResult.PayToReuse && retry > 0) { retry-- continue } - return Pair(res, internalPayto.bank(req.name, cfg.payto)) + return res } } WireMethod.X_TALER_BANK -> { @@ -229,7 +230,7 @@ suspend fun createAccount( val internalPayto = XTalerBankPayto.forUsername(req.username) - val res = db.account.create( + return db.account.create( login = req.username, name = req.name, email = req.contact_data?.email?.get(), @@ -243,9 +244,9 @@ suspend fun createAccount( bonus = if (!req.is_taler_exchange) cfg.registrationBonus else TalerAmount(0, 0, cfg.regionalCurrency), tanChannel = req.tan_channel, - checkPaytoIdempotent = req.payto_uri != null + checkPaytoIdempotent = req.payto_uri != null, + ctx = cfg.payto ) - return Pair(res, internalPayto.bank(req.name, cfg.payto)) } } } @@ -294,7 +295,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { authAdmin(db, TokenScope.readwrite, !ctx.allowRegistration) { post("/accounts") { val req = call.receive<RegisterAccountRequest>() - val (result, internalPayto) = createAccount(db, ctx, req, isAdmin) + val result = createAccount(db, ctx, req, isAdmin) when (result) { AccountCreationResult.BonusBalanceInsufficient -> throw conflict( "Insufficient admin funds to grant bonus", @@ -305,10 +306,10 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { TalerErrorCode.BANK_REGISTER_USERNAME_REUSE ) AccountCreationResult.PayToReuse -> throw conflict( - "Bank internalPayToUri reuse '$internalPayto'", + "Bank internalPayToUri reuse", TalerErrorCode.BANK_REGISTER_PAYTO_URI_REUSE ) - AccountCreationResult.Success -> call.respond(RegisterAccountResponse(internalPayto)) + is AccountCreationResult.Success -> call.respond(RegisterAccountResponse(result.payto)) } } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt index c4c5054c..13bc1e09 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -27,11 +27,11 @@ import java.time.Instant /** Data access logic for accounts */ class AccountDAO(private val db: Database) { /** Result status of account creation */ - enum class AccountCreationResult { - Success, - LoginReuse, - PayToReuse, - BonusBalanceInsufficient, + sealed interface AccountCreationResult { + data class Success(val payto: String): AccountCreationResult + data object LoginReuse: AccountCreationResult + data object PayToReuse: AccountCreationResult + data object BonusBalanceInsufficient: AccountCreationResult } /** Create new account */ @@ -49,7 +49,8 @@ class AccountDAO(private val db: Database) { bonus: TalerAmount, tanChannel: TanChannel?, // Whether to check [internalPaytoUri] for idempotency - checkPaytoIdempotent: Boolean + checkPaytoIdempotent: Boolean, + ctx: BankPaytoCtx ): AccountCreationResult = db.serializable { it -> val now = Instant.now().toDbMicros() ?: throw faultyTimestampByBank() it.transaction { conn -> @@ -62,6 +63,7 @@ class AccountDAO(private val db: Database) { AND is_public=? AND is_taler_exchange=? AND tan_channel IS NOT DISTINCT FROM ?::tan_enum + ,internal_payto_uri, name FROM customers JOIN bank_accounts ON customer_id=owning_customer_id @@ -79,13 +81,16 @@ class AccountDAO(private val db: Database) { setString(9, tanChannel?.name) setString(10, login) oneOrNull { - PwCrypto.checkpw(password, it.getString(1)) && it.getBoolean(2) + Pair( + PwCrypto.checkpw(password, it.getString(1)) && it.getBoolean(2), + it.getBankPayto("internal_payto_uri", "name", ctx) + ) } } if (idempotent != null) { - if (idempotent) { - AccountCreationResult.Success + if (idempotent.first) { + AccountCreationResult.Success(idempotent.second) } else { AccountCreationResult.LoginReuse } @@ -165,12 +170,12 @@ class AccountDAO(private val db: Database) { conn.rollback() AccountCreationResult.BonusBalanceInsufficient } - else -> AccountCreationResult.Success + else -> AccountCreationResult.Success(internalPayto.bank(name, ctx)) } } } } else { - AccountCreationResult.Success + AccountCreationResult.Success(internalPayto.bank(name, ctx)) } } } diff --git a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt index f8c5f25b..e502f62c 100644 --- a/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt +++ b/bank/src/main/kotlin/tech/libeufin/bank/helpers.kt @@ -127,7 +127,8 @@ suspend fun createAdminAccount(db: Database, cfg: BankConfig, pw: String? = null email = null, phone = null, cashoutPayto = null, - tanChannel = null + tanChannel = null, + ctx = cfg.payto ) } diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt index e41069f7..70736d79 100644 --- a/bank/src/test/kotlin/DatabaseTest.kt +++ b/bank/src/test/kotlin/DatabaseTest.kt @@ -1,6 +1,6 @@ /* * This file is part of LibEuFin. - * Copyright (C) 2023 Taler Systems S.A. + * Copyright (C) 2023-2024 Taler Systems S.A. * LibEuFin is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -29,6 +29,7 @@ import java.time.temporal.ChronoUnit import java.util.concurrent.TimeUnit import kotlin.test.assertEquals import kotlin.test.assertNull +import kotlin.test.assertIs class DatabaseTest { @@ -36,7 +37,7 @@ class DatabaseTest { @Test fun createAdmin() = setup { db, ctx -> // Create admin account - assertEquals(AccountCreationResult.Success, createAdminAccount(db, ctx)) + assertIs<AccountCreationResult.Success>(createAdminAccount(db, ctx)) // Checking idempotency assertEquals(AccountCreationResult.LoginReuse, createAdminAccount(db, ctx)) } diff --git a/bank/src/test/kotlin/helpers.kt b/bank/src/test/kotlin/helpers.kt index 303bd909..d78817b6 100644 --- a/bank/src/test/kotlin/helpers.kt +++ b/bank/src/test/kotlin/helpers.kt @@ -34,6 +34,7 @@ import kotlin.io.path.readText import kotlin.random.Random import kotlin.test.assertEquals import kotlin.test.assertNotNull +import kotlin.test.assertIs /* ----- Setup ----- */ @@ -79,10 +80,10 @@ fun bankSetup( conf: String = "test.conf", lambda: suspend ApplicationTestBuilder.(Database) -> Unit ) { - setup(conf) { db, ctx -> + setup(conf) { db, cfg -> // Creating the exchange and merchant accounts first. val bonus = TalerAmount("KUDOS:0") - assertEquals(AccountCreationResult.Success, db.account.create( + assertIs<AccountCreationResult.Success>(db.account.create( login = "merchant", password = "merchant-password", name = "Merchant", @@ -95,9 +96,10 @@ fun bankSetup( email = null, phone = null, cashoutPayto = null, - tanChannel = null + tanChannel = null, + ctx = cfg.payto )) - assertEquals(AccountCreationResult.Success, db.account.create( + assertIs<AccountCreationResult.Success>(db.account.create( login = "exchange", password = "exchange-password", name = "Exchange", @@ -110,9 +112,10 @@ fun bankSetup( email = null, phone = null, cashoutPayto = null, - tanChannel = null + tanChannel = null, + ctx = cfg.payto )) - assertEquals(AccountCreationResult.Success, db.account.create( + assertIs<AccountCreationResult.Success>(db.account.create( login = "customer", password = "customer-password", name = "Customer", @@ -125,15 +128,16 @@ fun bankSetup( email = null, phone = null, cashoutPayto = null, - tanChannel = null + tanChannel = null, + ctx = cfg.payto )) // Create admin account - assertEquals(AccountCreationResult.Success, createAdminAccount(db, ctx, "admin-password")) + assertIs<AccountCreationResult.Success>(createAdminAccount(db, cfg, "admin-password")) testApplication { application { - corebankWebApp(db, ctx) + corebankWebApp(db, cfg) } - if (ctx.allowConversion) { + if (cfg.allowConversion) { // Set conversion rates client.post("/conversion-info/conversion-rate") { pwAuth("admin") |