From 64e3705669e7c12b8013704654f17cf8eaf659d4 Mon Sep 17 00:00:00 2001
From: Sebastian <sebasjm@gmail.com>
Date: Thu, 25 May 2023 18:08:20 -0300
Subject: cases, account details and new-form screen

---
 .../exchange-backoffice-ui/src/pages/Officer.tsx   | 204 +++++++++++----------
 1 file changed, 104 insertions(+), 100 deletions(-)

(limited to 'packages/exchange-backoffice-ui/src/pages/Officer.tsx')

diff --git a/packages/exchange-backoffice-ui/src/pages/Officer.tsx b/packages/exchange-backoffice-ui/src/pages/Officer.tsx
index 4d8b90228..79dd8bace 100644
--- a/packages/exchange-backoffice-ui/src/pages/Officer.tsx
+++ b/packages/exchange-backoffice-ui/src/pages/Officer.tsx
@@ -4,69 +4,60 @@ import {
   notifyInfo,
   useLocalStorage,
   useMemoryStorage,
+  useTranslationContext,
 } from "@gnu-taler/web-util/browser";
 import { VNode, h } from "preact";
 import { useEffect, useState } from "preact/hooks";
 import {
+  Account,
   UnwrapKeyError,
   createNewAccount,
-  createNewSessionId,
   unlockAccount,
 } from "../account.js";
 import { createNewForm } from "../handlers/forms.js";
+import { Officer, codecForOfficer } from "../Dashboard.js";
 
 export function Officer() {
   const password = useMemoryStorage("password");
-  const session = useLocalStorage("session");
-  const officer = useLocalStorage("officer");
-  const [keys, setKeys] = useState({ accountId: "", pub: "" });
+  const officer = useLocalStorage("officer", {
+    codec: codecForOfficer(),
+  });
+  const [keys, setKeys] = useState<Account>();
 
   useEffect(() => {
-    if (
-      officer.value === undefined ||
-      session.value === undefined ||
-      password.value === undefined
-    ) {
+    if (officer.value === undefined || password.value === undefined) {
       return;
     }
-    unlockAccount(session.value, officer.value, password.value)
+
+    unlockAccount(officer.value.salt, officer.value.key, password.value)
       .then((keys) => setKeys(keys ?? { accountId: "", pub: "" }))
       .catch((e) => {
         if (e instanceof UnwrapKeyError) {
           console.log(e);
         }
       });
-  }, [officer.value, session.value, password.value]);
-
-  useEffect(() => {
-    if (!session.value) {
-      session.update(createNewSessionId());
-    }
-  }, []);
-
-  const { value: sessionId } = session;
-  if (!sessionId) {
-    return <div>loading...</div>;
-  }
+  }, [officer.value, password.value]);
 
-  if (officer.value === undefined) {
+  if (
+    officer.value === undefined ||
+    !officer.value.key ||
+    !officer.value.salt
+  ) {
     return (
       <CreateAccount
-        sessionId={sessionId}
-        onNewAccount={(id) => {
-          password.reset();
-          officer.update(id);
+        onNewAccount={(salt, key, pwd) => {
+          password.update(pwd);
+          officer.update({ salt, when: { t_ms: Date.now() }, key });
         }}
       />
     );
   }
 
-  console.log("pwd", password.value);
   if (password.value === undefined) {
     return (
       <UnlockAccount
-        sessionId={sessionId}
-        accountId={officer.value}
+        salt={officer.value.salt}
+        sealedKey={officer.value.key}
         onAccountUnlocked={(pwd) => {
           password.update(pwd);
         }}
@@ -76,42 +67,59 @@ export function Officer() {
 
   return (
     <div>
-      <div>Officer</div>
-      <h1>{sessionId}</h1>
       <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
         Public key
       </h1>
-      <div>
-        <p class="mt-6 leading-8 text-gray-700 break-all">
-          -----BEGIN PUBLIC KEY-----
-          <div>{keys.pub}</div>
-          -----END PUBLIC KEY-----
-        </p>
-      </div>
-      <h1 class="my-2 text-3xl font-bold tracking-tight text-gray-900 ">
-        Private key
-      </h1>
-      <div>
-        <p class="mt-6 leading-8 text-gray-700 break-all">
-          -----BEGIN PRIVATE KEY-----
-          <div>{keys.accountId}</div>
-          -----END PRIVATE KEY-----
-        </p>
+      <div class="max-w-xl text-base leading-7 text-gray-700 lg:max-w-lg">
+        <p class="mt-6 font-mono break-all">{keys?.accountId}</p>
       </div>
+      <p>
+        <a
+          href={`mailto:aml@exchange.taler.net?body=${encodeURIComponent(
+            `I want my AML account\n\n\nPubKey: ${keys?.accountId}`,
+          )}`}
+          target="_blank"
+          rel="noreferrer"
+          class="m-4 block rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700"
+        >
+          Request account activation
+        </a>
+      </p>
+      <p>
+        <button
+          type="button"
+          onClick={() => {
+            password.reset();
+          }}
+          class="m-4 block rounded-md border-0 bg-gray-200 px-3 py-2 text-center text-sm text-black shadow-sm "
+        >
+          Lock account
+        </button>
+      </p>
+      <p>
+        <button
+          type="button"
+          onClick={() => {
+            officer.reset();
+          }}
+          class="m-4 block rounded-md bg-red-600 px-3 py-2 text-center text-sm  text-white shadow-sm hover:bg-red-500 "
+        >
+          Remove account
+        </button>
+      </p>
     </div>
   );
 }
 
 function CreateAccount({
-  sessionId,
   onNewAccount,
 }: {
-  sessionId: string;
-  onNewAccount: (accountId: string) => void;
+  onNewAccount: (salt: string, accountId: string, password: string) => void;
 }): VNode {
+  const { i18n } = useTranslationContext();
   const Form = createNewForm<{
-    email: string;
     password: string;
+    repeat: string;
   }>();
 
   return (
@@ -125,24 +133,50 @@ function CreateAccount({
       <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] ">
         <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12">
           <Form.Provider
+            computeFormState={(v) => {
+              return {
+                password: {
+                  error: !v.password
+                    ? i18n.str`required`
+                    : v.password.length < 8
+                    ? i18n.str`should have at least 8 characters`
+                    : !v.password.match(/[a-z]/) && v.password.match(/[A-Z]/)
+                    ? i18n.str`should have lowercase and uppercase characters`
+                    : !v.password.match(/\d/)
+                    ? i18n.str`should have numbers`
+                    : !v.password.match(/[^a-zA-Z\d]/)
+                    ? i18n.str`should have at least one character which is not a number or letter`
+                    : undefined,
+                },
+                repeat: {
+                  // error: !v.repeat
+                  //   ? i18n.str`required`
+                  //   // : v.repeat !== v.password
+                  //   // ? i18n.str`doesn't match`
+                  //   : undefined,
+                },
+              };
+            }}
             onSubmit={async (v) => {
-              const keys = await createNewAccount(sessionId, v.password);
-              onNewAccount(keys.accountId);
+              const keys = await createNewAccount(v.password);
+              onNewAccount(keys.salt, keys.accountId, v.password);
             }}
           >
             <div class="mb-4">
               <Form.InputLine
-                label={"Email" as TranslatedString}
-                name="email"
-                type="email"
+                label={"Password" as TranslatedString}
+                name="password"
+                type="password"
+                help={
+                  "lower and upper case letters, number and special character" as TranslatedString
+                }
                 required
               />
             </div>
-
             <div class="mb-4">
               <Form.InputLine
-                label={"Password" as TranslatedString}
-                name="password"
+                label={"Repeat password" as TranslatedString}
+                name="repeat"
                 type="password"
                 required
               />
@@ -164,17 +198,15 @@ function CreateAccount({
 }
 
 function UnlockAccount({
-  sessionId,
-  accountId,
+  salt,
+  sealedKey,
   onAccountUnlocked,
 }: {
-  sessionId: string;
-  accountId: string;
+  salt: string;
+  sealedKey: string;
   onAccountUnlocked: (password: string) => void;
 }): VNode {
   const Form = createNewForm<{
-    sessionId: string;
-    accountId: string;
     password: string;
   }>();
 
@@ -182,34 +214,21 @@ function UnlockAccount({
     <div class="flex min-h-full flex-col ">
       <div class="sm:mx-auto sm:w-full sm:max-w-md">
         <h2 class="mt-6 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">
-          Unlock account
+          Account locked
         </h2>
+        <p class="mt-6 text-lg leading-8 text-gray-600">
+          Your account is normally locked anytime you reload. To unlock type
+          your password again.
+        </p>
       </div>
 
       <div class="mt-10 sm:mx-auto sm:w-full sm:max-w-[480px] ">
         <div class="bg-gray-100 px-6 py-6 shadow sm:rounded-lg sm:px-12">
           <Form.Provider
-            initialValue={{
-              sessionId,
-              accountId:
-                accountId.substring(0, 6) +
-                "..." +
-                accountId.substring(accountId.length - 6),
-            }}
-            computeFormState={(v) => {
-              return {
-                accountId: {
-                  disabled: true,
-                },
-                sessionId: {
-                  disabled: true,
-                },
-              };
-            }}
             onSubmit={async (v) => {
               try {
                 // test login
-                await unlockAccount(sessionId, accountId, v.password);
+                await unlockAccount(salt, sealedKey, v.password);
 
                 onAccountUnlocked(v.password ?? "");
                 notifyInfo("Account unlocked" as TranslatedString);
@@ -225,21 +244,6 @@ function UnlockAccount({
               }
             }}
           >
-            <div class="mb-4">
-              <Form.InputLine
-                label={"Session" as TranslatedString}
-                name="sessionId"
-                type="text"
-              />
-            </div>
-            <div class="mb-4">
-              <Form.InputLine
-                label={"AccountId" as TranslatedString}
-                name="accountId"
-                type="text"
-              />
-            </div>
-
             <div class="mb-4">
               <Form.InputLine
                 label={"Password" as TranslatedString}
-- 
cgit v1.2.3

