taler-typescript-core

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

commit c86e783708b7703651711bd5d4b65589ce5b7461
parent d1ca613fc7a06d66d9c53277a30b15a16b5195b0
Author: Florian Dold <florian@dold.me>
Date:   Mon,  9 Dec 2024 20:36:26 +0100

harness: do kycauth in merchant test

Diffstat:
Mpackages/taler-harness/src/harness/harness.ts | 103+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts | 208+++++++++++--------------------------------------------------------------------
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts | 22+++++++++++++++++++---
Mpackages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts | 51+++++++++++++++++++++++++++------------------------
Mpackages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts | 4+---
Mpackages/taler-harness/src/integrationtests/test-wallet-dd48.ts | 3++-
Mpackages/taler-util/src/types-taler-wallet.ts | 21+++++++++++----------
Mpackages/taler-wallet-core/src/wallet.ts | 8+++++---
8 files changed, 196 insertions(+), 224 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts @@ -33,6 +33,7 @@ import { Duration, EddsaKeyPair, Logger, + MerchantAccountKycRedirectsResponse, MerchantInstanceConfig, PartialMerchantInstanceConfig, PaytoString, @@ -43,6 +44,9 @@ import { TalerMerchantApi, TalerMerchantManagementHttpClient, WalletNotification, + WireGatewayApiClient, + codecForAccountKycRedirects, + codecForQueryInstancesResponse, createEddsaKeyPair, eddsaGetPublic, encodeCrock, @@ -56,6 +60,7 @@ import { HttpRequestLibrary, createPlatformHttpLib, expectSuccessResponseOrThrow, + readSuccessResponseJsonOrThrow, } from "@gnu-taler/taler-util/http"; import { WalletCoreApiClient, @@ -2488,3 +2493,101 @@ export function waitMs(tMs: number): Promise<void> { setTimeout(() => resolve(), tMs); }); } + +export async function doMerchantKycAuth( + t: GlobalTestState, + req: { + exchangeBankAccount: HarnessExchangeBankAccount; + merchant: MerchantServiceInterface; + bankClient: TalerCorebankApiClient; + }, +): Promise<void> { + const { merchant, bankClient } = req; + + let accountPub: string; + + { + const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl()); + const resp = await harnessHttpLib.fetch(instanceUrl.href); + const parsedResp = await readSuccessResponseJsonOrThrow( + resp, + codecForQueryInstancesResponse(), + ); + accountPub = parsedResp.merchant_pub; + } + + const wireGatewayApiClient = new WireGatewayApiClient( + req.exchangeBankAccount.wireGatewayApiBaseUrl, + { + auth: { + username: req.exchangeBankAccount.accountName, + password: req.exchangeBankAccount.accountPassword, + }, + }, + ); + + let kycRespOne: MerchantAccountKycRedirectsResponse | undefined = undefined; + + while (1) { + const kycStatusUrl = new URL("private/kyc", merchant.makeInstanceBaseUrl()) + .href; + logger.info(`requesting GET ${kycStatusUrl}`); + const resp = await harnessHttpLib.fetch(kycStatusUrl); + if (resp.status === 200) { + kycRespOne = await readSuccessResponseJsonOrThrow( + resp, + codecForAccountKycRedirects(), + ); + break; + } + // Wait 500ms + await delayMs(500); + } + + t.assertTrue(!!kycRespOne); + + logger.info(`mechant kyc status: ${j2s(kycRespOne)}`); + + t.assertDeepEqual(kycRespOne.kyc_data[0].exchange_http_status, 404); + + t.assertTrue(!!kycRespOne); + + await bankClient.registerAccountExtended({ + name: "merchant-default", + password: "merchant-default", + username: "merchant-default", + payto_uri: kycRespOne.kyc_data[0].payto_uri, //this bank user needs to have the same payto that the exchange is asking from + }); + await wireGatewayApiClient.adminAddKycauth({ + amount: "TESTKUDOS:0.1", + debitAccountPayto: kycRespOne.kyc_data[0].payto_uri, + accountPub, + }); + + let kycRespTwo: MerchantAccountKycRedirectsResponse | undefined = undefined; + + // We do this in a loop as a work-around. + // Not exactly the correct behavior from the merchant right now. + while (true) { + const kycStatusLongpollUrl = new URL( + "private/kyc", + merchant.makeInstanceBaseUrl(), + ); + kycStatusLongpollUrl.searchParams.set("lpt", "1"); + const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href); + t.assertDeepEqual(resp.status, 200); + const parsedResp = await readSuccessResponseJsonOrThrow( + resp, + codecForAccountKycRedirects(), + ); + logger.info(`kyc resp 2: ${j2s(parsedResp)}`); + if (parsedResp.kyc_data[0].payto_kycauths == null) { + kycRespTwo = parsedResp; + break; + } + // Wait 500ms + await delayMs(500); + } + + t.assertTrue(!!kycRespTwo); +} diff --git a/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-deposit-deposit.ts @@ -18,203 +18,51 @@ * Imports. */ import { + Configuration, Logger, - TalerCorebankApiClient, TransactionMajorState, TransactionMinorState, } from "@gnu-taler/taler-util"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { - createSyncCryptoApi, - EddsaKeyPairStrings, - WalletApiOperation, -} from "@gnu-taler/taler-wallet-core"; -import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; -import { - BankService, - DbInfo, - ExchangeService, - getTestHarnessPaytoForLabel, - GlobalTestState, - HarnessExchangeBankAccount, - setupDb, - WalletClient, - WalletService, -} from "../harness/harness.js"; -import { - EnvOptions, + createKycTestkudosEnvironment, postAmlDecisionNoRules, withdrawViaBankV3, } from "../harness/environments.js"; +import { GlobalTestState } from "../harness/harness.js"; const logger = new Logger("test-kyc-deposit-deposit.ts"); -interface KycTestEnv { - commonDb: DbInfo; - bankClient: TalerCorebankApiClient; - exchange: ExchangeService; - exchangeBankAccount: HarnessExchangeBankAccount; - walletClient: WalletClient; - walletService: WalletService; - amlKeypair: EddsaKeyPairStrings; -} - -async function createKycTestkudosEnvironment( - t: GlobalTestState, - coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")), - opts: EnvOptions = {}, -): Promise<KycTestEnv> { - const db = await setupDb(t); - - const bank = await BankService.create(t, { - allowRegistrations: true, - currency: "TESTKUDOS", - database: db.connStr, - httpPort: 8082, - }); - - const exchange = ExchangeService.create(t, { - name: "testexchange-1", - currency: "TESTKUDOS", - httpPort: 8081, - database: db.connStr, - }); - - let receiverName = "Exchange"; - let exchangeBankUsername = "exchange"; - let exchangeBankPassword = "mypw-password"; - let exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername); - - await exchange.addBankAccount("1", { - accountName: exchangeBankUsername, - accountPassword: exchangeBankPassword, - wireGatewayApiBaseUrl: new URL( - "accounts/exchange/taler-wire-gateway/", - bank.baseUrl, - ).href, - accountPaytoUri: exchangePaytoUri, - }); - - bank.setSuggestedExchange(exchange, exchangePaytoUri); - - await bank.start(); - - await bank.pingUntilAvailable(); - - const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, { - auth: { - username: "admin", - password: "admin-password", - }, - }); - - await bankClient.registerAccountExtended({ - name: receiverName, - password: exchangeBankPassword, - username: exchangeBankUsername, - is_taler_exchange: true, - payto_uri: exchangePaytoUri, - }); - - const ageMaskSpec = opts.ageMaskSpec; - - if (ageMaskSpec) { - exchange.enableAgeRestrictions(ageMaskSpec); - // Enable age restriction for all coins. - exchange.addCoinConfigList( - coinConfig.map((x) => ({ - ...x, - name: `${x.name}-age`, - ageRestricted: true, - })), - ); - // For mixed age restrictions, we also offer coins without age restrictions - if (opts.mixedAgeRestriction) { - exchange.addCoinConfigList( - coinConfig.map((x) => ({ ...x, ageRestricted: false })), - ); - } - } else { - exchange.addCoinConfigList(coinConfig); - } - - await exchange.modifyConfig(async (config) => { - config.setString("exchange", "enable_kyc", "yes"); - - config.setString("KYC-RULE-R1", "operation_type", "deposit"); - config.setString("KYC-RULE-R1", "enabled", "yes"); - config.setString("KYC-RULE-R1", "exposed", "yes"); - config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); - config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5"); - config.setString("KYC-RULE-R1", "timeframe", "1d"); - config.setString("KYC-RULE-R1", "next_measures", "M1"); - - config.setString("KYC-MEASURE-M1", "check_name", "C1"); - config.setString("KYC-MEASURE-M1", "context", "{}"); - config.setString("KYC-MEASURE-M1", "program", "P1"); - - config.setString("AML-PROGRAM-P1", "command", "/bin/true"); - config.setString("AML-PROGRAM-P1", "enabled", "true"); - config.setString("AML-PROGRAM-P1", "description", "this does nothing"); - config.setString("AML-PROGRAM-P1", "fallback", "M1"); - - config.setString("KYC-CHECK-C1", "type", "INFO"); - config.setString("KYC-CHECK-C1", "description", "my check!"); - config.setString("KYC-CHECK-C1", "fallback", "M1"); - }); - - await exchange.start(); - - const cryptoApi = createSyncCryptoApi(); - const amlKeypair = await cryptoApi.createEddsaKeypair({}); - - await exchange.enableAmlAccount(amlKeypair.pub, "Alice"); - - const walletService = new WalletService(t, { - name: "wallet", - useInMemoryDb: true, - }); - await walletService.start(); - await walletService.pingUntilAvailable(); - - const walletClient = new WalletClient({ - name: "wallet", - unixPath: walletService.socketPath, - onNotification(n) { - console.log("got notification", n); - }, - }); - await walletClient.connect(); - await walletClient.client.call(WalletApiOperation.InitWallet, { - config: { - testing: { - skipDefaults: true, - }, - }, - }); - - console.log("setup done!"); - - return { - commonDb: db, - exchange, - amlKeypair, - walletClient, - walletService, - bankClient, - exchangeBankAccount: { - accountName: "", - accountPassword: "", - accountPaytoUri: "", - wireGatewayApiBaseUrl: "", - }, - }; +function adjustExchangeConfig(config: Configuration) { + config.setString("exchange", "enable_kyc", "yes"); + + config.setString("KYC-RULE-R1", "operation_type", "deposit"); + config.setString("KYC-RULE-R1", "enabled", "yes"); + config.setString("KYC-RULE-R1", "exposed", "yes"); + config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); + config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5"); + config.setString("KYC-RULE-R1", "timeframe", "1d"); + config.setString("KYC-RULE-R1", "next_measures", "M1"); + + config.setString("KYC-MEASURE-M1", "check_name", "C1"); + config.setString("KYC-MEASURE-M1", "context", "{}"); + config.setString("KYC-MEASURE-M1", "program", "P1"); + + config.setString("AML-PROGRAM-P1", "command", "/bin/true"); + config.setString("AML-PROGRAM-P1", "enabled", "true"); + config.setString("AML-PROGRAM-P1", "description", "this does nothing"); + config.setString("AML-PROGRAM-P1", "fallback", "M1"); + + config.setString("KYC-CHECK-C1", "type", "INFO"); + config.setString("KYC-CHECK-C1", "description", "my check!"); + config.setString("KYC-CHECK-C1", "fallback", "M1"); } export async function runKycDepositDepositTest(t: GlobalTestState) { // Set up test environment const { walletClient, bankClient, exchange, amlKeypair } = - await createKycTestkudosEnvironment(t); + await createKycTestkudosEnvironment(t, { adjustExchangeConfig }); // Withdraw digital cash into the wallet. diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-aggregate.ts @@ -23,7 +23,11 @@ import { makeTestPaymentV2, withdrawViaBankV3, } from "../harness/environments.js"; -import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; +import { + doMerchantKycAuth, + GlobalTestState, + harnessHttpLib, +} from "../harness/harness.js"; function adjustExchangeConfig(config: Configuration) { config.setString("exchange", "enable_kyc", "yes"); @@ -53,8 +57,14 @@ function adjustExchangeConfig(config: Configuration) { export async function runKycMerchantAggregateTest(t: GlobalTestState) { // Set up test environment - const { merchant, walletClient, bankClient, exchange, amlKeypair } = - await createKycTestkudosEnvironment(t, { adjustExchangeConfig }); + const { + merchant, + walletClient, + bankClient, + exchange, + amlKeypair, + exchangeBankAccount, + } = await createKycTestkudosEnvironment(t, { adjustExchangeConfig }); // Withdraw digital cash into the wallet. @@ -67,6 +77,12 @@ export async function runKycMerchantAggregateTest(t: GlobalTestState) { await wres.withdrawalFinishedCond; + await doMerchantKycAuth(t, { + bankClient, + exchangeBankAccount, + merchant, + }); + await makeTestPaymentV2(t, { merchant, walletClient, diff --git a/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts b/packages/taler-harness/src/integrationtests/test-kyc-merchant-deposit.ts @@ -21,6 +21,7 @@ import { codecForAccountKycRedirects, codecForKycProcessClientInformation, codecForQueryInstancesResponse, + Configuration, encodeCrock, hashNormalizedPaytoUri, j2s, @@ -46,6 +47,31 @@ import { const logger = new Logger(`test-kyc-merchant-deposit.ts`); +function adjustExchangeConfig(config: Configuration) { + config.setString("exchange", "enable_kyc", "yes"); + + config.setString("KYC-RULE-R1", "operation_type", "deposit"); + config.setString("KYC-RULE-R1", "enabled", "yes"); + config.setString("KYC-RULE-R1", "exposed", "yes"); + config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); + config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:0"); + config.setString("KYC-RULE-R1", "timeframe", "1d"); + config.setString("KYC-RULE-R1", "next_measures", "M1"); + + config.setString("KYC-MEASURE-M1", "check_name", "C1"); + config.setString("KYC-MEASURE-M1", "context", "{}"); + config.setString("KYC-MEASURE-M1", "program", "P1"); + + config.setString("AML-PROGRAM-P1", "command", "/bin/true"); + config.setString("AML-PROGRAM-P1", "enabled", "true"); + config.setString("AML-PROGRAM-P1", "description", "this does nothing"); + config.setString("AML-PROGRAM-P1", "fallback", "M1"); + + config.setString("KYC-CHECK-C1", "type", "INFO"); + config.setString("KYC-CHECK-C1", "description", "my check!"); + config.setString("KYC-CHECK-C1", "fallback", "M1"); +} + export async function runKycMerchantDepositTest(t: GlobalTestState) { // Set up test environment @@ -57,30 +83,7 @@ export async function runKycMerchantDepositTest(t: GlobalTestState) { exchangeBankAccount, amlKeypair, } = await createKycTestkudosEnvironment(t, { - adjustExchangeConfig(config) { - config.setString("exchange", "enable_kyc", "yes"); - - config.setString("KYC-RULE-R1", "operation_type", "deposit"); - config.setString("KYC-RULE-R1", "enabled", "yes"); - config.setString("KYC-RULE-R1", "exposed", "yes"); - config.setString("KYC-RULE-R1", "is_and_combinator", "yes"); - config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:0"); - config.setString("KYC-RULE-R1", "timeframe", "1d"); - config.setString("KYC-RULE-R1", "next_measures", "M1"); - - config.setString("KYC-MEASURE-M1", "check_name", "C1"); - config.setString("KYC-MEASURE-M1", "context", "{}"); - config.setString("KYC-MEASURE-M1", "program", "P1"); - - config.setString("AML-PROGRAM-P1", "command", "/bin/true"); - config.setString("AML-PROGRAM-P1", "enabled", "true"); - config.setString("AML-PROGRAM-P1", "description", "this does nothing"); - config.setString("AML-PROGRAM-P1", "fallback", "M1"); - - config.setString("KYC-CHECK-C1", "type", "INFO"); - config.setString("KYC-CHECK-C1", "description", "my check!"); - config.setString("KYC-CHECK-C1", "fallback", "M1"); - }, + adjustExchangeConfig, }); let accountPub: string; diff --git a/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts b/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts @@ -18,7 +18,6 @@ * Imports. */ import { - AbsoluteTime, codecForAny, codecForKycProcessClientInformation, codecOptional, @@ -46,7 +45,6 @@ export const AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT: TalerKycAml.AmlProgramDefin { name: "from-attr-to-context", logic: (_input, config) => { - const now = AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()); const outcome: TalerKycAml.AmlOutcome = { to_investigate: false, // pushing to info into properties for testing purposes @@ -57,7 +55,7 @@ export const AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT: TalerKycAml.AmlProgramDefin }, events: [], new_rules: { - expiration_time: now, + expiration_time: TalerProtocolTimestamp.zero(), rules: [], successor_measure: "ask_more_info", custom_measures: { diff --git a/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts b/packages/taler-harness/src/integrationtests/test-wallet-dd48.ts @@ -28,6 +28,7 @@ import { } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; +import { withdrawViaBankV3 } from "../harness/environments.js"; import { BankService, ExchangeService, @@ -37,7 +38,6 @@ import { getTestHarnessPaytoForLabel, setupDb, } from "../harness/harness.js"; -import { withdrawViaBankV3 } from "../harness/environments.js"; /** * Test for DD48 notifications. @@ -131,6 +131,7 @@ export async function runWalletDd48Test(t: GlobalTestState) { await walletClient.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: exchange.baseUrl, + ephemeral: true, }); { diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts @@ -1781,6 +1781,8 @@ export type GetExchangeEntryByUrlResponse = ExchangeListItem; export interface AddExchangeRequest { exchangeBaseUrl: string; + ephemeral?: boolean; + /** * @deprecated use a separate API call to start a forced exchange update instead */ @@ -1791,6 +1793,7 @@ export const codecForAddExchangeRequest = (): Codec<AddExchangeRequest> => buildCodecForObject<AddExchangeRequest>() .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("forceUpdate", codecOptional(codecForBoolean())) + .property("ephemeral", codecOptional(codecForBoolean())) .build("AddExchangeRequest"); export interface UpdateExchangeEntryRequest { @@ -2030,11 +2033,10 @@ export interface ListBankAccountsRequest { currency?: string; } -export const codecForListBankAccounts = - (): Codec<ListBankAccountsRequest> => - buildCodecForObject<ListBankAccountsRequest>() - .property("currency", codecOptional(codecForString())) - .build("ListBankAccountsRequest"); +export const codecForListBankAccounts = (): Codec<ListBankAccountsRequest> => + buildCodecForObject<ListBankAccountsRequest>() + .property("currency", codecOptional(codecForString())) + .build("ListBankAccountsRequest"); export interface AddBankAccountRequest { /** @@ -2077,11 +2079,10 @@ export interface ForgetBankAccountRequest { bankAccountId: string; } -export const codecForForgetBankAccount = - (): Codec<ForgetBankAccountRequest> => - buildCodecForObject<ForgetBankAccountRequest>() - .property("bankAccountId", codecForString()) - .build("ForgetBankAccountsRequest"); +export const codecForForgetBankAccount = (): Codec<ForgetBankAccountRequest> => + buildCodecForObject<ForgetBankAccountRequest>() + .property("bankAccountId", codecForString()) + .build("ForgetBankAccountsRequest"); export interface GetContractTermsDetailsRequest { transactionId: string; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts @@ -931,9 +931,11 @@ async function handleAddExchange( await fetchFreshExchange(wex, req.exchangeBaseUrl, {}); // Exchange has been explicitly added upon user request. // Thus, we mark it as "used". - await wex.db.runAllStoresReadWriteTx({}, async (tx) => { - await markExchangeUsed(wex, tx, req.exchangeBaseUrl); - }); + if (!req.ephemeral) { + await wex.db.runAllStoresReadWriteTx({}, async (tx) => { + await markExchangeUsed(wex, tx, req.exchangeBaseUrl); + }); + } return {}; }