gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wallet-core: KYC mvp


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: KYC mvp
Date: Tue, 10 Jan 2023 17:31:39 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new a82d8fab6 wallet-core: KYC mvp
a82d8fab6 is described below

commit a82d8fab696d3fca24c2f1c48a1646107e38cef8
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Jan 10 17:31:01 2023 +0100

    wallet-core: KYC mvp
    
    Only hard withdrawal KYC is supporte so far, and no long-polling is done
    yet.
---
 packages/taler-harness/src/harness/harness.ts      |  11 ++
 .../taler-harness/src/integrationtests/test-kyc.ts | 204 +++++++++++++++++++++
 .../src/integrationtests/testrunner.ts             |  50 ++---
 packages/taler-util/src/taler-types.ts             |  15 ++
 packages/taler-wallet-core/src/db.ts               |   7 +
 .../taler-wallet-core/src/operations/withdraw.ts   |  73 +++++++-
 6 files changed, 329 insertions(+), 31 deletions(-)

diff --git a/packages/taler-harness/src/harness/harness.ts 
b/packages/taler-harness/src/harness/harness.ts
index a9298637f..5b72cbc06 100644
--- a/packages/taler-harness/src/harness/harness.ts
+++ b/packages/taler-harness/src/harness/harness.ts
@@ -1081,6 +1081,17 @@ export class ExchangeService implements 
ExchangeServiceInterface {
     return this.exchangeConfig.httpPort;
   }
 
+  /**
+   * Run a function that modifies the existing exchange configuration.
+   * The modified exchange configuration will then be written to the
+   * file system.
+   */
+  async modifyConfig(f: (config: Configuration) => Promise<void>): 
Promise<void> {
+    const config = Configuration.load(this.configFilename);
+    await f(config);
+    config.write(this.configFilename);
+  }
+
   async addBankAccount(
     localName: string,
     exchangeBankAccount: HarnessExchangeBankAccount,
diff --git a/packages/taler-harness/src/integrationtests/test-kyc.ts 
b/packages/taler-harness/src/integrationtests/test-kyc.ts
new file mode 100644
index 000000000..40474fb6f
--- /dev/null
+++ b/packages/taler-harness/src/integrationtests/test-kyc.ts
@@ -0,0 +1,204 @@
+/*
+ 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 { Duration } from "@gnu-taler/taler-util";
+import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
+import {
+  BankService,
+  ExchangeService,
+  getPayto,
+  GlobalTestState,
+  MerchantService,
+  setupDb,
+  WalletCli,
+} from "../harness/harness.js";
+import {
+  withdrawViaBank,
+  makeTestPayment,
+  EnvOptions,
+  SimpleTestEnvironment,
+} from "../harness/helpers.js";
+
+export async function createKycTestkudosEnvironment(
+  t: GlobalTestState,
+  coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
+  opts: EnvOptions = {},
+): Promise<SimpleTestEnvironment> {
+  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,
+  });
+
+  const merchant = await MerchantService.create(t, {
+    name: "testmerchant-1",
+    currency: "TESTKUDOS",
+    httpPort: 8083,
+    database: db.connStr,
+  });
+
+  const exchangeBankAccount = await bank.createExchangeAccount(
+    "myexchange",
+    "x",
+  );
+  exchange.addBankAccount("1", exchangeBankAccount);
+
+  bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);
+
+  await bank.start();
+
+  await bank.pingUntilAvailable();
+
+  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) => {
+    const myprov = "kyc-provider-myprov";
+    config.setString(myprov, "cost", "0");
+    config.setString(myprov, "logic", "oauth2");
+    config.setString(myprov, "provided_checks", "dummy1");
+    config.setString(myprov, "user_type", "individual");
+    config.setString(myprov, "kyc_oauth2_validity", "forever");
+    config.setString(
+      myprov,
+      "kyc_oauth2_auth_url",
+      "http://localhost:6666/oauth/v2/token";,
+    );
+    config.setString(
+      myprov,
+      "kyc_oauth2_login_url",
+      "http://localhost:6666/oauth/v2/login";,
+    );
+    config.setString(
+      myprov,
+      "kyc_oauth2_info_url",
+      "http://localhost:6666/oauth/v2/login";,
+    );
+    config.setString(
+      myprov,
+      "kyc_oauth2_client_id",
+      "taler-exchange",
+    );
+    config.setString(
+      myprov,
+      "kyc_oauth2_client_secret",
+      "exchange-secret",
+    );
+    config.setString(
+      myprov,
+      "kyc_oauth2_post_url",
+      "https://taler.com";,
+    );
+
+    config.setString("kyc-legitimization-withdraw1", "operation_type", 
"withdraw");
+    config.setString("kyc-legitimization-withdraw1", "required_checks", 
"dummy1");
+    config.setString("kyc-legitimization-withdraw1", "timeframe", "1d");
+    config.setString("kyc-legitimization-withdraw1", "threshold", 
"TESTKUDOS:5");
+  });
+
+  await exchange.start();
+  await exchange.pingUntilAvailable();
+
+  merchant.addExchange(exchange);
+
+  await merchant.start();
+  await merchant.pingUntilAvailable();
+
+  await merchant.addInstance({
+    id: "default",
+    name: "Default Instance",
+    paytoUris: [getPayto("merchant-default")],
+    defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+      Duration.fromSpec({ minutes: 1 }),
+    ),
+  });
+
+  await merchant.addInstance({
+    id: "minst1",
+    name: "minst1",
+    paytoUris: [getPayto("minst1")],
+    defaultWireTransferDelay: Duration.toTalerProtocolDuration(
+      Duration.fromSpec({ minutes: 1 }),
+    ),
+  });
+
+  console.log("setup done!");
+
+  const wallet = new WalletCli(t);
+
+  return {
+    commonDb: db,
+    exchange,
+    merchant,
+    wallet,
+    bank,
+    exchangeBankAccount,
+  };
+}
+
+export async function runKycTest(t: GlobalTestState) {
+  // Set up test environment
+
+  const { wallet, bank, exchange, merchant } =
+    await createKycTestkudosEnvironment(t);
+
+  // Withdraw digital cash into the wallet.
+
+  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
+
+  const order = {
+    summary: "Buy me!",
+    amount: "TESTKUDOS:5",
+    fulfillment_url: "taler://fulfillment-success/thx",
+  };
+
+  await makeTestPayment(t, { wallet, merchant, order });
+  await wallet.runUntilDone();
+}
+
+runKycTest.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 4b1c28bde..9e64a151a 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -96,6 +96,7 @@ import { runWalletBalanceTest } from 
"./test-wallet-balance.js";
 import { runAgeRestrictionsMixedMerchantTest } from 
"./test-age-restrictions-mixed-merchant.js";
 import { runWalletCryptoWorkerTest } from "./test-wallet-cryptoworker.js";
 import { runWithdrawalHighTest } from "./test-withdrawal-high.js";
+import { runKycTest } from "./test-kyc.js";
 
 /**
  * Test runner.
@@ -113,75 +114,76 @@ interface TestMainFunction {
 
 const allTests: TestMainFunction[] = [
   runAgeRestrictionsMerchantTest,
-  runAgeRestrictionsPeerTest,
   runAgeRestrictionsMixedMerchantTest,
+  runAgeRestrictionsPeerTest,
   runBankApiTest,
   runClaimLoopTest,
   runClauseSchnorrTest,
-  runWalletCryptoWorkerTest,
-  runDepositTest,
   runDenomUnofferedTest,
+  runDepositTest,
   runExchangeManagementTest,
   runExchangeTimetravelTest,
   runFeeRegressionTest,
   runForcedSelectionTest,
-  runLibeufinBasicTest,
-  runLibeufinKeyrotationTest,
-  runLibeufinTutorialTest,
-  runLibeufinRefundTest,
-  runLibeufinC5xTest,
-  runLibeufinNexusBalanceTest,
-  runLibeufinBadGatewayTest,
-  runLibeufinRefundMultipleUsersTest,
-  runLibeufinApiPermissionsTest,
-  runLibeufinApiFacadeTest,
-  runLibeufinApiFacadeBadRequestTest,
+  runKycTest,
   runLibeufinAnastasisFacadeTest,
-  runLibeufinApiSchedulingTest,
-  runLibeufinApiUsersTest,
   runLibeufinApiBankaccountTest,
   runLibeufinApiBankconnectionTest,
-  runLibeufinApiSandboxTransactionsTest,
+  runLibeufinApiFacadeBadRequestTest,
+  runLibeufinApiFacadeTest,
+  runLibeufinApiPermissionsTest,
   runLibeufinApiSandboxCamtTest,
+  runLibeufinApiSandboxTransactionsTest,
+  runLibeufinApiSchedulingTest,
+  runLibeufinApiUsersTest,
+  runLibeufinBadGatewayTest,
+  runLibeufinBasicTest,
+  runLibeufinC5xTest,
+  runLibeufinKeyrotationTest,
+  runLibeufinNexusBalanceTest,
+  runLibeufinRefundMultipleUsersTest,
+  runLibeufinRefundTest,
   runLibeufinSandboxWireTransferCliTest,
+  runLibeufinTutorialTest,
   runMerchantExchangeConfusionTest,
-  runMerchantInstancesTest,
   runMerchantInstancesDeleteTest,
+  runMerchantInstancesTest,
   runMerchantInstancesUrlsTest,
   runMerchantLongpollingTest,
-  runMerchantSpecPublicOrdersTest,
   runMerchantRefundApiTest,
+  runMerchantSpecPublicOrdersTest,
   runPaymentClaimTest,
+  runPaymentDemoTest,
   runPaymentFaultTest,
   runPaymentForgettableTest,
   runPaymentIdempotencyTest,
   runPaymentMultipleTest,
   runPaymentTest,
-  runPaymentDemoTest,
   runPaymentTransientTest,
   runPaymentZeroTest,
   runPayPaidTest,
   runPaywallFlowTest,
-  runPeerToPeerPushTest,
   runPeerToPeerPullTest,
+  runPeerToPeerPushTest,
   runRefundAutoTest,
   runRefundGoneTest,
   runRefundIncrementalTest,
   runRefundTest,
   runRevocationTest,
   runTestWithdrawalManualTest,
-  runWithdrawalFakebankTest,
   runTimetravelAutorefreshTest,
   runTimetravelWithdrawTest,
   runTippingTest,
   runWalletBackupBasicTest,
   runWalletBackupDoublespendTest,
   runWalletBalanceTest,
-  runWithdrawalHighTest,
-  runWallettestingTest,
+  runWalletCryptoWorkerTest,
   runWalletDblessTest,
+  runWallettestingTest,
   runWithdrawalAbortBankTest,
   runWithdrawalBankIntegratedTest,
+  runWithdrawalFakebankTest,
+  runWithdrawalHighTest,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index 292ace94b..9251868e6 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -2027,3 +2027,18 @@ export interface ExchangeDepositRequest {
 
   h_age_commitment?: string;
 }
+
+export interface WalletKycUuid {
+  // UUID that the wallet should use when initiating
+  // the KYC check.
+  requirement_row: number;
+
+  // Hash of the payto:// account URI for the wallet.
+  h_payto: string;
+}
+
+export const codecForWalletKycUuid = (): Codec<WalletKycUuid> =>
+  buildCodecForObject<WalletKycUuid>()
+    .property("requirement_row", codecForNumber())
+    .property("h_payto", codecForString())
+    .build("WalletKycUuid");
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 04fee9495..c56c3a9b5 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1327,6 +1327,11 @@ export type WgInfo =
   | WgInfoBankPeerPush
   | WgInfoBankRecoup;
 
+
+export interface WithdrawalKycPendingInfo {
+  paytoHash: string;
+  requirementRow: number;
+}
 /**
  * Group of withdrawal operations that need to be executed.
  * (Either for a normal withdrawal or from a tip.)
@@ -1342,6 +1347,8 @@ export interface WithdrawalGroupRecord {
 
   wgInfo: WgInfo;
 
+  kycPending?: WithdrawalKycPendingInfo;
+
   /**
    * Secret seed used to derive planchets.
    * Stored since planchets are created lazily.
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 76bbec416..368cf3510 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -33,6 +33,7 @@ import {
   codecForBankWithdrawalOperationPostResponse,
   codecForReserveStatus,
   codecForTalerConfigResponse,
+  codecForWalletKycUuid,
   codecForWithdrawBatchResponse,
   codecForWithdrawOperationStatusResponse,
   codecForWithdrawResponse,
@@ -75,6 +76,7 @@ import {
   WgInfo,
   WithdrawalGroupRecord,
   WithdrawalGroupStatus,
+  WithdrawalKycPendingInfo,
   WithdrawalRecordType,
 } from "../db.js";
 import {
@@ -530,8 +532,11 @@ async function processPlanchetExchangeRequest(
     const resp = await ws.http.postJson(reqUrl, reqBody);
     if (resp.status === HttpStatusCode.UnavailableForLegalReasons) {
       logger.info("withdrawal requires KYC");
+      const respJson = await resp.json();
+      const uuidResp = codecForWalletKycUuid().decode(respJson);
+      logger.info(`kyc uuid response: ${j2s(uuidResp)}`);
       await ws.db
-        .mktx((x) => [x.planchets])
+        .mktx((x) => [x.planchets, x.withdrawalGroups])
         .runReadWrite(async (tx) => {
           let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
             withdrawalGroup.withdrawalGroupId,
@@ -541,7 +546,18 @@ async function processPlanchetExchangeRequest(
             return;
           }
           planchet.planchetStatus = PlanchetStatus.KycRequired;
+          const wg2 = await tx.withdrawalGroups.get(
+            withdrawalGroup.withdrawalGroupId,
+          );
+          if (!wg2) {
+            return;
+          }
+          wg2.kycPending = {
+            paytoHash: uuidResp.h_payto,
+            requirementRow: uuidResp.requirement_row,
+          };
           await tx.planchets.put(planchet);
+          await tx.withdrawalGroups.put(wg2);
         });
       return;
     }
@@ -1148,7 +1164,7 @@ export async function processWithdrawalGroup(
   let finishedForFirstTime = false;
   let errorsPerCoin: Record<number, TalerErrorDetail> = {};
 
-  await ws.db
+  let res = await ws.db
     .mktx((x) => [x.coins, x.withdrawalGroups, x.planchets])
     .runReadWrite(async (tx) => {
       const wg = await tx.withdrawalGroups.get(withdrawalGroupId);
@@ -1177,13 +1193,56 @@ export async function processWithdrawalGroup(
       }
 
       await tx.withdrawalGroups.put(wg);
+
+      return {
+        kycInfo: wg.kycPending,
+      };
     });
+
+  if (!res) {
+    throw Error("withdrawal group does not exist anymore");
+  }
+
+  const { kycInfo } = res;
+
   if (numKycRequired > 0) {
-    throw TalerError.fromDetail(
-      TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
-      {},
-      `KYC check required for withdrawal (not yet implemented in wallet-core)`,
-    );
+    if (kycInfo) {
+      const url = new URL(
+        `kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/individual`,
+        withdrawalGroup.exchangeBaseUrl,
+      );
+      logger.info(`kyc url ${url.href}`);
+      const kycStatusReq = await ws.http.fetch(url.href, {
+        method: "GET",
+      });
+      logger.warn("kyc requested, but already fulfilled");
+      if (kycStatusReq.status === HttpStatusCode.Ok) {
+        return {
+          type: OperationAttemptResultType.Pending,
+          result: undefined,
+        };
+      } else if (kycStatusReq.status === HttpStatusCode.Accepted) {
+        const kycStatus = await kycStatusReq.json();
+        logger.info(`kyc status: ${j2s(kycStatus)}`);
+        throw TalerError.fromDetail(
+          TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
+          {
+            kycUrl: kycStatus.kyc_url,
+          },
+          `KYC check required for withdrawal`,
+        );
+      } else {
+        throw Error(
+          `unexpected response from kyc-check (${kycStatusReq.status})`,
+        );
+      }
+    } else {
+      throw TalerError.fromDetail(
+        TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
+        {},
+        `KYC check required for withdrawal (not yet implemented in 
wallet-core)`,
+      );
+    }
   }
   if (numFinished != numTotalCoins) {
     throw TalerError.fromDetail(

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]