diff options
Diffstat (limited to 'packages/exchange-backoffice-ui/src/pages/Officer.tsx')
-rw-r--r-- | packages/exchange-backoffice-ui/src/pages/Officer.tsx | 204 |
1 files changed, 104 insertions, 100 deletions
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); @@ -227,21 +246,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} name="password" type="password" |