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:
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";
}