summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-13 21:17:00 +0100
committerAntoine A <>2024-03-13 21:17:00 +0100
commitc481ebac54b747ebb56b09c1430755bc4a70f3e5 (patch)
treeb285e1e3f8226709bd5e5de4b3a348a346d899b6
parentd5d5035d3740104acb38b1b0f95c1305f78bfd95 (diff)
downloadlibeufin-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.kt11
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt19
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt27
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/helpers.kt3
-rw-r--r--bank/src/test/kotlin/DatabaseTest.kt5
-rw-r--r--bank/src/test/kotlin/helpers.kt24
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")