taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit d5b68895e9545d3482eaf7ac5f38069dcb111a3a
parent 13c6ede78f2ecdb1336f5b5863fc52c3ea123752
Author: Florian Dold <dold@taler.net>
Date:   Thu, 30 Jul 2026 14:19:01 +0200

wallet-core: reject replacing a bank account that does not exist

An unknown replaceBankAccountId silently created an account under the
client's ID instead of failing, so a stale ID in the client went unnoticed.

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-known-accounts.ts | 28+++++++++++++++++++++++++++-
Mpackages/taler-wallet-core/src/requests.ts | 11+++++++++++
Mpackages/taler-wallet-core/src/wallet-api-types.ts | 1+
3 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/packages/taler-harness/src/integrationtests/test-known-accounts.ts b/packages/taler-harness/src/integrationtests/test-known-accounts.ts @@ -17,7 +17,11 @@ /** * Imports. */ -import { j2s, TalerCorebankApiClient } from "@gnu-taler/taler-util"; +import { + j2s, + TalerCorebankApiClient, + TalerErrorCode, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { useSharedTestkudosEnvironment, @@ -122,6 +126,28 @@ export async function runKnownAccountsTest(t: GlobalTestState) { console.log(`accounts after replace: ${j2s(accts2)}`); t.assertDeepEqual(accts2.accounts.length, 3); } + + // Replacing an account that does not exist must fail instead of creating + // one under the requested ID. + { + const err = await t.assertThrowsTalerErrorAsync(async () => + walletClient.call(WalletApiOperation.AddBankAccount, { + replaceBankAccountId: "acct:does-not-exist", + paytoUri: "payto://iban/CH4308307000289537312", + label: "Nope", + currencies: ["CHF"], + }), + ); + t.assertTrue( + err.errorDetail.code === TalerErrorCode.WALLET_BANK_ACCOUNT_NOT_FOUND, + ); + + const accts2 = await walletClient.call( + WalletApiOperation.ListBankAccounts, + {}, + ); + t.assertDeepEqual(accts2.accounts.length, 3); + } } runKnownAccountsTest.suites = ["wallet", "shared-env"]; diff --git a/packages/taler-wallet-core/src/requests.ts b/packages/taler-wallet-core/src/requests.ts @@ -1041,6 +1041,17 @@ async function handleAddBankAccount( let myId: string; const oldAcct = await tx.getBankAccountByPaytoUri(req.paytoUri); if (req.replaceBankAccountId) { + // Without this check, replacing an account that does not exist would + // create one under the client's ID instead of failing, which hides a + // stale ID in the client and mints accounts with foreign ID formats. + const toReplace = await tx.getBankAccount(req.replaceBankAccountId); + if (!toReplace) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_BANK_ACCOUNT_NOT_FOUND, + { bankAccountId: req.replaceBankAccountId }, + `bank account ${req.replaceBankAccountId} not found, cannot replace it`, + ); + } myId = req.replaceBankAccountId; } else if (oldAcct) { myId = oldAcct.bankAccountId; diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -1887,6 +1887,7 @@ export const walletApiExpectedErrors = { ], [WalletApiOperation.AddBankAccount]: [ TalerErrorCode.GENERIC_PAYTO_URI_MALFORMED, + TalerErrorCode.WALLET_BANK_ACCOUNT_NOT_FOUND, ], // --- Tokens (discounts and subscriptions) -------------------------------