libeufin

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

commit 2e2101a2dacbaf985fe0a57bbce7a14064c5704b
parent 8bf176ecece5aa0cfcccc63828c5b274f58758b8
Author: Antoine A <>
Date:   Tue, 22 Oct 2024 16:48:12 +0200

bank: check password length

Diffstat:
Mbank/src/main/kotlin/tech/libeufin/bank/Constants.kt | 2+-
Mbank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt | 9+++++++--
Mbank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt | 2++
Mbank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt | 6+++---
Mbank/src/test/kotlin/CoreBankApiTest.kt | 30++++++++++++++++++++++++++++--
Mcommon/src/main/kotlin/TalerErrorCode.kt | 338++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcommon/src/main/kotlin/crypto/PwCrypto.kt | 27++++++++++++++++++++++++---
7 files changed, 402 insertions(+), 12 deletions(-)

diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt b/bank/src/main/kotlin/tech/libeufin/bank/Constants.kt @@ -37,6 +37,6 @@ val RESERVED_ACCOUNTS = setOf("admin", "bank") const val IBAN_ALLOCATION_RETRY_COUNTER: Int = 5 // API version -const val COREBANK_API_VERSION: String = "5:1:2" +const val COREBANK_API_VERSION: String = "5:2:2" const val CONVERSION_API_VERSION: String = "0:1:0" const val INTEGRATION_API_VERSION: String = "4:0:4" diff --git a/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt b/bank/src/main/kotlin/tech/libeufin/bank/api/CoreBankApi.kt @@ -40,6 +40,7 @@ import tech.libeufin.bank.db.TransactionDAO.BankTransactionResult import tech.libeufin.bank.db.WithdrawalDAO.WithdrawalConfirmationResult import tech.libeufin.bank.db.WithdrawalDAO.WithdrawalCreationResult import tech.libeufin.common.* +import tech.libeufin.common.crypto.* import java.time.Duration import java.time.Instant import java.time.temporal.ChronoUnit @@ -206,6 +207,8 @@ suspend fun createAccount( "'exchange' account must be a taler exchange account", TalerErrorCode.END ) + + val password = req.password.checkPw() suspend fun doDb(internalPayto: Payto) = db.account.create( username = req.username, @@ -213,7 +216,7 @@ suspend fun createAccount( email = req.contact_data?.email?.get(), phone = req.contact_data?.phone?.get(), cashoutPayto = req.cashout_payto_uri, - password = req.password, + password = password.pw, internalPayto = internalPayto, isPublic = req.is_public, isTalerExchange = req.is_taler_exchange, @@ -398,7 +401,9 @@ private fun Routing.coreBankAccountsApi(db: Database, ctx: BankConfig) { TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD ) } - when (db.account.reconfigPassword(call.username, req.new_password, req.old_password, call.isAdmin || challenge != null, ctx.pwCrypto)) { + val newPassword = req.new_password.checkPw() + + when (db.account.reconfigPassword(call.username, newPassword, req.old_password, call.isAdmin || challenge != null, ctx.pwCrypto)) { AccountPatchAuthResult.Success -> call.respond(HttpStatusCode.NoContent) AccountPatchAuthResult.TanRequired -> call.respondChallenge(db, Operation.account_auth_reconfig, req) AccountPatchAuthResult.UnknownAccount -> throw unknownAccount(call.username) diff --git a/bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt b/bank/src/main/kotlin/tech/libeufin/bank/cli/ChangePw.kt @@ -30,6 +30,7 @@ import tech.libeufin.bank.logger import tech.libeufin.bank.withDb import tech.libeufin.common.CommonOption import tech.libeufin.common.cliCmd +import tech.libeufin.common.crypto.checkPw import com.github.ajalt.mordant.terminal.* class ChangePw : CliktCommand("passwd") { @@ -60,6 +61,7 @@ class ChangePw : CliktCommand("passwd") { ).ask()!! } override fun run() = cliCmd(logger, common.log) { + val password = password.checkPw() bankConfig(common.config).withDb { db, cfg -> val res = db.account.reconfigPassword(username, password, null, true, cfg.pwCrypto) when (res) { diff --git a/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt b/bank/src/main/kotlin/tech/libeufin/bank/db/AccountDAO.kt @@ -21,7 +21,7 @@ package tech.libeufin.bank.db import tech.libeufin.bank.* import tech.libeufin.common.* -import tech.libeufin.common.crypto.PwCrypto +import tech.libeufin.common.crypto.* import tech.libeufin.common.db.* import java.time.Instant @@ -419,7 +419,7 @@ class AccountDAO(private val db: Database) { /** Change account [username] password to [newPw] if current match [oldPw] */ suspend fun reconfigPassword( username: String, - newPw: String, + newPw: Password, oldPw: String?, is2fa: Boolean, pwCrypto: PwCrypto @@ -439,7 +439,7 @@ class AccountDAO(private val db: Database) { } else if (oldPw != null && !pwCrypto.checkpw(oldPw, currentPwh).match) { AccountPatchAuthResult.OldPasswordMismatch } else { - val newPwh = pwCrypto.hashpw(newPw) + val newPwh = pwCrypto.hashpw(newPw.pw) conn.withStatement("UPDATE customers SET password_hash=? WHERE customer_id=?") { setString(1, newPwh) setLong(2, customerId) diff --git a/bank/src/test/kotlin/CoreBankApiTest.kt b/bank/src/test/kotlin/CoreBankApiTest.kt @@ -430,6 +430,18 @@ class CoreBankAccountsApiTest { "payto_uri" to "payto://x-taler-bank/bank.hostname.test/bar" } }.assertBadRequest() + // Testing short password + client.post("/accounts") { + json(req) { + "password" to "short" + } + }.assertConflict(TalerErrorCode.BANK_PASSWORD_TOO_SHORT) + // Testing long password + client.post("/accounts") { + json(req) { + "password" to "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong-password" + } + }.assertConflict(TalerErrorCode.BANK_PASSWORD_TOO_LONG) // Check cashout payto receiver name logic client.post("/accounts") { @@ -490,7 +502,7 @@ class CoreBankAccountsApiTest { fun createBonus() = bankSetup(conf = "test_bonus.conf") { val req = obj { "username" to "foo" - "password" to "xyz" + "password" to "password-xyz" "name" to "Mallory" } @@ -528,7 +540,7 @@ class CoreBankAccountsApiTest { pwAuth("admin") json { "username" to "baz" - "password" to "xyz" + "password" to "password-xyz" "name" to "Mallory" } }.assertOk() @@ -895,6 +907,20 @@ class CoreBankAccountsApiTest { "new_password" to "new-password" } }.assertConflict(TalerErrorCode.BANK_NON_ADMIN_PATCH_MISSING_OLD_PASSWORD) + // Testing short password + client.patchA("/accounts/merchant/auth") { + json { + "old_password" to "ignored" + "new_password" to "short" + } + }.assertConflict(TalerErrorCode.BANK_PASSWORD_TOO_SHORT) + // Testing long password + client.patchA("/accounts/merchant/auth") { + json { + "old_password" to "ignored" + "new_password" to "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong-password" + } + }.assertConflict(TalerErrorCode.BANK_PASSWORD_TOO_LONG) // Check admin client.patch("/accounts/customer/auth") { diff --git a/common/src/main/kotlin/TalerErrorCode.kt b/common/src/main/kotlin/TalerErrorCode.kt @@ -50,6 +50,14 @@ enum class TalerErrorCode(val code: Int) { /** + * The client does not support the protocol version advertised by the server. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_CLIENT_UNSUPPORTED_PROTOCOL_VERSION(3), + + + /** * The response we got from the server was not in the expected format. Most likely, the server does not speak the GNU Taler protocol. Check the URL and/or the network connection to the server. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). @@ -178,6 +186,14 @@ enum class TalerErrorCode(val code: Int) { /** + * A segment in the path of the URL provided by the client is malformed. Check that you are using the correct encoding for the URL. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + GENERIC_PATH_SEGMENT_MALFORMED(29), + + + /** * The currency involved in the operation is not acceptable for this server. Check your configuration and make sure the currency specified for a given service provider is one of the currencies supported by that provider. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -674,6 +690,38 @@ enum class TalerErrorCode(val code: Int) { /** + * The KYC operation failed. This could be because the KYC provider rejected the KYC data provided, or because the user aborted the KYC process. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_GENERIC_KYC_FAILED(1038), + + + /** + * A fallback measure for a KYC operation failed. This is a bug. Users should contact the exchange operator. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_GENERIC_KYC_FALLBACK_FAILED(1039), + + + /** + * The specified fallback measure for a KYC operation is unknown. This is a bug. Users should contact the exchange operator. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_GENERIC_KYC_FALLBACK_UNKNOWN(1040), + + + /** + * The exchange is not aware of the bank account (payto URI or hash thereof) specified in the request and thus cannot perform the requested operation. The client should check that the select account is correct. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN(1041), + + + /** * The exchange did not find information about the specified transaction in the database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -1906,6 +1954,54 @@ enum class TalerErrorCode(val code: Int) { /** + * The KYC info access token is not recognized. Hence the request was denied. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_INFO_AUTHORIZATION_FAILED(1919), + + + /** + * The exchange got stuck in a long series of (likely recursive) KYC rules without user-inputs that did not result in a timely conclusion. This is a configuration failure. Please contact the administrator. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_RECURSIVE_RULE_DETECTED(1920), + + + /** + * The submitted KYC data lacks an attribute that is required by the KYC form. Please submit the complete form. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_AML_FORM_INCOMPLETE(1921), + + + /** + * The request requires an AML program which is no longer configured at the exchange. Contact the exchange operator to address the configuration issue. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_GENERIC_AML_PROGRAM_GONE(1922), + + + /** + * The given check is not of type 'form' and thus using this handler for form submission is incorrect. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_NOT_A_FORM(1923), + + + /** + * The request requires a check which is no longer configured at the exchange. Contact the exchange operator to address the configuration issue. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_GENERIC_CHECK_GONE(1924), + + + /** * The signature affirming the wallet's KYC request was invalid. * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). * (A value of 0 indicates that the error is generated client-side). @@ -2018,7 +2114,7 @@ enum class TalerErrorCode(val code: Int) { /** - * The exchange is unaware of the given requirement row. + * The exchange is unaware of the requested payto URI with respect to the KYC status. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). */ @@ -2034,6 +2130,78 @@ enum class TalerErrorCode(val code: Int) { /** + * The form has been previously uploaded, and may only be filed once. The user should be redirected to their main KYC page and see if any other steps need to be taken. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_FORM_ALREADY_UPLOADED(1941), + + + /** + * The internal state of the exchange specifying KYC measures is malformed. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_MEASURES_MALFORMED(1942), + + + /** + * The specified index does not refer to a valid KYC measure. Please check the URL. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_MEASURE_INDEX_INVALID(1943), + + + /** + * The operation is not supported by the selected KYC logic. This is either caused by a configuration change or some invalid use of the API. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_INVALID_LOGIC_TO_CHECK(1944), + + + /** + * The AML program failed. This is either caused by a configuration change or a bug. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_AML_PROGRAM_FAILURE(1945), + + + /** + * The AML program returned a malformed result. This is a bug. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_AML_PROGRAM_MALFORMED_RESULT(1946), + + + /** + * The response from the KYC provider lacked required attributes. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_GENERIC_PROVIDER_INCOMPLETE_REPLY(1947), + + + /** + * The context of the KYC check lacked required fields. This is a bug. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_GENERIC_PROVIDER_INCOMPLETE_CONTEXT(1948), + + + /** + * The logic plugin had a bug in its AML processing. This is a bug. Please contact technical support. + * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR (500). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG(1949), + + + /** * The exchange does not know a contract under the given contract public key. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -2090,6 +2258,14 @@ enum class TalerErrorCode(val code: Int) { /** + * The exchange is currently processing the KYC status and is not able to return a response yet. + * Returned with an HTTP status code of #MHD_HTTP_ACCEPTED (202). + * (A value of 0 indicates that the error is generated client-side). + */ + EXCHANGE_KYC_INFO_BUSY(1977), + + + /** * TOTP key is not valid. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). @@ -2122,6 +2298,14 @@ enum class TalerErrorCode(val code: Int) { /** + * The product category is not known to the backend. + * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_GENERIC_CATEGORY_UNKNOWN(2003), + + + /** * The proposal is not known to the backend. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -2282,6 +2466,14 @@ enum class TalerErrorCode(val code: Int) { /** + * The exchange specified in the operation is not trusted by this exchange. The client should limit its operation to exchanges enabled by the merchant, or ask the merchant to enable additional exchanges in the configuration. + * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_GENERIC_EXCHANGE_UNTRUSTED(2025), + + + /** * The exchange failed to provide a valid answer to the tracking request, thus those details are not in the response. * Returned with an HTTP status code of #MHD_HTTP_OK (200). * (A value of 0 indicates that the error is generated client-side). @@ -2578,6 +2770,22 @@ enum class TalerErrorCode(val code: Int) { /** + * Invalid token because it was already used, is expired or not yet valid. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_PAY_TOKEN_INVALID(2183), + + + /** + * The payment violates a transaction limit configured at the given exchange. The wallet has a bug in that it failed to check exchange limits during coin selection. Please report the bug to your wallet developer. + * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_PAY_EXCHANGE_TRANSACTION_LIMIT_VIOLATION(2184), + + + /** * The contract hash does not match the given order ID. * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400). * (A value of 0 indicates that the error is generated client-side). @@ -2890,6 +3098,22 @@ enum class TalerErrorCode(val code: Int) { /** + * The refund amount would violate a refund transaction limit configured at the given exchange. Please find another way to refund the customer, and inquire with your legislator why they make strange banking regulations. + * Returned with an HTTP status code of #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS (451). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_POST_ORDERS_ID_REFUND_EXCHANGE_TRANSACTION_LIMIT_VIOLATION(2512), + + + /** + * The total order amount exceeds hard legal transaction limits from the available exchanges, thus a customer could never legally make this payment. You may try to increase your limits by passing legitimization checks with exchange operators. You could also inquire with your legislator why the limits are prohibitively low for your business. + * Returned with an HTTP status code of #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS (451). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_PRIVATE_POST_ORDERS_AMOUNT_EXCEEDS_LEGAL_LIMITS(2513), + + + /** * The order provided to the backend could not be deleted, our offer is still valid and awaiting payment. Deletion may work later after the offer has expired if it remains unpaid. * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). * (A value of 0 indicates that the error is generated client-side). @@ -2938,6 +3162,14 @@ enum class TalerErrorCode(val code: Int) { /** + * A token family referenced in this order is either expired or not valid yet. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_PRIVATE_POST_ORDERS_TOKEN_FAMILY_NOT_VALID(2534), + + + /** * The exchange says it does not know this transfer. * Returned with an HTTP status code of #MHD_HTTP_BAD_GATEWAY (502). * (A value of 0 indicates that the error is generated client-side). @@ -3074,6 +3306,14 @@ enum class TalerErrorCode(val code: Int) { /** + * A category with the same name exists already. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + MERCHANT_PRIVATE_POST_CATEGORIES_CONFLICT_CATEGORY_EXISTS(2651), + + + /** * The update would have reduced the total amount of product lost, which is not allowed. * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). * (A value of 0 indicates that the error is generated client-side). @@ -3250,6 +3490,22 @@ enum class TalerErrorCode(val code: Int) { /** + * The auditor refused the connection due to a lack of authorization. + * Returned with an HTTP status code of #MHD_HTTP_UNAUTHORIZED (401). + * (A value of 0 indicates that the error is generated client-side). + */ + AUDITOR_GENERIC_UNAUTHORIZED(3001), + + + /** + * This method is not allowed here. + * Returned with an HTTP status code of #MHD_HTTP_METHOD_NOT_ALLOWED (405). + * (A value of 0 indicates that the error is generated client-side). + */ + AUDITOR_GENERIC_METHOD_NOT_ALLOWED(3002), + + + /** * The signature from the exchange on the deposit confirmation is invalid. * Returned with an HTTP status code of #MHD_HTTP_FORBIDDEN (403). * (A value of 0 indicates that the error is generated client-side). @@ -3666,6 +3922,22 @@ enum class TalerErrorCode(val code: Int) { /** + * Provided password is too short. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_PASSWORD_TOO_SHORT(5150), + + + /** + * Provided password is too long. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + BANK_PASSWORD_TOO_LONG(5151), + + + /** * The sync service failed find the account in its database. * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404). * (A value of 0 indicates that the error is generated client-side). @@ -4082,6 +4354,62 @@ enum class TalerErrorCode(val code: Int) { /** + * A wallet-core request failed because the user needs to first accept the exchange's terms of service. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_EXCHANGE_TOS_NOT_ACCEPTED(7037), + + + /** + * An exchange entry could not be updated, as the exchange's new details conflict with the new details. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_EXCHANGE_ENTRY_UPDATE_CONFLICT(7038), + + + /** + * The wallet's information about the exchange is outdated. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_EXCHANGE_ENTRY_OUTDATED(7039), + + + /** + * The merchant needs to do KYC first, the payment could not be completed. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_PAY_MERCHANT_KYC_MISSING(7040), + + + /** + * A peer-pull-debit transaction was aborted because the exchange reported the purse as gone. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_PEER_PULL_DEBIT_PURSE_GONE(7041), + + + /** + * A transaction was aborted on explicit request by the user. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_TRANSACTION_ABORTED_BY_USER(7042), + + + /** + * A transaction was abandoned on explicit request by the user. + * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). + * (A value of 0 indicates that the error is generated client-side). + */ + WALLET_TRANSACTION_ABANDONED_BY_USER(7043), + + + /** * We encountered a timeout with our payment backend. * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504). * (A value of 0 indicates that the error is generated client-side). @@ -4690,6 +5018,14 @@ enum class TalerErrorCode(val code: Int) { /** + * The client re-used a unique donor identifier nonce, which is not allowed. + * Returned with an HTTP status code of #MHD_HTTP_CONFLICT (409). + * (A value of 0 indicates that the error is generated client-side). + */ + DONAU_DONOR_IDENTIFIER_NONCE_REUSE(8617), + + + /** * A generic error happened in the LibEuFin nexus. See the enclose details JSON for more information. * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0). * (A value of 0 indicates that the error is generated client-side). diff --git a/common/src/main/kotlin/crypto/PwCrypto.kt b/common/src/main/kotlin/crypto/PwCrypto.kt @@ -20,9 +20,30 @@ package tech.libeufin.common.crypto import kotlinx.serialization.Serializable -import tech.libeufin.common.decodeBase64 -import tech.libeufin.common.encodeBase64 -import tech.libeufin.common.secureRand +import tech.libeufin.common.* + +@JvmInline +value class Password(val pw: String) + +// NIST Password Guidelines 2024 +private const val PASSWORD_MIN_LEN = 8 +private const val PASSWORD_MAX_LEN = 64 + +/** Check if a string is a valid password */ +fun String.checkPw(): Password { + val len = this.length + return when { + len < PASSWORD_MIN_LEN -> throw conflict( + "Password is too short, expect at least ${PASSWORD_MIN_LEN} characters got ${len}", + TalerErrorCode.BANK_PASSWORD_TOO_SHORT + ) + len > PASSWORD_MAX_LEN -> throw conflict( + "Password is too long, expect at most ${PASSWORD_MAX_LEN} characters got ${len}", + TalerErrorCode.BANK_PASSWORD_TOO_LONG + ) + else -> Password(this) + } +} data class PasswordHashCheck( val match: Boolean,