gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: validate IBAN, removing inter


From: gnunet
Subject: [taler-wallet-core] branch master updated: validate IBAN, removing internal iban from account form, add missing logo, do not save backend URL in login state
Date: Sun, 05 Mar 2023 19:21:20 +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 4de014927 validate IBAN, removing internal iban from account form, add 
missing logo, do not save backend URL in login state
4de014927 is described below

commit 4de014927e95d792633ea367eb4404459489d44f
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Sun Mar 5 15:21:12 2023 -0300

    validate IBAN, removing internal iban from account form, add missing logo, 
do not save backend URL in login state
---
 copy-demobank-into-prebuilt.sh               |   2 +-
 packages/demobank-ui/README.md               |   2 +
 packages/demobank-ui/src/hooks/backend.ts    |  16 +-
 packages/demobank-ui/src/hooks/circuit.ts    |  10 +-
 packages/demobank-ui/src/pages/AdminPage.tsx | 214 +++++++++++++++------------
 packages/demobank-ui/src/utils.ts            | 205 +++++++++++++++++++++++++
 6 files changed, 339 insertions(+), 110 deletions(-)

diff --git a/copy-demobank-into-prebuilt.sh b/copy-demobank-into-prebuilt.sh
index 5927eac96..3fd4ec5c5 100755
--- a/copy-demobank-into-prebuilt.sh
+++ b/copy-demobank-into-prebuilt.sh
@@ -2,7 +2,7 @@
 
 [ ! -d prebuilt ] && echo 'directory "prebuilt" not found. first checkout the 
prebuilt branch into a prebuilt directory' && exit 1
 
-for file in index.html index.js index.css; do
+for file in index.html index.js index.css logo-white-U55BSKA2.svg; do
        cp packages/demobank-ui/dist/$file prebuilt/demobank/
 done
 
diff --git a/packages/demobank-ui/README.md b/packages/demobank-ui/README.md
index b8f96c5ea..1732b5f38 100644
--- a/packages/demobank-ui/README.md
+++ b/packages/demobank-ui/README.md
@@ -18,6 +18,7 @@ By default, the demobank-ui points to 
`https://bank.demo.taler.net/demobanks/def
 as the bank access API base URL.
 
 This can be changed for testing by setting the URL via local storage (via your 
browser's devtools):
+
 ```
 localStorage.setItem("bank-base-url", OTHER_URL);
 ```
@@ -35,6 +36,7 @@ to the default settings:
 
 ```
 globalThis.talerDemobankSettings = {
+  backendBaseURL: "https://bank.demo.taler.net/demobanks/default/";,
   allowRegistrations: true,
   bankName: "Taler Bank",
   // Show explainer text and navbar to other demo sites
diff --git a/packages/demobank-ui/src/hooks/backend.ts 
b/packages/demobank-ui/src/hooks/backend.ts
index 3f2981edf..3eaf1f186 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -42,26 +42,23 @@ export interface BackendCredentials {
 }
 
 interface LoggedIn extends BackendCredentials {
-  url: string;
   status: "loggedIn";
   isUserAdministrator: boolean;
 }
 interface LoggedOut {
-  url: string;
   status: "loggedOut";
 }
 
-const maybeRootPath = bankUiSettings.backendBaseURL;
-
 export function getInitialBackendBaseURL(): string {
   const overrideUrl = localStorage.getItem("bank-base-url");
 
-  return canonicalizeBaseUrl(overrideUrl ? overrideUrl : maybeRootPath);
+  return canonicalizeBaseUrl(
+    overrideUrl ? overrideUrl : bankUiSettings.backendBaseURL,
+  );
 }
 
 export const defaultState: BackendState = {
   status: "loggedOut",
-  url: getInitialBackendBaseURL(),
 };
 
 export interface BackendStateHandler {
@@ -91,13 +88,12 @@ export function useBackendState(): BackendStateHandler {
   return {
     state,
     logOut() {
-      update(JSON.stringify({ ...defaultState, url: state.url }));
+      update(JSON.stringify({ ...defaultState }));
     },
     logIn(info) {
       //admin is defined by the username
       const nextState: BackendState = {
         status: "loggedIn",
-        url: state.url,
         ...info,
         isUserAdministrator: info.username === "admin",
       };
@@ -125,7 +121,7 @@ export function usePublicBackend(): useBackendType {
   const { state } = useBackendContext();
   const { request: requestHandler } = useApiContext();
 
-  const baseUrl = state.url;
+  const baseUrl = getInitialBackendBaseURL();
 
   const request = useCallback(
     function requestImpl<T>(
@@ -201,7 +197,7 @@ export function useAuthenticatedBackend(): useBackendType {
   const { request: requestHandler } = useApiContext();
 
   const creds = state.status === "loggedIn" ? state : undefined;
-  const baseUrl = state.url;
+  const baseUrl = getInitialBackendBaseURL();
 
   const request = useCallback(
     function requestImpl<T>(
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index c2563adb4..423ed1a5b 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -24,7 +24,11 @@ import {
 import { useEffect, useMemo, useState } from "preact/hooks";
 import { useBackendContext } from "../context/backend.js";
 import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils.js";
-import { useAuthenticatedBackend, useMatchMutate } from "./backend.js";
+import {
+  getInitialBackendBaseURL,
+  useAuthenticatedBackend,
+  useMatchMutate,
+} from "./backend.js";
 
 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
 import _useSWR, { SWRHook } from "swr";
@@ -210,10 +214,10 @@ export interface CircuitAccountAPI {
 
 async function getBusinessStatus(
   request: ReturnType<typeof useApiContext>["request"],
-  url: string,
   basicAuth: { username: string; password: string },
 ): Promise<boolean> {
   try {
+    const url = getInitialBackendBaseURL();
     const result = await request<
       HttpResponseOk<SandboxBackend.Circuit.CircuitAccountData>
     >(url, `circuit-api/accounts/${basicAuth.username}`, { basicAuth });
@@ -234,7 +238,7 @@ export function useBusinessAccountFlag(): boolean | 
undefined {
 
   useEffect(() => {
     if (!creds) return;
-    getBusinessStatus(request, state.url, creds)
+    getBusinessStatus(request, creds)
       .then((result) => {
         setIsBusiness(result);
       })
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx 
b/packages/demobank-ui/src/pages/AdminPage.tsx
index 2a5701a95..3d0c09cbf 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -40,6 +40,7 @@ import {
   PartialButDefined,
   RecursivePartial,
   undefinedIfEmpty,
+  validateIBAN,
   WithIntermediate,
 } from "../utils.js";
 import { ErrorBannerFloat } from "./BankFrame.js";
@@ -230,74 +231,78 @@ export function AdminPage({ onLoadNotOk }: Props): VNode {
       </p>
 
       <section id="main">
-        <article>
-          <h2>{i18n.str`Accounts:`}</h2>
-          <div class="results">
-            <table class="pure-table pure-table-striped">
-              <thead>
-                <tr>
-                  <th>{i18n.str`Username`}</th>
-                  <th>{i18n.str`Name`}</th>
-                  <th></th>
-                  <th></th>
-                </tr>
-              </thead>
-              <tbody>
-                {customers.map((item, idx) => {
-                  return (
-                    <tr key={idx}>
-                      <td>
-                        <a
-                          href="#"
-                          onClick={(e) => {
-                            e.preventDefault();
-                            setShowDetails(item.username);
-                          }}
-                        >
-                          {item.username}
-                        </a>
-                      </td>
-                      <td>{item.name}</td>
-                      <td>
-                        <a
-                          href="#"
-                          onClick={(e) => {
-                            e.preventDefault();
-                            setUpdatePassword(item.username);
-                          }}
-                        >
-                          change password
-                        </a>
-                      </td>
-                      <td>
-                        <a
-                          href="#"
-                          onClick={(e) => {
-                            e.preventDefault();
-                            setShowCashouts(item.username);
-                          }}
-                        >
-                          cashouts
-                        </a>
-                      </td>
-                      <td>
-                        <a
-                          href="#"
-                          onClick={(e) => {
-                            e.preventDefault();
-                            setRemoveAccount(item.username);
-                          }}
-                        >
-                          remove
-                        </a>
-                      </td>
-                    </tr>
-                  );
-                })}
-              </tbody>
-            </table>
-          </div>
-        </article>
+        {!customers.length ? (
+          <div></div>
+        ) : (
+          <article>
+            <h2>{i18n.str`Accounts:`}</h2>
+            <div class="results">
+              <table class="pure-table pure-table-striped">
+                <thead>
+                  <tr>
+                    <th>{i18n.str`Username`}</th>
+                    <th>{i18n.str`Name`}</th>
+                    <th></th>
+                    <th></th>
+                  </tr>
+                </thead>
+                <tbody>
+                  {customers.map((item, idx) => {
+                    return (
+                      <tr key={idx}>
+                        <td>
+                          <a
+                            href="#"
+                            onClick={(e) => {
+                              e.preventDefault();
+                              setShowDetails(item.username);
+                            }}
+                          >
+                            {item.username}
+                          </a>
+                        </td>
+                        <td>{item.name}</td>
+                        <td>
+                          <a
+                            href="#"
+                            onClick={(e) => {
+                              e.preventDefault();
+                              setUpdatePassword(item.username);
+                            }}
+                          >
+                            change password
+                          </a>
+                        </td>
+                        <td>
+                          <a
+                            href="#"
+                            onClick={(e) => {
+                              e.preventDefault();
+                              setShowCashouts(item.username);
+                            }}
+                          >
+                            cashouts
+                          </a>
+                        </td>
+                        <td>
+                          <a
+                            href="#"
+                            onClick={(e) => {
+                              e.preventDefault();
+                              setRemoveAccount(item.username);
+                            }}
+                          >
+                            remove
+                          </a>
+                        </td>
+                      </tr>
+                    );
+                  })}
+                </tbody>
+              </table>
+            </div>
+          </article>
+        )}
       </section>
     </Fragment>
   );
@@ -835,15 +840,15 @@ function AccountForm({
         ? i18n.str`only "IBAN" target are supported`
         : !IBAN_REGEX.test(parsed.iban)
         ? i18n.str`IBAN should have just uppercased letters and numbers`
-        : undefined,
+        : validateIBAN(parsed.iban, i18n),
       contact_data: undefinedIfEmpty({
         email: !newForm.contact_data?.email
-          ? undefined
+          ? i18n.str`required`
           : !EMAIL_REGEX.test(newForm.contact_data.email)
           ? i18n.str`it should be an email`
           : undefined,
         phone: !newForm.contact_data?.phone
-          ? undefined
+          ? i18n.str`required`
           : !newForm.contact_data.phone.startsWith("+")
           ? i18n.str`should start with +`
           : !REGEX_JUST_NUMBERS_REGEX.test(newForm.contact_data.phone)
@@ -851,10 +856,10 @@ function AccountForm({
           : undefined,
       }),
       iban: !newForm.iban
-        ? i18n.str`required`
+        ? undefined //optional field
         : !IBAN_REGEX.test(newForm.iban)
         ? i18n.str`IBAN should have just uppercased letters and numbers`
-        : undefined,
+        : validateIBAN(newForm.iban, i18n),
       name: !newForm.name ? i18n.str`required` : undefined,
       username: !newForm.username ? i18n.str`required` : undefined,
     });
@@ -866,7 +871,10 @@ function AccountForm({
   return (
     <form class="pure-form">
       <fieldset>
-        <label for="username">{i18n.str`Username`}</label>
+        <label for="username">
+          {i18n.str`Username`}
+          {purpose === "create" && <b style={{ color: "red" }}>*</b>}
+        </label>
         <input
           name="username"
           type="text"
@@ -876,14 +884,17 @@ function AccountForm({
             form.username = e.currentTarget.value;
             updateForm(structuredClone(form));
           }}
-        />
+        />{" "}
         <ShowInputErrorLabel
           message={errors?.username}
           isDirty={form.username !== undefined}
         />
       </fieldset>
       <fieldset>
-        <label>{i18n.str`Name`}</label>
+        <label>
+          {i18n.str`Name`}
+          {purpose === "create" && <b style={{ color: "red" }}>*</b>}
+        </label>
         <input
           disabled={purpose !== "create"}
           value={form.name ?? ""}
@@ -897,23 +908,28 @@ function AccountForm({
           isDirty={form.name !== undefined}
         />
       </fieldset>
+      {purpose !== "create" && (
+        <fieldset>
+          <label>{i18n.str`Internal IBAN`}</label>
+          <input
+            disabled={true}
+            value={form.iban ?? ""}
+            onChange={(e) => {
+              form.iban = e.currentTarget.value;
+              updateForm(structuredClone(form));
+            }}
+          />
+          <ShowInputErrorLabel
+            message={errors?.iban}
+            isDirty={form.iban !== undefined}
+          />
+        </fieldset>
+      )}
       <fieldset>
-        <label>{i18n.str`Internal IBAN`}</label>
-        <input
-          disabled={purpose !== "create"}
-          value={form.iban ?? ""}
-          onChange={(e) => {
-            form.iban = e.currentTarget.value;
-            updateForm(structuredClone(form));
-          }}
-        />
-        <ShowInputErrorLabel
-          message={errors?.iban}
-          isDirty={form.iban !== undefined}
-        />
-      </fieldset>
-      <fieldset>
-        <label>{i18n.str`Email`}</label>
+        <label>
+          {i18n.str`Email`}
+          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+        </label>
         <input
           disabled={purpose === "show"}
           value={form.contact_data.email ?? ""}
@@ -928,7 +944,10 @@ function AccountForm({
         />
       </fieldset>
       <fieldset>
-        <label>{i18n.str`Phone`}</label>
+        <label>
+          {i18n.str`Phone`}
+          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+        </label>
         <input
           disabled={purpose === "show"}
           value={form.contact_data.phone ?? ""}
@@ -943,12 +962,15 @@ function AccountForm({
         />
       </fieldset>
       <fieldset>
-        <label>{i18n.str`Cashout address`}</label>
+        <label>
+          {i18n.str`Cashout address`}
+          {purpose !== "show" && <b style={{ color: "red" }}>*</b>}
+        </label>
         <input
           disabled={purpose === "show"}
-          value={form.cashout_address ?? ""}
+          value={(form.cashout_address ?? 
"").substring("payto://iban/".length)}
           onChange={(e) => {
-            form.cashout_address = e.currentTarget.value;
+            form.cashout_address = "payto://iban/" + e.currentTarget.value;
             updateForm(structuredClone(form));
           }}
         />
diff --git a/packages/demobank-ui/src/utils.ts 
b/packages/demobank-ui/src/utils.ts
index 81dd450a4..0796db65d 100644
--- a/packages/demobank-ui/src/utils.ts
+++ b/packages/demobank-ui/src/utils.ts
@@ -161,3 +161,208 @@ export function buildRequestErrorMessage(
   }
   return result;
 }
+
+export const COUNTRY_TABLE = {
+  AE: "U.A.E.",
+  AF: "Afghanistan",
+  AL: "Albania",
+  AM: "Armenia",
+  AN: "Netherlands Antilles",
+  AR: "Argentina",
+  AT: "Austria",
+  AU: "Australia",
+  AZ: "Azerbaijan",
+  BA: "Bosnia and Herzegovina",
+  BD: "Bangladesh",
+  BE: "Belgium",
+  BG: "Bulgaria",
+  BH: "Bahrain",
+  BN: "Brunei Darussalam",
+  BO: "Bolivia",
+  BR: "Brazil",
+  BT: "Bhutan",
+  BY: "Belarus",
+  BZ: "Belize",
+  CA: "Canada",
+  CG: "Congo",
+  CH: "Switzerland",
+  CI: "Cote d'Ivoire",
+  CL: "Chile",
+  CM: "Cameroon",
+  CN: "People's Republic of China",
+  CO: "Colombia",
+  CR: "Costa Rica",
+  CS: "Serbia and Montenegro",
+  CZ: "Czech Republic",
+  DE: "Germany",
+  DK: "Denmark",
+  DO: "Dominican Republic",
+  DZ: "Algeria",
+  EC: "Ecuador",
+  EE: "Estonia",
+  EG: "Egypt",
+  ER: "Eritrea",
+  ES: "Spain",
+  ET: "Ethiopia",
+  FI: "Finland",
+  FO: "Faroe Islands",
+  FR: "France",
+  GB: "United Kingdom",
+  GD: "Caribbean",
+  GE: "Georgia",
+  GL: "Greenland",
+  GR: "Greece",
+  GT: "Guatemala",
+  HK: "Hong Kong",
+  // HK: "Hong Kong S.A.R.",
+  HN: "Honduras",
+  HR: "Croatia",
+  HT: "Haiti",
+  HU: "Hungary",
+  ID: "Indonesia",
+  IE: "Ireland",
+  IL: "Israel",
+  IN: "India",
+  IQ: "Iraq",
+  IR: "Iran",
+  IS: "Iceland",
+  IT: "Italy",
+  JM: "Jamaica",
+  JO: "Jordan",
+  JP: "Japan",
+  KE: "Kenya",
+  KG: "Kyrgyzstan",
+  KH: "Cambodia",
+  KR: "South Korea",
+  KW: "Kuwait",
+  KZ: "Kazakhstan",
+  LA: "Laos",
+  LB: "Lebanon",
+  LI: "Liechtenstein",
+  LK: "Sri Lanka",
+  LT: "Lithuania",
+  LU: "Luxembourg",
+  LV: "Latvia",
+  LY: "Libya",
+  MA: "Morocco",
+  MC: "Principality of Monaco",
+  MD: "Moldava",
+  // MD: "Moldova",
+  ME: "Montenegro",
+  MK: "Former Yugoslav Republic of Macedonia",
+  ML: "Mali",
+  MM: "Myanmar",
+  MN: "Mongolia",
+  MO: "Macau S.A.R.",
+  MT: "Malta",
+  MV: "Maldives",
+  MX: "Mexico",
+  MY: "Malaysia",
+  NG: "Nigeria",
+  NI: "Nicaragua",
+  NL: "Netherlands",
+  NO: "Norway",
+  NP: "Nepal",
+  NZ: "New Zealand",
+  OM: "Oman",
+  PA: "Panama",
+  PE: "Peru",
+  PH: "Philippines",
+  PK: "Islamic Republic of Pakistan",
+  PL: "Poland",
+  PR: "Puerto Rico",
+  PT: "Portugal",
+  PY: "Paraguay",
+  QA: "Qatar",
+  RE: "Reunion",
+  RO: "Romania",
+  RS: "Serbia",
+  RU: "Russia",
+  RW: "Rwanda",
+  SA: "Saudi Arabia",
+  SE: "Sweden",
+  SG: "Singapore",
+  SI: "Slovenia",
+  SK: "Slovak",
+  SN: "Senegal",
+  SO: "Somalia",
+  SR: "Suriname",
+  SV: "El Salvador",
+  SY: "Syria",
+  TH: "Thailand",
+  TJ: "Tajikistan",
+  TM: "Turkmenistan",
+  TN: "Tunisia",
+  TR: "Turkey",
+  TT: "Trinidad and Tobago",
+  TW: "Taiwan",
+  TZ: "Tanzania",
+  UA: "Ukraine",
+  US: "United States",
+  UY: "Uruguay",
+  VA: "Vatican",
+  VE: "Venezuela",
+  VN: "Viet Nam",
+  YE: "Yemen",
+  ZA: "South Africa",
+  ZW: "Zimbabwe",
+};
+
+/**
+ * An IBAN is validated by converting it into an integer and performing a
+ * basic mod-97 operation (as described in ISO 7064) on it.
+ * If the IBAN is valid, the remainder equals 1.
+ *
+ * The algorithm of IBAN validation is as follows:
+ * 1.- Check that the total IBAN length is correct as per the country. If not, 
the IBAN is invalid
+ * 2.- Move the four initial characters to the end of the string
+ * 3.- Replace each letter in the string with two digits, thereby expanding 
the string, where A = 10, B = 11, ..., Z = 35
+ * 4.- Interpret the string as a decimal integer and compute the remainder of 
that number on division by 97
+ *
+ * If the remainder is 1, the check digit test is passed and the IBAN might be 
valid.
+ *
+ */
+export function validateIBAN(
+  iban: string,
+  i18n: ReturnType<typeof useTranslationContext>["i18n"],
+): string | undefined {
+  // Check total length
+  if (iban.length < 4)
+    return i18n.str`IBAN numbers usually have more that 4 digits`;
+  if (iban.length > 34)
+    return i18n.str`IBAN numbers usually have less that 34 digits`;
+
+  const A_code = "A".charCodeAt(0);
+  const Z_code = "Z".charCodeAt(0);
+  const IBAN = iban.toUpperCase();
+  // check supported country
+  const code = IBAN.substring(0, 2);
+  const found = code in COUNTRY_TABLE;
+  if (!found) return i18n.str`IBAN country code not found`;
+
+  // 2.- Move the four initial characters to the end of the string
+  const step2 = IBAN.substring(4) + iban.substring(0, 4);
+  const step3 = Array.from(step2)
+    .map((letter) => {
+      const code = letter.charCodeAt(0);
+      if (code < A_code || code > Z_code) return letter;
+      return `${letter.charCodeAt(0) - "A".charCodeAt(0) + 10}`;
+    })
+    .join("");
+
+  const checksum = calculate_iban_checksum(step3);
+  if (checksum !== 1)
+    return i18n.str`IBAN number is not valid, checksum is wrong`;
+  return undefined;
+}
+
+function calculate_iban_checksum(str: string): number {
+  const numberStr = str.substring(0, 5);
+  const rest = str.substring(5);
+  const number = parseInt(numberStr, 10);
+  const result = number % 97;
+  if (rest.length > 0) {
+    return calculate_iban_checksum(`${result}${rest}`);
+  }
+  return result;
+}

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