gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (64c3cbc1d -> 0bf92a44d)


From: gnunet
Subject: [taler-wallet-core] branch master updated (64c3cbc1d -> 0bf92a44d)
Date: Wed, 15 Mar 2023 13:25:37 +0100

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

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

    from 64c3cbc1d remove workaround
     new 41339c1db check credetials on login
     new 0700bbe9d always use both servers
     new 0bf92a44d test login with an endpoint and cleaner calculation

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/demobank-ui/src/hooks/backend.ts          |  28 ++++++
 packages/demobank-ui/src/hooks/circuit.ts          |   3 +-
 packages/demobank-ui/src/pages/AdminPage.tsx       |   5 +-
 packages/demobank-ui/src/pages/BusinessAccount.tsx | 111 +++++++++++++--------
 packages/demobank-ui/src/pages/LoginForm.tsx       |  48 ++++++++-
 .../src/components/exception/login.tsx             |  22 ++--
 .../merchant-backoffice-ui/src/hooks/backend.ts    |  19 ++++
 packages/merchant-backoffice-ui/src/hooks/index.ts |   3 -
 packages/web-util/src/serve.ts                     |  34 ++++---
 9 files changed, 199 insertions(+), 74 deletions(-)

diff --git a/packages/demobank-ui/src/hooks/backend.ts 
b/packages/demobank-ui/src/hooks/backend.ts
index 3eaf1f186..851a3fb5b 100644
--- a/packages/demobank-ui/src/hooks/backend.ts
+++ b/packages/demobank-ui/src/hooks/backend.ts
@@ -16,6 +16,7 @@
 
 import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
 import {
+  ErrorType,
   RequestError,
   useLocalStorage,
 } from "@gnu-taler/web-util/lib/index.browser";
@@ -192,6 +193,33 @@ export function usePublicBackend(): useBackendType {
   };
 }
 
+export function useCredentialsChecker() {
+  const { request } = useApiContext();
+  const baseUrl = getInitialBackendBaseURL();
+  //check against account details endpoint
+  //while sandbox backend doesn't have a login endpoint
+  return async function testLogin(
+    username: string,
+    password: string,
+  ): Promise<{
+    valid: boolean;
+    cause?: ErrorType;
+  }> {
+    try {
+      await request(baseUrl, `access-api/accounts/${username}/`, {
+        basicAuth: { username, password },
+        preventCache: true,
+      });
+      return { valid: true };
+    } catch (error) {
+      if (error instanceof RequestError) {
+        return { valid: false, cause: error.cause.type };
+      }
+      return { valid: false, cause: ErrorType.UNEXPECTED };
+    }
+  };
+}
+
 export function useAuthenticatedBackend(): useBackendType {
   const { state } = useBackendContext();
   const { request: requestHandler } = useApiContext();
diff --git a/packages/demobank-ui/src/hooks/circuit.ts 
b/packages/demobank-ui/src/hooks/circuit.ts
index 548862d85..137a7aee2 100644
--- a/packages/demobank-ui/src/hooks/circuit.ts
+++ b/packages/demobank-ui/src/hooks/circuit.ts
@@ -288,9 +288,10 @@ export function useRatiosAndFeeConfig(): HttpResponse<
     HttpResponseOk<SandboxBackend.Circuit.Config>,
     RequestError<SandboxBackend.SandboxError>
   >([`circuit-api/config`], fetcher, {
-    refreshInterval: 1000,
+    refreshInterval: 60 * 1000,
     refreshWhenHidden: false,
     revalidateOnFocus: false,
+    revalidateIfStale: false,
     revalidateOnReconnect: false,
     refreshWhenOffline: false,
     errorRetryCount: 0,
diff --git a/packages/demobank-ui/src/pages/AdminPage.tsx 
b/packages/demobank-ui/src/pages/AdminPage.tsx
index f565455bb..ac0457e9b 100644
--- a/packages/demobank-ui/src/pages/AdminPage.tsx
+++ b/packages/demobank-ui/src/pages/AdminPage.tsx
@@ -36,9 +36,9 @@ import {
 } from "../context/pageState.js";
 import { useAccountDetails } from "../hooks/access.js";
 import {
+  useAdminAccountAPI,
   useBusinessAccountDetails,
   useBusinessAccounts,
-  useAdminAccountAPI,
 } from "../hooks/circuit.js";
 import {
   buildRequestErrorMessage,
@@ -50,7 +50,6 @@ import {
 } from "../utils.js";
 import { ErrorBannerFloat } from "./BankFrame.js";
 import { ShowCashoutDetails } from "./BusinessAccount.js";
-import { PaymentOptions } from "./PaymentOptions.js";
 import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
 import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
 
@@ -581,7 +580,6 @@ function CreateNewAccount({
           template={undefined}
           purpose="create"
           onChange={(a) => {
-            console.log(a);
             setSubmitAccount(a);
           }}
         />
@@ -831,6 +829,7 @@ function RemoveAccount({
             title: i18n.str`Can't delete the account`,
             description: i18n.str`Balance is not empty`,
           }}
+          onClear={() => saveError(undefined)}
         />
       )}
       {error && (
diff --git a/packages/demobank-ui/src/pages/BusinessAccount.tsx 
b/packages/demobank-ui/src/pages/BusinessAccount.tsx
index 128b47114..258802e58 100644
--- a/packages/demobank-ui/src/pages/BusinessAccount.tsx
+++ b/packages/demobank-ui/src/pages/BusinessAccount.tsx
@@ -237,6 +237,7 @@ function CreateCashout({
   if (!result.ok) return onLoadNotOk(result);
   if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
   const config = ratiosResult.data;
+
   const balance = Amounts.parseOrThrow(result.data.balance.amount);
   const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
   const zero = Amounts.zeroOfCurrency(balance.currency);
@@ -254,23 +255,14 @@ function CreateCashout({
   if (!sellRate || sellRate < 0) return <div>error rate</div>;
 
   const amount = Amounts.parse(`${balance.currency}:${form.amount}`);
-  const amount_debit = !amount
-    ? zero
-    : form.isDebit
-    ? amount
-    : truncate(Amounts.divide(Amounts.add(amount, sellFee).amount, sellRate));
-  const credit_before_fee = !amount
-    ? zero
-    : form.isDebit
-    ? truncate(Amounts.divide(amount, 1 / sellRate))
-    : Amounts.add(amount, sellFee).amount;
 
-  const __amount_credit = Amounts.sub(credit_before_fee, sellFee).amount;
-  const amount_credit = Amounts.parseOrThrow(
-    `${fiatCurrency}:${Amounts.stringifyValue(__amount_credit)}`,
-  );
+  const calc = !amount
+    ? { debit: zero, credit: zero, beforeFee: zero }
+    : !form.isDebit
+    ? calculateFromCredit(amount, sellFee, sellRate)
+    : calculateFromDebit(amount, sellFee, sellRate);
 
-  const balanceAfter = Amounts.sub(balance, amount_debit).amount;
+  const balanceAfter = Amounts.sub(balance, calc.debit).amount;
 
   function updateForm(newForm: typeof form): void {
     setForm(newForm);
@@ -280,11 +272,11 @@ function CreateCashout({
       ? i18n.str`required`
       : !amount
       ? i18n.str`could not be parsed`
-      : Amounts.cmp(limit, amount_debit) === -1
+      : Amounts.cmp(limit, calc.debit) === -1
       ? i18n.str`balance is not enough`
-      : Amounts.cmp(credit_before_fee, sellFee) === -1
+      : Amounts.cmp(calc.beforeFee, sellFee) === -1
       ? i18n.str`the total amount to transfer does not cover the fees`
-      : Amounts.isZero(amount_credit)
+      : Amounts.isZero(calc.credit)
       ? i18n.str`the total transfer at destination will be zero`
       : undefined,
     channel: !form.channel ? i18n.str`required` : undefined,
@@ -408,7 +400,7 @@ function CreateCashout({
               id="withdraw-amount"
               disabled
               name="withdraw-amount"
-              value={amount_debit ? Amounts.stringifyValue(amount_debit) : ""}
+              value={Amounts.stringifyValue(calc.debit)}
             />
           </div>
         </fieldset>
@@ -454,7 +446,7 @@ function CreateCashout({
                   // type="number"
                   style={{ color: "black" }}
                   disabled
-                  value={Amounts.stringifyValue(credit_before_fee)}
+                  value={Amounts.stringifyValue(calc.beforeFee)}
                 />
               </div>
             </fieldset>
@@ -503,7 +495,7 @@ function CreateCashout({
               id="withdraw-amount"
               disabled
               name="withdraw-amount"
-              value={amount_credit ? Amounts.stringifyValue(amount_credit) : 
""}
+              value={Amounts.stringifyValue(calc.credit)}
             />
           </div>
         </fieldset>
@@ -584,8 +576,10 @@ function CreateCashout({
               if (errors) return;
               try {
                 const res = await createCashout({
-                  amount_credit: Amounts.stringify(amount_credit),
-                  amount_debit: Amounts.stringify(amount_debit),
+                  amount_credit: `${fiatCurrency}:${Amounts.stringifyValue(
+                    calc.credit,
+                  )}`,
+                  amount_debit: Amounts.stringify(calc.debit),
                   subject: form.subject,
                   tan_channel: form.channel,
                 });
@@ -630,25 +624,6 @@ function CreateCashout({
   );
 }
 
-const MAX_AMOUNT_DIGIT = 2;
-/**
- * Truncate the amount of digits to display
- * in the form based on the fee calculations
- *
- * Backend must have the same truncation
- * @param a
- * @returns
- */
-function truncate(a: AmountJson): AmountJson {
-  const str = Amounts.stringify(a);
-  const idx = str.indexOf(".");
-  if (idx === -1) {
-    return a;
-  }
-  const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
-  return Amounts.parseOrThrow(truncated);
-}
-
 interface ShowCashoutProps {
   id: string;
   onCancel: () => void;
@@ -836,6 +811,58 @@ export function ShowCashoutDetails({
   );
 }
 
+const MAX_AMOUNT_DIGIT = 2;
+/**
+ * Truncate the amount of digits to display
+ * in the form based on the fee calculations
+ *
+ * Backend must have the same truncation
+ * @param a
+ * @returns
+ */
+function truncate(a: AmountJson): AmountJson {
+  const str = Amounts.stringify(a);
+  const idx = str.indexOf(".");
+  if (idx === -1) {
+    return a;
+  }
+  const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
+  return Amounts.parseOrThrow(truncated);
+}
+
+type TransferCalculation = {
+  debit: AmountJson;
+  credit: AmountJson;
+  beforeFee: AmountJson;
+};
+
+function calculateFromDebit(
+  amount: AmountJson,
+  sellFee: AmountJson,
+  sellRate: number,
+): TransferCalculation {
+  const debit = amount;
+
+  const beforeFee = truncate(Amounts.divide(debit, 1 / sellRate));
+
+  const credit = Amounts.sub(beforeFee, sellFee).amount;
+  return { debit, credit, beforeFee };
+}
+
+function calculateFromCredit(
+  amount: AmountJson,
+  sellFee: AmountJson,
+  sellRate: number,
+): TransferCalculation {
+  const credit = amount;
+
+  const beforeFee = Amounts.add(credit, sellFee).amount;
+
+  const debit = truncate(Amounts.divide(beforeFee, sellRate));
+
+  return { debit, credit, beforeFee };
+}
+
 export function assertUnreachable(x: never): never {
   throw new Error("Didn't expect to get here");
 }
diff --git a/packages/demobank-ui/src/pages/LoginForm.tsx 
b/packages/demobank-ui/src/pages/LoginForm.tsx
index 2452745a5..16d2373da 100644
--- a/packages/demobank-ui/src/pages/LoginForm.tsx
+++ b/packages/demobank-ui/src/pages/LoginForm.tsx
@@ -14,12 +14,18 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
+import {
+  ErrorType,
+  useTranslationContext,
+} from "@gnu-taler/web-util/lib/index.browser";
 import { Fragment, h, VNode } from "preact";
 import { useEffect, useRef, useState } from "preact/hooks";
 import { useBackendContext } from "../context/backend.js";
+import { ErrorMessage } from "../context/pageState.js";
+import { useCredentialsChecker } from "../hooks/backend.js";
 import { bankUiSettings } from "../settings.js";
 import { undefinedIfEmpty } from "../utils.js";
+import { ErrorBannerFloat } from "./BankFrame.js";
 import { USERNAME_REGEX } from "./RegistrationPage.js";
 import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
 
@@ -31,6 +37,8 @@ export function LoginForm({ onRegister }: { onRegister: () => 
void }): VNode {
   const [username, setUsername] = useState<string | undefined>();
   const [password, setPassword] = useState<string | undefined>();
   const { i18n } = useTranslationContext();
+  const testLogin = useCredentialsChecker();
+  const [error, saveError] = useState<ErrorMessage | undefined>();
   const ref = useRef<HTMLInputElement>(null);
   useEffect(function focusInput() {
     ref.current?.focus();
@@ -48,6 +56,9 @@ export function LoginForm({ onRegister }: { onRegister: () => 
void }): VNode {
   return (
     <Fragment>
       <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
+      {error && (
+        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
+      )}
       <div class="login-div">
         <form
           class="login-form"
@@ -105,10 +116,41 @@ export function LoginForm({ onRegister }: { onRegister: 
() => void }): VNode {
               type="submit"
               class="pure-button pure-button-primary"
               disabled={!!errors}
-              onClick={(e) => {
+              onClick={async (e) => {
                 e.preventDefault();
                 if (!username || !password) return;
-                backend.logIn({ username, password });
+                const { valid, cause } = await testLogin(username, password);
+                if (valid) {
+                  backend.logIn({ username, password });
+                } else {
+                  switch (cause) {
+                    case ErrorType.CLIENT: {
+                      saveError({
+                        title: i18n.str`Wrong credentials or username`,
+                      });
+                      break;
+                    }
+                    case ErrorType.SERVER: {
+                      saveError({
+                        title: i18n.str`Server had a problem, try again later 
or report.`,
+                      });
+                      break;
+                    }
+                    case ErrorType.TIMEOUT: {
+                      saveError({
+                        title: i18n.str`Could not reach the server, please 
report.`,
+                      });
+                      break;
+                    }
+                    default: {
+                      saveError({
+                        title: i18n.str`Unexpected error, please report.`,
+                      });
+                      break;
+                    }
+                  }
+                  backend.logOut();
+                }
                 setUsername(undefined);
                 setPassword(undefined);
               }}
diff --git a/packages/merchant-backoffice-ui/src/components/exception/login.tsx 
b/packages/merchant-backoffice-ui/src/components/exception/login.tsx
index 9a0411642..435ff1d6a 100644
--- a/packages/merchant-backoffice-ui/src/components/exception/login.tsx
+++ b/packages/merchant-backoffice-ui/src/components/exception/login.tsx
@@ -24,6 +24,7 @@ import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
 import { useBackendContext } from "../../context/backend.js";
 import { useInstanceContext } from "../../context/instance.js";
+import { useCredentialsChecker } from "../../hooks/backend.js";
 import { Notification } from "../../utils/types.js";
 
 interface Props {
@@ -31,15 +32,15 @@ interface Props {
   onConfirm: (backend: string, token?: string) => void;
 }
 
-function getTokenValuePart(t?: string): string | undefined {
+function getTokenValuePart(t: string): string {
   if (!t) return t;
   const match = /secret-token:(.*)/.exec(t);
-  if (!match || !match[1]) return undefined;
+  if (!match || !match[1]) return "";
   return match[1];
 }
 
-function normalizeToken(r: string | undefined): string | undefined {
-  return r ? `secret-token:${encodeURIComponent(r)}` : undefined;
+function normalizeToken(r: string): string {
+  return `secret-token:${encodeURIComponent(r)}`;
 }
 
 function cleanUp(s: string): string {
@@ -53,8 +54,9 @@ function cleanUp(s: string): string {
 export function LoginModal({ onConfirm, withMessage }: Props): VNode {
   const { url: backendUrl, token: baseToken } = useBackendContext();
   const { admin, token: instanceToken } = useInstanceContext();
+  const testLogin = useCredentialsChecker();
   const currentToken = getTokenValuePart(
-    !admin ? baseToken : instanceToken || "",
+    (!admin ? baseToken : instanceToken) ?? "",
   );
   const [token, setToken] = useState(currentToken);
 
@@ -137,8 +139,14 @@ export function LoginModal({ onConfirm, withMessage }: 
Props): VNode {
           >
             <button
               class="button is-info"
-              onClick={(): void => {
-                onConfirm(url, normalizeToken(token));
+              onClick={async () => {
+                const secretToken = normalizeToken(token);
+                const isOk = await testLogin(url, secretToken);
+                if (isOk) {
+                  onConfirm(url, secretToken);
+                } else {
+                  onConfirm(url);
+                }
               }}
             >
               <i18n.Translate>Confirm</i18n.Translate>
diff --git a/packages/merchant-backoffice-ui/src/hooks/backend.ts 
b/packages/merchant-backoffice-ui/src/hooks/backend.ts
index 6c4e5c176..93e95934e 100644
--- a/packages/merchant-backoffice-ui/src/hooks/backend.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/backend.ts
@@ -139,6 +139,25 @@ interface useBackendBaseRequestType {
 
 type YesOrNo = "yes" | "no";
 
+export function useCredentialsChecker() {
+  const { request } = useApiContext();
+  //check against instance details endpoint
+  //while merchant backend doesn't have a login endpoint
+  return async function testLogin(
+    instance: string,
+    token: string,
+  ): Promise<boolean> {
+    try {
+      const response = await request(instance, `/private/`, {
+        token,
+      });
+      return true;
+    } catch (e) {
+      return false;
+    }
+  };
+}
+
 /**
  *
  * @param root the request is intended to the base URL and no the instance URL
diff --git a/packages/merchant-backoffice-ui/src/hooks/index.ts 
b/packages/merchant-backoffice-ui/src/hooks/index.ts
index bb210c9ba..316620cf7 100644
--- a/packages/merchant-backoffice-ui/src/hooks/index.ts
+++ b/packages/merchant-backoffice-ui/src/hooks/index.ts
@@ -59,7 +59,6 @@ export function useBackendDefaultToken(
 export function useBackendInstanceToken(
   id: string,
 ): [string | undefined, StateUpdater<string | undefined>] {
-  const [random, setRandom] = useState(0);
   const [token, setToken] = useLocalStorage(`backend-token-${id}`);
   const [defaultToken, defaultSetToken] = useBackendDefaultToken();
 
@@ -74,8 +73,6 @@ export function useBackendInstanceToken(
   ): void {
     setToken((p) => {
       const toStore = value instanceof Function ? value(p) : value;
-      // setToken(value)
-      setRandom(new Date().getTime());
       return toStore;
     });
   }
diff --git a/packages/web-util/src/serve.ts b/packages/web-util/src/serve.ts
index 597303ba2..f37ef90ce 100644
--- a/packages/web-util/src/serve.ts
+++ b/packages/web-util/src/serve.ts
@@ -31,7 +31,6 @@ export async function serve(opts: {
   port: number;
   source?: string;
   development?: boolean;
-  insecure?: boolean;
   examplesLocationJs?: string;
   examplesLocationCss?: string;
   onUpdate?: () => Promise<void>;
@@ -39,10 +38,10 @@ export async function serve(opts: {
   const app = express();
 
   app.use(PATHS.APP, express.static(opts.folder));
-  const server = opts.insecure
-    ? http.createServer(app)
-    : https.createServer(httpServerOptions, app);
-  logger.info(`serving ${opts.folder} on ${opts.port}`);
+  const servers = [
+    http.createServer(app),
+    https.createServer(httpServerOptions, app),
+  ];
   logger.info(`  ${PATHS.APP}: application`);
   logger.info(`  ${PATHS.EXAMPLE}: examples`);
   logger.info(`  ${PATHS.WS}: websocket`);
@@ -55,15 +54,17 @@ export async function serve(opts: {
       ws.send("welcome");
     });
 
-    server.on("upgrade", function upgrade(request, socket, head) {
-      const { pathname } = parse(request.url || "");
-      if (pathname === PATHS.WS) {
-        wss.handleUpgrade(request, socket, head, function done(ws) {
-          wss.emit("connection", ws, request);
-        });
-      } else {
-        socket.destroy();
-      }
+    servers.forEach(function addWSHandler(server) {
+      server.on("upgrade", function upgrade(request, socket, head) {
+        const { pathname } = parse(request.url || "");
+        if (pathname === PATHS.WS) {
+          wss.handleUpgrade(request, socket, head, function done(ws) {
+            wss.emit("connection", ws, request);
+          });
+        } else {
+          socket.destroy();
+        }
+      });
     });
 
     const sendToAllClients = function (data: object): void {
@@ -121,6 +122,9 @@ export async function serve(opts: {
       res.send("ok");
     });
 
-    server.listen(opts.port);
+    servers.forEach(function startServer(server, index) {
+      logger.info(`serving ${opts.folder} on ${opts.port + index}`);
+      server.listen(opts.port + index);
+    });
   }
 }

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