gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: accept tos kyc/aml form


From: gnunet
Subject: [taler-wallet-core] branch master updated: accept tos kyc/aml form
Date: Fri, 29 Nov 2024 13:32:14 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new d396aba3a accept tos kyc/aml form
d396aba3a is described below

commit d396aba3a5815258abbce13c256438a96b040ff6
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Thu Nov 28 15:44:33 2024 -0300

    accept tos kyc/aml form
---
 packages/aml-backoffice-ui/src/pages/Measures.tsx  |  13 +-
 packages/kyc-ui/src/Routing.tsx                    |  38 ++-
 packages/kyc-ui/src/forms/accept-tos.ts            |  57 +++++
 packages/kyc-ui/src/forms/index.ts                 |  12 +-
 packages/kyc-ui/src/pages/FillForm.tsx             |   5 +-
 packages/kyc-ui/src/pages/Frame.tsx                |   5 +-
 packages/kyc-ui/src/pages/Start.tsx                |  16 +-
 packages/kyc-ui/src/pages/TriggerKyc.tsx           | 272 +++++++++++++++++++++
 packages/taler-harness/src/index.ts                |   1 +
 .../integrationtests/test-kyc-skip-expiration.ts   |   1 +
 packages/taler-util/src/http-client/exchange.ts    |  14 +-
 .../taler-util/src/http-client/officer-account.ts  |  26 +-
 packages/web-util/src/forms/HtmlIframe.tsx         |  43 ++++
 packages/web-util/src/forms/forms.ts               |  14 ++
 packages/web-util/src/forms/index.ts               |   1 +
 packages/web-util/src/forms/ui-form.ts             |  14 ++
 16 files changed, 488 insertions(+), 44 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/pages/Measures.tsx 
b/packages/aml-backoffice-ui/src/pages/Measures.tsx
index 0330d09c9..1e6a633d5 100644
--- a/packages/aml-backoffice-ui/src/pages/Measures.tsx
+++ b/packages/aml-backoffice-ui/src/pages/Measures.tsx
@@ -14,23 +14,16 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 import {
-  AbsoluteTime,
-  AmlDecisionRequest,
-  TalerError,
-  TalerExchangeApi,
+  TalerError
 } from "@gnu-taler/taler-util";
 import {
-  FormMetadata,
-  InternationalizationAPI,
   Loading,
   useExchangeApiContext,
-  useTranslationContext,
+  useTranslationContext
 } from "@gnu-taler/web-util/browser";
 import { Fragment, h } from "preact";
-import { useUiFormsContext } from "../context/ui-forms.js";
-import { preloadedForms } from "../forms/index.js";
-import { useServerMeasures } from "../hooks/account.js";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
+import { useServerMeasures } from "../hooks/account.js";
 
 export function Measures({}: {}) {
   const { config } = useExchangeApiContext();
diff --git a/packages/kyc-ui/src/Routing.tsx b/packages/kyc-ui/src/Routing.tsx
index 846a16d2a..be95f4862 100644
--- a/packages/kyc-ui/src/Routing.tsx
+++ b/packages/kyc-ui/src/Routing.tsx
@@ -14,7 +14,7 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
- import {
+import {
   Loading,
   urlPattern,
   useCurrentLocation,
@@ -28,6 +28,7 @@ import { CallengeCompleted } from 
"./pages/CallengeCompleted.js";
 import { Frame } from "./pages/Frame.js";
 import { Start } from "./pages/Start.js";
 import { useSessionState } from "./hooks/session.js";
+import { TriggerKyc } from "./pages/TriggerKyc.js";
 
 export function Routing(): VNode {
   // check session and defined if this is
@@ -39,9 +40,10 @@ export function Routing(): VNode {
   );
 }
 
-const publicPages = {
+export const publicPages = {
   completed: urlPattern(/\/completed/, () => `#/completed`),
   start: urlPattern(/\/start/, () => `#/start`),
+  triggerKyc: urlPattern(/\/test\/trigger-kyc/, () => `#/test/trigger-kyc`),
 };
 
 function safeGetParam(
@@ -61,12 +63,12 @@ export function safeToURL(s: string | undefined): URL | 
undefined {
   }
 }
 
-const ACCESS_TOKEN_REGEX = new RegExp("[A-Z0-9]{52}")
+const ACCESS_TOKEN_REGEX = new RegExp("[A-Z0-9]{52}");
 
 /**
  * by how the exchange serves the SPA
  * /kyc-spa/KXAFXEWM7E3EJSYD9GJ30FYK1C17AKZWV119ZJA3XGPBBMZFJ2C0#/start
- * 
+ *
  * by how dev.mjs serves the SPA
  * /app/?token=KXAFXEWM7E3EJSYD9GJ30FYK1C17AKZWV119ZJA3XGPBBMZFJ2C0#/start
  * @returns
@@ -75,11 +77,11 @@ function getAccessTokenFromURL(): AccessToken | undefined {
   if (typeof window === "undefined") return undefined;
   const paths = window.location.pathname.split("/");
   if (paths.length < 3) return undefined;
-  const path = paths[2]
+  const path = paths[2];
   if (path && ACCESS_TOKEN_REGEX.test(path)) {
     return path as AccessToken;
   }
-  const param = new URLSearchParams(window.location.search).get("token")
+  const param = new URLSearchParams(window.location.search).get("token");
   if (param && ACCESS_TOKEN_REGEX.test(param)) {
     return param as AccessToken;
   }
@@ -93,7 +95,7 @@ function PublicRounting(): VNode {
   useErrorBoundary((e) => {
     console.log("error", e);
   });
-  const sessionToken = state?.accessToken
+  const sessionToken = state?.accessToken;
   const urlToken = getAccessTokenFromURL();
   useEffect(() => {
     if (!urlToken) {
@@ -102,13 +104,9 @@ function PublicRounting(): VNode {
     }
     // loading a new session
     if (urlToken !== sessionToken) {
-      start(urlToken)
+      start(urlToken);
     }
-  },[sessionToken, urlToken])
-
-  if (!sessionToken) {
-    return <div>No access token</div>;
-  }
+  }, [sessionToken, urlToken]);
 
   switch (location.name) {
     case undefined: {
@@ -116,6 +114,10 @@ function PublicRounting(): VNode {
       return <Loading />;
     }
     case "start": {
+      if (!sessionToken) {
+        return <div>No access token</div>;
+      }
+
       return (
         <Start
           token={sessionToken}
@@ -129,6 +131,16 @@ function PublicRounting(): VNode {
     case "completed": {
       return <CallengeCompleted />;
     }
+    case "triggerKyc": {
+      return (
+        <TriggerKyc
+          onKycStarted={(token) => {
+            start(token)
+            navigateTo(publicPages.start.url({}));
+          }}
+        />
+      );
+    }
     default:
       assertUnreachable(location);
   }
diff --git a/packages/kyc-ui/src/forms/accept-tos.ts 
b/packages/kyc-ui/src/forms/accept-tos.ts
new file mode 100644
index 000000000..0545efd8c
--- /dev/null
+++ b/packages/kyc-ui/src/forms/accept-tos.ts
@@ -0,0 +1,57 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 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/>
+ */
+
+import type {
+  DoubleColumnForm,
+  InternationalizationAPI,
+  UIHandlerId,
+} from "@gnu-taler/web-util/browser";
+
+function filterUndefined<T>(ar: Array<T|undefined>): Array<T> {
+  return ar.filter((a):a is T => !!a)
+}
+
+export const acceptTos = (i18n: InternationalizationAPI, context?: any): 
DoubleColumnForm => ({
+  type: "double-column" as const,
+  design: [
+    {
+      title: i18n.str`Accept Term of Service`,
+      fields: filterUndefined([
+        context?.tos_url ? {
+          type: "htmlIframe",
+          label: context?.provider_name ?? `Link`,
+          url: context.tos_url
+        } : undefined,
+        {
+          type: "choiceHorizontal",
+          id: "asd" as UIHandlerId,
+          required: true,
+          label: i18n.str`Do you accept terms of service`,
+          choices: [
+            {
+              label: i18n.str`Yes`,
+              value: "yes",
+            },
+            {
+              label: i18n.str`No`,
+              value: "no",
+            },
+          ],
+        },
+      ]),
+    },
+  ],
+});
diff --git a/packages/kyc-ui/src/forms/index.ts 
b/packages/kyc-ui/src/forms/index.ts
index a8803ec9f..ca8b1d49d 100644
--- a/packages/kyc-ui/src/forms/index.ts
+++ b/packages/kyc-ui/src/forms/index.ts
@@ -18,12 +18,14 @@ import {
   InternationalizationAPI,
 } from "@gnu-taler/web-util/browser";
 import { simplest } from "./simplest.js";
-import { personalInfo } from "./personal-info.js";
+import { acceptTos } from "./accept-tos.js";
 import { nameAndDob } from "./nameAndBirthdate.js";
+import { personalInfo } from "./personal-info.js";
 
 export const preloadedForms: (
   i18n: InternationalizationAPI,
-) => Array<FormMetadata> = (i18n) => [
+  context: object | undefined,
+) => Array<FormMetadata> = (i18n, context) => [
   {
     label: i18n.str`Simple comment`,
     id: "__simple_comment",
@@ -36,6 +38,12 @@ export const preloadedForms: (
     version: 1,
     config: personalInfo(i18n),
   },
+  {
+    label: i18n.str`Accept Terms of Service`,
+    id: "accept-tos",
+    version: 1,
+    config: acceptTos(i18n, context),
+  },
   {
     label: i18n.str`Name and birthdate`,
     id: "name_and_dob",
diff --git a/packages/kyc-ui/src/pages/FillForm.tsx 
b/packages/kyc-ui/src/pages/FillForm.tsx
index 459186240..0b691dc11 100644
--- a/packages/kyc-ui/src/pages/FillForm.tsx
+++ b/packages/kyc-ui/src/pages/FillForm.tsx
@@ -87,7 +87,7 @@ export function FillForm({
         } as FormMetadata)
       : undefined;
 
-  const theForm = searchForm(i18n, customForm ? [customForm] : [], formId);
+  const theForm = searchForm(i18n, customForm ? [customForm] : [], formId, 
requirement.context);
   if (!theForm) {
     return <div>form with id {formId} not found</div>;
   }
@@ -274,13 +274,14 @@ function searchForm(
   i18n: InternationalizationAPI,
   forms: FormMetadata[],
   formId: string,
+  context: object | undefined,
 ): FormMetadata | undefined {
   {
     const found = forms.find((v) => v.id === formId);
     if (found) return found;
   }
   {
-    const pf = preloadedForms(i18n);
+    const pf = preloadedForms(i18n, context);
     const found = pf.find((v) => v.id === formId);
     if (found) return found;
   }
diff --git a/packages/kyc-ui/src/pages/Frame.tsx 
b/packages/kyc-ui/src/pages/Frame.tsx
index a45fd129b..7cfdc7ad0 100644
--- a/packages/kyc-ui/src/pages/Frame.tsx
+++ b/packages/kyc-ui/src/pages/Frame.tsx
@@ -31,6 +31,7 @@ import {
   usePreferences,
 } from "../context/preferences.js";
 import { useSettingsContext } from "../context/settings.js";
+import { publicPages } from "../Routing.js";
 
 const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : 
undefined;
 const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined;
@@ -66,7 +67,9 @@ export function Frame({ children }: { children: 
ComponentChildren }): VNode {
         title="KYC"
         onLogout={undefined}
         iconLinkURL="#"
-        sites={[]}
+        sites={!preferences.showDebugInfo ? [] : [
+          ["Test kyc", publicPages.triggerKyc.url({})]
+        ]}
         supportedLangs={["en"]}
       >
         <li>
diff --git a/packages/kyc-ui/src/pages/Start.tsx 
b/packages/kyc-ui/src/pages/Start.tsx
index 3dbecc093..3c0bb78b3 100644
--- a/packages/kyc-ui/src/pages/Start.tsx
+++ b/packages/kyc-ui/src/pages/Start.tsx
@@ -30,7 +30,7 @@ import {
   useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { Fragment, VNode, h } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
 import { ErrorLoadingWithDebug } from "../components/ErrorLoadingWithDebug.js";
 import { useKycInfo } from "../hooks/kyc.js";
 import { FillForm } from "./FillForm.js";
@@ -40,7 +40,7 @@ type Props = {
   token: AccessToken;
 };
 
-function ShowReqList({
+export function ShowReqList({
   token,
   onFormSelected,
 }: {
@@ -52,6 +52,18 @@ function ShowReqList({
   // const { lib } = useExchangeApiContext();
   // const { state, start } = useSessionState();
   const result = useKycInfo(token);
+
+  const firstAccount =
+    result &&
+    !(result instanceof TalerError) &&
+    result.type === "ok" &&
+    result.body.requirements.length > 0
+      ? result.body.requirements[0]
+      : undefined;
+  useEffect(() => {
+    if (firstAccount) onFormSelected(firstAccount);
+  }, [firstAccount]);
+
   if (!result) {
     return <Loading />;
   }
diff --git a/packages/kyc-ui/src/pages/TriggerKyc.tsx 
b/packages/kyc-ui/src/pages/TriggerKyc.tsx
new file mode 100644
index 000000000..c7d2fa4cb
--- /dev/null
+++ b/packages/kyc-ui/src/pages/TriggerKyc.tsx
@@ -0,0 +1,272 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022-2024 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/>
+ */
+import {
+  Attention,
+  Button,
+  convertUiField,
+  FormMetadata,
+  getConverterById,
+  LocalNotificationBanner,
+  RenderAllFieldsByUiConfig,
+  UIHandlerId,
+  useExchangeApiContext,
+  useLocalNotificationHandler,
+  useTranslationContext,
+} from "@gnu-taler/web-util/browser";
+import { Fragment, VNode, h } from "preact";
+import {
+  FormErrors,
+  getRequiredFields,
+  getShapeFromFields,
+  useFormState,
+  validateRequiredFields,
+} from "../hooks/form.js";
+import { undefinedIfEmpty } from "./Start.js";
+import {
+  AbsoluteTime,
+  AccessToken,
+  AmountJson,
+  Amounts,
+  AmountString,
+  assertUnreachable,
+  createNewOfficerAccount,
+  createNewWalletKycAccount,
+  HttpStatusCode,
+} from "@gnu-taler/taler-util";
+import { useEffect, useMemo, useState } from "preact/hooks";
+
+type FormType = {
+  amount: AmountJson;
+};
+type Props = {
+  onKycStarted: (token: AccessToken) => void;
+};
+export function TriggerKyc({ onKycStarted }: Props): VNode {
+  const { i18n } = useTranslationContext();
+  const [notification, withErrorHandler, notify] =
+    useLocalNotificationHandler();
+  const { config, lib } = useExchangeApiContext();
+  const [kycAccount, setKycAccount] = useState<string>();
+
+  const theForm: FormMetadata = {
+    id: "asd",
+    version: 1,
+    label: i18n.str`Trigger KYC balance`,
+    config: {
+      type: "double-column",
+      design: [
+        {
+          title: i18n.str`Trigger KYC Balance`,
+          fields: [
+            {
+              id: ".amount" as UIHandlerId,
+              type: "amount",
+              currency: config.currency,
+              label: i18n.str`Amount`,
+              required: true,
+              converterId: "Taler.Amount",
+            },
+          ],
+        },
+      ],
+    },
+  };
+
+  const shape: Array<UIHandlerId> = [];
+  const requiredFields: Array<UIHandlerId> = [];
+
+  theForm.config.design.forEach((section) => {
+    Array.prototype.push.apply(shape, getShapeFromFields(section.fields));
+    Array.prototype.push.apply(
+      requiredFields,
+      getRequiredFields(section.fields),
+    );
+  });
+  const [form, state] = useFormState<FormType>(
+    shape,
+    {
+      amount: Amounts.parseOrThrow(`${config.currency}:1000000`),
+    },
+    (st) => {
+      const partialErrors = undefinedIfEmpty<FormErrors<FormType>>({});
+
+      const errors = undefinedIfEmpty<FormErrors<FormType> | undefined>(
+        validateRequiredFields(partialErrors, st, requiredFields),
+      );
+
+      if (errors === undefined) {
+        return {
+          status: "ok",
+          result: st as any,
+          errors: undefined,
+        };
+      }
+
+      return {
+        status: "fail",
+        result: st as any,
+        errors,
+      };
+    },
+  );
+
+  const accountPromise = useMemo(async () => {
+    const resp = await lib.exchange.getSeed();
+    const extraEntropy = resp.type === "ok" ? resp.body : new Uint8Array();
+    return createNewWalletKycAccount(extraEntropy);
+  }, [1]);
+
+  useEffect(() => {
+    if (!kycAccount) return;
+    const paytoHash = kycAccount
+    async function check() {
+      const {signingKey} = await accountPromise;
+      const result = await lib.exchange.checkKycStatus(signingKey, paytoHash);
+      if (result.type === "ok") {
+        if (result.body) {
+          onKycStarted(result.body.access_token)
+        } else {
+          console.log("empty body")
+        }
+      } else {
+        switch(result.case) {
+          case HttpStatusCode.Forbidden:{
+            notify({
+              type: "error",
+              title: i18n.str`could not create token`,
+              description: i18n.str`access denied`,
+              when: AbsoluteTime.now(),
+            })
+          }
+          case HttpStatusCode.NotFound: {
+            notify({
+              type: "error",
+              title: i18n.str`could not create token`,
+              description: i18n.str`not found`,
+              when: AbsoluteTime.now(),
+            })
+          }
+          case HttpStatusCode.Conflict: {
+            notify({
+              type: "error",
+              title: i18n.str`could not create token`,
+              description: i18n.str`conflict`,
+              when: AbsoluteTime.now(),
+            })
+
+          }
+        }
+      }
+    }
+    check()
+  }, [kycAccount]);
+
+  const submitHandler =
+    theForm === undefined || state.status === "fail"
+      ? undefined
+      : withErrorHandler(
+          async () => {
+            const account = await accountPromise;
+
+            return lib.exchange.notifyKycBalanceLimit(
+              account,
+              Amounts.stringify(state.result.amount),
+            );
+          },
+          (res) => {
+            notify({
+              type: "info",
+              title: i18n.str`No kyc required`,
+              when: AbsoluteTime.now(),
+            });
+          },
+          (fail) => {
+            switch (fail.case) {
+              case HttpStatusCode.Forbidden:
+                return i18n.str`Access denied trying to test balance.`;
+              case HttpStatusCode.UnavailableForLegalReasons:
+                setKycAccount(fail.body.h_payto);
+                return i18n.str`Unavailable For Legal Reasons`;
+              default:
+                assertUnreachable(fail);
+            }
+          },
+        );
+
+  if (kycAccount) {
+    return <div>loading...</div>
+  }
+
+  return (
+    <div class="rounded-lg bg-white px-5 py-6 shadow m-4">
+      <LocalNotificationBanner notification={notification} />
+      <div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
+        {theForm.config.design.map((section, i) => {
+          if (!section) return <Fragment />;
+          return (
+            <div
+              key={i}
+              class="grid grid-cols-1 gap-x-8 gap-y-8 pt-5 md:grid-cols-3"
+            >
+              <div class="px-4 sm:px-0">
+                <h2 class="text-base font-semibold leading-7 text-gray-900">
+                  {section.title}
+                </h2>
+                {section.description && (
+                  <p class="mt-1 text-sm leading-6 text-gray-600">
+                    {section.description}
+                  </p>
+                )}
+              </div>
+              <div class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md 
md:col-span-2">
+                <div class="p-3">
+                  <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 
sm:grid-cols-6">
+                    <RenderAllFieldsByUiConfig
+                      key={i}
+                      fields={convertUiField(
+                        i18n,
+                        section.fields,
+                        form,
+                        getConverterById,
+                      )}
+                    />
+                  </div>
+                </div>
+              </div>
+            </div>
+          );
+        })}
+      </div>
+
+      <div class="mt-6 flex items-center justify-end gap-x-6">
+        <button
+          onClick={() => {}}
+          class="text-sm font-semibold leading-6 text-gray-900"
+        >
+          <i18n.Translate>Cancel</i18n.Translate>
+        </button>
+        <Button
+          type="submit"
+          handler={submitHandler}
+          // disabled={!submitHandler}
+          class="disabled:opacity-50 disabled:cursor-default rounded-md 
bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm 
hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 
focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
+        >
+          <i18n.Translate>Confirm</i18n.Translate>
+        </Button>
+      </div>
+    </div>
+  );
+}
diff --git a/packages/taler-harness/src/index.ts 
b/packages/taler-harness/src/index.ts
index f209c1906..4de5b1527 100644
--- a/packages/taler-harness/src/index.ts
+++ b/packages/taler-harness/src/index.ts
@@ -1447,6 +1447,7 @@ const allAmlPrograms: TalerKycAml.AmlProgramDefinition[] 
= [
       return outcome;
     },
     requiredAttributes: [],
+    requiredInputs: [],
     requiredContext: [],
   },
   AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT,
diff --git 
a/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts 
b/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts
index b3405bf02..0ced6a697 100644
--- a/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts
+++ b/packages/taler-harness/src/integrationtests/test-kyc-skip-expiration.ts
@@ -75,6 +75,7 @@ export const AML_PROGRAM_FROM_ATTRIBUTES_TO_CONTEXT: 
TalerKycAml.AmlProgramDefin
       return outcome;
     },
     requiredAttributes: [],
+    requiredInputs: [],
     requiredContext: [],
   };
 
diff --git a/packages/taler-util/src/http-client/exchange.ts 
b/packages/taler-util/src/http-client/exchange.ts
index 7526fd8a0..319ed47d8 100644
--- a/packages/taler-util/src/http-client/exchange.ts
+++ b/packages/taler-util/src/http-client/exchange.ts
@@ -38,6 +38,7 @@ import {
 import { Codec, codecForAny } from "../codec.js";
 import {
   TalerSignaturePurpose,
+  amountToBuffer,
   bufferForUint64,
   buildSigPS,
   decodeCrock,
@@ -616,7 +617,7 @@ export class TalerExchangeHttpClient {
     const body: WalletKycRequest = {
       balance,
       reserve_pub: account.id,
-      reserve_sig: encodeCrock(account.signingKey),
+      reserve_sig: buildKYCWalletBalanceSignature(account.signingKey, balance),
     };
 
     const resp = await this.httpLib.fetch(url.href, {
@@ -1049,6 +1050,17 @@ export class TalerExchangeHttpClient {
   }
 }
 
+function buildKYCWalletBalanceSignature(
+  key: SigningKey,
+  balance: AmountString,
+): string {
+  const sigBlob = buildSigPS(TalerSignaturePurpose.WALLET_ACCOUNT_SETUP)
+    .put(amountToBuffer(balance))
+    .build();
+
+  return encodeCrock(eddsaSign(sigBlob, key));
+}
+
 function buildKYCQuerySignature(key: SigningKey): string {
   const sigBlob = buildSigPS(TalerSignaturePurpose.KYC_AUTH).build();
 
diff --git a/packages/taler-util/src/http-client/officer-account.ts 
b/packages/taler-util/src/http-client/officer-account.ts
index f5f55ea1b..6a2663155 100644
--- a/packages/taler-util/src/http-client/officer-account.ts
+++ b/packages/taler-util/src/http-client/officer-account.ts
@@ -19,6 +19,8 @@ import {
   LockedAccount,
   OfficerAccount,
   OfficerId,
+  ReserveAccount,
+  ReservePub,
   SigningKey,
   createEddsaKeyPair,
   decodeCrock,
@@ -106,27 +108,25 @@ export async function createNewOfficerAccount(
  */
 export async function createNewWalletKycAccount(
   extraNonce: EncryptionNonce,
-  password: string,
-): Promise<OfficerAccount & { safe: LockedAccount }> {
+  password?: string,
+): Promise<ReserveAccount & { safe?: LockedAccount }> {
   const { eddsaPriv, eddsaPub } = createEddsaKeyPair();
 
-  const key = stringToBytes(password);
+  const mergedRnd: EncryptionNonce = extraNonce && password
+    ? kdf(24, stringToBytes("aml-officer"), extraNonce, getRandomBytesF(24))
+    : getRandomBytesF(24);
 
-  const localRnd = getRandomBytesF(24);
-  const mergedRnd: EncryptionNonce = extraNonce
-    ? kdf(24, stringToBytes("aml-officer"), extraNonce, localRnd)
-    : localRnd;
-
-  const protectedPrivKey = await encryptWithDerivedKey(
+  
+  const protectedPrivKey = password ? await encryptWithDerivedKey(
     mergedRnd,
-    key,
+    stringToBytes(password),
     eddsaPriv,
     password,
-  );
+  ) : undefined;
 
   const signingKey = eddsaPriv as SigningKey;
-  const accountId = encodeCrock(eddsaPub) as OfficerId;
-  const safe = encodeCrock(protectedPrivKey) as LockedAccount;
+  const accountId = encodeCrock(eddsaPub) as ReservePub;
+  const safe = protectedPrivKey ? encodeCrock(protectedPrivKey) as 
LockedAccount : undefined;
 
   return { id: accountId, signingKey, safe };
 }
diff --git a/packages/web-util/src/forms/HtmlIframe.tsx 
b/packages/web-util/src/forms/HtmlIframe.tsx
new file mode 100644
index 000000000..4569011a6
--- /dev/null
+++ b/packages/web-util/src/forms/HtmlIframe.tsx
@@ -0,0 +1,43 @@
+import { TranslatedString } from "@gnu-taler/taler-util";
+import { VNode, h } from "preact";
+import { LabelWithTooltipMaybeRequired, RenderAddon } from "./InputLine.js";
+import { Addon } from "./FormProvider.js";
+
+interface Props {
+  label: TranslatedString;
+  url: string;
+  tooltip?: TranslatedString;
+  help?: TranslatedString;
+  before?: Addon;
+  after?: Addon;
+}
+
+export function HtmlIframe({
+  before,
+  url,
+  after,
+  label,
+  tooltip,
+  help,
+}: Props): VNode {
+  return (
+    <div class="sm:col-span-6">
+      {before !== undefined && <RenderAddon addon={before} />}
+      <a
+        href={url}
+        target="_blank"
+        rel="noreferrer"
+        class="underline cursor-pointer"
+      >
+        <p>{label}</p>
+      </a>
+      <iframe src={url} title={label} class="w-full"></iframe>
+      {after !== undefined && <RenderAddon addon={after} />}
+      {help && (
+        <p class="mt-2 text-sm text-gray-500" id="email-description">
+          {help}
+        </p>
+      )}
+    </div>
+  );
+}
diff --git a/packages/web-util/src/forms/forms.ts 
b/packages/web-util/src/forms/forms.ts
index 2c789b9a3..70c090fb1 100644
--- a/packages/web-util/src/forms/forms.ts
+++ b/packages/web-util/src/forms/forms.ts
@@ -20,12 +20,14 @@ import {
 } from "../index.browser.js";
 import { assertUnreachable, TranslatedString } from "@gnu-taler/taler-util";
 import { UIFormFieldBaseConfig, UIFormElementConfig } from "./ui-form.js";
+import { HtmlIframe } from "./HtmlIframe.js";
 /**
  * Constrain the type with the ui props
  */
 type FieldType<T extends object = any, K extends keyof T = any> = {
   group: Parameters<typeof Group>[0];
   caption: Parameters<typeof Caption>[0];
+  htmlIframe: Parameters<typeof HtmlIframe>[0];
   array: Parameters<typeof InputArray<T, K>>[0];
   file: Parameters<typeof InputFile<T, K>>[0];
   selectOne: Parameters<typeof InputSelectOne<T, K>>[0];
@@ -46,6 +48,7 @@ type FieldType<T extends object = any, K extends keyof T = 
any> = {
 export type UIFormField =
   | { type: "group"; properties: FieldType["group"] }
   | { type: "caption"; properties: FieldType["caption"] }
+  | { type: "htmlIframe"; properties: FieldType["htmlIframe"] }
   | { type: "array"; properties: FieldType["array"] }
   | { type: "file"; properties: FieldType["file"] }
   | { type: "amount"; properties: FieldType["amount"] }
@@ -85,6 +88,7 @@ type UIFormFieldMap = {
 const UIFormConfiguration: UIFormFieldMap = {
   group: Group,
   caption: Caption,
+  htmlIframe: HtmlIframe,
   //@ts-ignore
   array: InputArray,
   text: InputText,
@@ -173,6 +177,16 @@ export function convertUiField(
         };
         return resp;
       }
+      case "htmlIframe": {
+        const resp: UIFormField = {
+          type: config.type,
+          properties: {
+            ...converBaseFieldsProps(i18n_, config),
+            url: config.url
+          },
+        };
+        return resp;
+      }
       case "group": {
         const resp: UIFormField = {
           type: config.type,
diff --git a/packages/web-util/src/forms/index.ts 
b/packages/web-util/src/forms/index.ts
index 7320c70d0..5e3f4e1d3 100644
--- a/packages/web-util/src/forms/index.ts
+++ b/packages/web-util/src/forms/index.ts
@@ -1,5 +1,6 @@
 export * from "./Calendar.js"
 export * from "./Caption.js"
+export * from "./HtmlIframe.js"
 export * from "./DefaultForm.js"
 export * from "./Dialog.js"
 export * from "./FormProvider.js"
diff --git a/packages/web-util/src/forms/ui-form.ts 
b/packages/web-util/src/forms/ui-form.ts
index f26e08f3b..22ef0bdfa 100644
--- a/packages/web-util/src/forms/ui-form.ts
+++ b/packages/web-util/src/forms/ui-form.ts
@@ -3,11 +3,13 @@ import {
   buildCodecForUnion,
   Codec,
   codecForBoolean,
+  codecForCanonBaseUrl,
   codecForConstString,
   codecForLazy,
   codecForList,
   codecForNumber,
   codecForString,
+  codecForStringURL,
   codecForTimestamp,
   codecOptional,
   Integer,
@@ -38,6 +40,7 @@ export type DoubleColumnFormSection = {
 export type UIFormElementConfig =
   | UIFormElementGroup
   | UIFormElementCaption
+  | UIFormElementHtmlIframe
   | UIFormFieldAbsoluteTime
   | UIFormFieldAmount
   | UIFormFieldArray
@@ -73,6 +76,10 @@ type UIFormFieldArray = {
 } & UIFormFieldBaseConfig;
 
 type UIFormElementCaption = { type: "caption" } & UIFieldElementDescription;
+type UIFormElementHtmlIframe = {
+  type: "htmlIframe";
+  url: string;
+} & UIFieldElementDescription;
 
 type UIFormElementGroup = {
   type: "group";
@@ -227,6 +234,12 @@ const codecForUiFormFieldCaption = (): 
Codec<UIFormElementCaption> =>
     .property("type", codecForConstString("caption"))
     .build("UIFormFieldCaption");
 
+const codecForUiFormFieldHtmlIFrame = (): Codec<UIFormElementHtmlIframe> =>
+  codecForUIFormFieldBaseDescriptionTemplate<UIFormElementHtmlIframe>()
+    .property("type", codecForConstString("htmlIframe"))
+    .property("url", codecForStringURL())
+    .build("codecForUiFormFieldHtmlIFrame");
+
 const codecForUiFormSelectUiChoice = (): Codec<SelectUiChoice> =>
   buildCodecForObject<SelectUiChoice>()
     .property("description", codecOptional(codecForString()))
@@ -309,6 +322,7 @@ const codecForUiFormField = (): Codec<UIFormElementConfig> 
=>
     .alternative("absoluteTimeText", codecForUiFormFieldAbsoluteTime())
     .alternative("amount", codecForUiFormFieldAmount())
     .alternative("caption", codecForUiFormFieldCaption())
+    .alternative("htmlIframe", codecForUiFormFieldHtmlIFrame())
     .alternative("choiceHorizontal", codecForUiFormFieldChoiceHorizontal())
     .alternative("choiceStacked", codecForUiFormFieldChoiceStacked())
     .alternative("file", codecForUiFormFieldFile())

-- 
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]