taler-typescript-core

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

commit bf32d1ba81fe079a555d856daeaa11e9930c32e5
parent 0e6ec2c2b2b016539c5a5539e98b525d0f44a24c
Author: Florian Dold <florian@dold.me>
Date:   Tue, 24 Jun 2025 10:52:37 +0200

harness: test for broken compression handling

Diffstat:
Apackages/taler-harness/src/integrationtests/test-kyc-form-compression.ts | 254+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 2++
Mpackages/taler-util/src/http-impl.node.ts | 8+++++++-
3 files changed, 263 insertions(+), 1 deletion(-)

diff --git a/packages/taler-harness/src/integrationtests/test-kyc-form-compression.ts b/packages/taler-harness/src/integrationtests/test-kyc-form-compression.ts @@ -0,0 +1,254 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Imports. + */ +import { + codecForAny, + codecForKycProcessClientInformation, + Configuration, + decodeCrock, + encodeCrock, + j2s, + signAmlQuery, + TransactionIdStr, + TransactionMajorState, + TransactionMinorState, +} from "@gnu-taler/taler-util"; +import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; +import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + configureCommonKyc, + createKycTestkudosEnvironment, + withdrawViaBankV3, +} from "../harness/environments.js"; +import { GlobalTestState, harnessHttpLib } from "../harness/harness.js"; + +function adjustExchangeConfig(config: Configuration) { + configureCommonKyc(config); + + config.setString("KYC-RULE-R1", "operation_type", "withdraw"); + 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 M2"); + + config.setString("KYC-MEASURE-M1", "check_name", "C1"); + config.setString("KYC-MEASURE-M1", "context", "{}"); + config.setString("KYC-MEASURE-M1", "program", "P1"); + + config.setString("KYC-MEASURE-M2", "check_name", "C2"); + config.setString("KYC-MEASURE-M2", "context", "{}"); + config.setString("KYC-MEASURE-M2", "program", "NONE"); + + config.setString( + "AML-PROGRAM-P1", + "command", + "taler-exchange-helper-measure-test-form", + ); + config.setString("AML-PROGRAM-P1", "enabled", "true"); + config.setString( + "AML-PROGRAM-P1", + "description", + "test for FULL_NAME and DATE_OF_BIRTH", + ); + config.setString("AML-PROGRAM-P1", "description_i18n", "{}"); + config.setString("AML-PROGRAM-P1", "fallback", "FREEZE"); + + config.setString("KYC-CHECK-C1", "type", "FORM"); + config.setString("KYC-CHECK-C1", "form_name", "myform"); + config.setString("KYC-CHECK-C1", "description", "my check!"); + config.setString("KYC-CHECK-C1", "description_i18n", "{}"); + config.setString("KYC-CHECK-C1", "outputs", "FULL_NAME DATE_OF_BIRTH"); + config.setString("KYC-CHECK-C1", "fallback", "FREEZE"); + + config.setString("KYC-CHECK-C2", "type", "INFO"); + config.setString("KYC-CHECK-C2", "description", "my check info!"); + config.setString("KYC-CHECK-C2", "description_i18n", "{}"); + config.setString("KYC-CHECK-C2", "fallback", "FREEZE"); +} + +export async function runKycFormCompressionTest(t: GlobalTestState) { + // Set up test environment + + const { walletClient, bankClient, exchange, amlKeypair } = + await createKycTestkudosEnvironment(t, { adjustExchangeConfig }); + + // Withdraw digital cash into the wallet. + + const wres = await withdrawViaBankV3(t, { + amount: "TESTKUDOS:20", + bankClient, + exchange, + walletClient, + }); + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: wres.transactionId as TransactionIdStr, + txState: { + major: TransactionMajorState.Pending, + minor: TransactionMinorState.KycRequired, + }, + }); + + const txDetails = await walletClient.call( + WalletApiOperation.GetTransactionById, + { + transactionId: wres.transactionId, + }, + ); + + console.log(j2s(txDetails)); + const accessToken = txDetails.kycAccessToken; + t.assertTrue(!!accessToken); + + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-info/${txDetails.kycAccessToken}`, exchange.baseUrl).href, + ); + + const clientInfo = await readResponseJsonOrThrow( + infoResp, + codecForKycProcessClientInformation(), + ); + + console.log(j2s(clientInfo)); + + const kycId = clientInfo.requirements.find((x) => x.id != null)?.id; + t.assertTrue(!!kycId); + + const CONTENTS = + "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"; + + const uploadResp = await harnessHttpLib.fetch( + new URL(`kyc-upload/${kycId}`, exchange.baseUrl).href, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + compress: "deflate", + body: { + FULL_NAME: "Alice Abc", + DATE_OF_BIRTH: "2000-01-01", + STUFF: { + FILE_MAP: { + file_0000: { + CONTENTS, + ENCODING: "base64", + FILENAME: "test.pdf", + MIME_TYPE: "application/pdf", + }, + file_0001: { + CONTENTS, + ENCODING: "base64", + FILENAME: "test.pdf", + MIME_TYPE: "application/pdf", + }, + file_0002: { + CONTENTS, + ENCODING: "base64", + FILENAME: "test.pdf", + MIME_TYPE: "application/pdf", + }, + file_0003: { + CONTENTS, + ENCODING: "base64", + FILENAME: "test.pdf", + MIME_TYPE: "application/pdf", + }, + }, + FORM_ID: "multi_upload", + FORM_VERSION: 1, + FORM_CONTEXT: { + REQUESTED_FILES: [ + { + REQUESTED_FILE_ID: null, + REQUESTED_FILE_TITLE: "Registerauszug", + REQUESTED_FILE_DESCRIPTION: null, + REQUESTED_FILE_REQUIRED: true, + }, + { + REQUESTED_FILE_ID: null, + REQUESTED_FILE_TITLE: "Gesellschaftsvertrag", + REQUESTED_FILE_DESCRIPTION: null, + REQUESTED_FILE_REQUIRED: true, + }, + { + REQUESTED_FILE_ID: null, + REQUESTED_FILE_TITLE: "Liste der Gesellschafter", + REQUESTED_FILE_DESCRIPTION: null, + REQUESTED_FILE_REQUIRED: true, + }, + { + REQUESTED_FILE_ID: null, + REQUESTED_FILE_TITLE: "Transparenzregisterauszug", + REQUESTED_FILE_DESCRIPTION: null, + REQUESTED_FILE_REQUIRED: true, + }, + ], + }, + }, + }, + }, + ); + + console.log("resp status", uploadResp.status); + + t.assertDeepEqual(uploadResp.status, 204); + + { + // Do a GET on kyc-info here as this reproduces a bug in the + // exchange. + const infoResp = await harnessHttpLib.fetch( + new URL(`kyc-info/${txDetails.kycAccessToken}`, exchange.baseUrl).href, + ); + + await readResponseJsonOrThrow( + infoResp, + codecForKycProcessClientInformation(), + ); + } + + const sig = signAmlQuery(decodeCrock(amlKeypair.priv)); + + const decisionsResp = await harnessHttpLib.fetch( + new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href, + { + headers: { + "Taler-AML-Officer-Signature": encodeCrock(sig), + }, + }, + ); + + const decisions = await readResponseJsonOrThrow(decisionsResp, codecForAny()); + console.log(j2s(decisions)); + + t.assertDeepEqual(decisionsResp.status, 200); + + // KYC should pass now + + await walletClient.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: wres.transactionId as TransactionIdStr, + txState: { + major: TransactionMajorState.Done, + }, + }); +} + +runKycFormCompressionTest.suites = ["wallet"]; diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts @@ -72,6 +72,7 @@ import { runKycDepositDepositKyctransferTest } from "./test-kyc-deposit-deposit- import { runKycDepositDepositTest } from "./test-kyc-deposit-deposit.js"; import { runKycExchangeWalletTest } from "./test-kyc-exchange-wallet.js"; import { runKycFormBadMeasureTest } from "./test-kyc-form-bad-measure.js"; +import { runKycFormCompressionTest } from "./test-kyc-form-compression.js"; import { runKycFormWithdrawalTest } from "./test-kyc-form-withdrawal.js"; import { runKycMerchantActivateBankAccountTest } from "./test-kyc-merchant-activate-bank-account.js"; import { runKycMerchantAggregateTest } from "./test-kyc-merchant-aggregate.js"; @@ -353,6 +354,7 @@ const allTests: TestMainFunction[] = [ runExchangeKycAuthTest, runTopsPeerTest, runWalletExchangeMigrationTest, + runKycFormCompressionTest, ]; export interface TestRunSpec { diff --git a/packages/taler-util/src/http-impl.node.ts b/packages/taler-util/src/http-impl.node.ts @@ -21,6 +21,7 @@ import followRedirects from "follow-redirects"; import type { ClientRequest, IncomingMessage } from "node:http"; import { RequestOptions } from "node:http"; import * as net from "node:net"; +import { deflateSync } from "node:zlib"; import { TalerError } from "./errors.js"; import { HttpLibArgs, encodeBody, getDefaultHeaders } from "./http-common.js"; import { @@ -50,7 +51,7 @@ const https = followRedirects.https; if ( // check for `node` in case we want to use this in "exotic" JS envs process.versions.node && - process.versions.node.match(/(20|22|24)\./) + process.versions.node.match(/(20|22|24)\./) ) { //@ts-ignore net.setDefaultAutoSelectFamily(false); @@ -145,6 +146,11 @@ export class HttpLibImpl implements HttpRequestLibrary { reqBody = encodeBody(opt.body); } + if (reqBody && opt?.compress === "deflate") { + reqBody = deflateSync(reqBody); + requestHeadersMap["Content-Encoding"] = "deflate"; + } + if (opt?.body instanceof URLSearchParams) { requestHeadersMap["Content-Type"] = "application/x-www-form-urlencoded"; }