libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

commit d8709085d570041b82737a7d6c05694e82a267a3
parent a657794c0861a0000d073193328d810e2f6b0f8e
Author: Antoine A <>
Date:   Fri, 29 Dec 2023 08:55:58 +0000

Add operation kind

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/Main.kt | 1+
Mbank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt | 4++++
Mbank/src/main/kotlin/tech/libeufin/bank/Tan.kt | 3++-
Mbank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt | 14++++++++------
Mbank/src/test/kotlin/DatabaseTest.kt | 2+-
Mdatabase-versioning/libeufin-bank-0002.sql | 7++++++-
Mdatabase-versioning/libeufin-bank-procedures.sql | 3+++
8 files changed, 26 insertions(+), 10 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/CoreBankApi.kt @@ -278,7 +278,7 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { val res = patchAccount(db, ctx, req, username, isAdmin, is2fa) when (res) { AccountPatchResult.Success -> call.respond(HttpStatusCode.NoContent) - AccountPatchResult.TanRequired -> call.respondChallenge(db, req) + AccountPatchResult.TanRequired -> call.respondChallenge(db, Operation.account_reconfig, req) AccountPatchResult.UnknownAccount -> throw unknownAccount(username) AccountPatchResult.NonAdminName -> throw conflict( "non-admin user cannot change their legal name", diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt @@ -140,6 +140,7 @@ fun Application.corebankWebApp(db: Database, ctx: BankConfig) { } install(StatusPages) { exception<Exception> { call, cause -> + logger.debug("request failed", cause) when (cause) { is LibeufinException -> call.err(cause) is SQLException -> { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt b/bank/src/main/kotlin/tech/libeufin/bank/TalerMessage.kt @@ -72,6 +72,10 @@ enum class Timeframe { year } +enum class Operation { + account_reconfig +} + @Serializable(with = Option.Serializer::class) sealed class Option<out T> { object None : Option<Nothing>() diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Tan.kt b/bank/src/main/kotlin/tech/libeufin/bank/Tan.kt @@ -29,11 +29,12 @@ import io.ktor.server.response.* import io.ktor.server.application.* -inline suspend fun <reified B> ApplicationCall.respondChallenge(db: Database, body: B) { +inline suspend fun <reified B> ApplicationCall.respondChallenge(db: Database, op: Operation, body: B) { val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), body); val code = Tan.genCode() val id = db.tan.new( login = username, + op = op, body = json, code = code, now = Instant.now(), diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/TanDAO.kt @@ -30,19 +30,21 @@ class TanDAO(private val db: Database) { /** Create new TAN challenge */ suspend fun new( login: String, + op: Operation, body: String, code: String, now: Instant, retryCounter: Int, validityPeriod: Duration ): Long = db.serializable { conn -> - val stmt = conn.prepareStatement("SELECT tan_challenge_create(?, ?, ?, ?, ?, ?, NULL, NULL)") + val stmt = conn.prepareStatement("SELECT tan_challenge_create(?, ?::op_enum, ?, ?, ?, ?, ?, NULL, NULL)") stmt.setString(1, body) - stmt.setString(2, code) - stmt.setLong(3, now.toDbMicros() ?: throw faultyTimestampByBank()) - stmt.setLong(4, TimeUnit.MICROSECONDS.convert(validityPeriod)) - stmt.setInt(5, retryCounter) - stmt.setString(6, login) + stmt.setString(2, op.name) + stmt.setString(3, code) + stmt.setLong(4, now.toDbMicros() ?: throw faultyTimestampByBank()) + stmt.setLong(5, TimeUnit.MICROSECONDS.convert(validityPeriod)) + stmt.setInt(6, retryCounter) + stmt.setString(7, login) stmt.oneOrNull { it.getLong(1) }!! // TODO handle database weirdness diff --git a/bank/src/test/kotlin/DatabaseTest.kt b/bank/src/test/kotlin/DatabaseTest.kt @@ -168,7 +168,7 @@ class DatabaseTest { @Test fun tanChallenge() = bankSetup { db -> db.conn { conn -> - val createStmt = conn.prepareStatement("SELECT tan_challenge_create('',?,?,?,?,'customer',NULL,NULL)") + val createStmt = conn.prepareStatement("SELECT tan_challenge_create('','account_reconfig'::op_enum,?,?,?,?,'customer',NULL,NULL)") val markSentStmt = conn.prepareStatement("SELECT tan_challenge_mark_sent(?,?,?)") val tryStmt = conn.prepareStatement("SELECT out_ok, out_no_retry, out_expired FROM tan_challenge_try(?,'customer',?,?)") val sendStmt = conn.prepareStatement("SELECT out_tan_code FROM tan_challenge_send(?,'customer',?,?,?,?)") diff --git a/database-versioning/libeufin-bank-0002.sql b/database-versioning/libeufin-bank-0002.sql @@ -24,9 +24,13 @@ SET search_path TO libeufin_bank; ALTER TABLE customers ADD tan_channel tan_enum NULL; -CREATE TABLE tan_challenges -- TODO add op_enum as body is not enough to differenciate operation kind +CREATE TYPE op_enum + AS ENUM ('account_reconfig'); + +CREATE TABLE tan_challenges (challenge_id INT8 GENERATED BY DEFAULT AS IDENTITY UNIQUE ,body TEXT NOT NULL + ,op op_enum NOT NULL ,code TEXT NOT NULL ,creation_date INT8 NOT NULL ,expiration_date INT8 NOT NULL @@ -41,6 +45,7 @@ CREATE TABLE tan_challenges -- TODO add op_enum as body is not enough to differe ,tan_info TEXT NULL DEFAULT NULL ); COMMENT ON TABLE tan_challenges IS 'Stores 2FA challenges'; +COMMENT ON COLUMN tan_challenges.op IS 'The protected operation to run after the challenge'; COMMENT ON COLUMN tan_challenges.code IS 'The pin code sent to the user and verified'; COMMENT ON COLUMN tan_challenges.creation_date IS 'Creation date of the code'; COMMENT ON COLUMN tan_challenges.retransmission_date IS 'When did we last transmit the challenge to the user'; diff --git a/database-versioning/libeufin-bank-procedures.sql b/database-versioning/libeufin-bank-procedures.sql @@ -1306,6 +1306,7 @@ COMMENT ON FUNCTION challenge_try IS 'Try to confirm a challenge, return true if CREATE FUNCTION tan_challenge_create ( IN in_body TEXT, + IN in_op op_enum, IN in_code TEXT, IN in_now_date INT8, IN in_validity_period INT8, @@ -1324,6 +1325,7 @@ SELECT customer_id INTO account_id FROM customers WHERE login = in_login; -- Create challenge INSERT INTO tan_challenges ( body, + op, code, creation_date, expiration_date, @@ -1333,6 +1335,7 @@ INSERT INTO tan_challenges ( tan_info ) VALUES ( in_body, + in_op, in_code, in_now_date, in_now_date + in_validity_period,