summaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src/hooks/officer.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/aml-backoffice-ui/src/hooks/officer.ts')
-rw-r--r--packages/aml-backoffice-ui/src/hooks/officer.ts163
1 files changed, 163 insertions, 0 deletions
diff --git a/packages/aml-backoffice-ui/src/hooks/officer.ts b/packages/aml-backoffice-ui/src/hooks/officer.ts
new file mode 100644
index 000000000..1bb73b8fc
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/hooks/officer.ts
@@ -0,0 +1,163 @@
+/*
+ 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 {
+ AbsoluteTime,
+ Codec,
+ LockedAccount,
+ OfficerAccount,
+ OfficerId,
+ OperationOk,
+ SigningKey,
+ buildCodecForObject,
+ codecForAbsoluteTime,
+ codecForString,
+ createNewOfficerAccount,
+ decodeCrock,
+ encodeCrock,
+ opFixedSuccess,
+ unlockOfficerAccount,
+} from "@gnu-taler/taler-util";
+import { buildStorageKey, useExchangeApiContext, useLocalStorage } from "@gnu-taler/web-util/browser";
+import { useMemo } from "preact/hooks";
+import { usePreferences } from "./preferences.js";
+
+export interface Officer {
+ account: LockedAccount;
+ when: AbsoluteTime;
+}
+
+const codecForLockedAccount = codecForString() as Codec<LockedAccount>;
+
+type OfficerAccountString = {
+ id: string;
+ strKey: string;
+};
+
+export const codecForOfficerAccount = (): Codec<OfficerAccountString> =>
+ buildCodecForObject<OfficerAccountString>()
+ .property("id", codecForString()) // FIXME
+ .property("strKey", codecForString()) // FIXME
+ .build("OfficerAccount");
+
+export const codecForOfficer = (): Codec<Officer> =>
+ buildCodecForObject<Officer>()
+ .property("account", codecForLockedAccount) // FIXME
+ .property("when", codecForAbsoluteTime) // FIXME
+ .build("Officer");
+
+export type OfficerState = OfficerNotReady | OfficerReady;
+export type OfficerNotReady = OfficerNotFound | OfficerLocked;
+interface OfficerNotFound {
+ state: "not-found";
+ create: (password: string) => Promise<OperationOk<OfficerId>>;
+}
+interface OfficerLocked {
+ state: "locked";
+ forget: () => OperationOk<void>;
+ tryUnlock: (password: string) => Promise<OperationOk<void>>;
+}
+interface OfficerReady {
+ state: "ready";
+ account: OfficerAccount;
+ forget: () => OperationOk<void>;
+ lock: () => OperationOk<void>;
+}
+
+const OFFICER_KEY = buildStorageKey("officer", codecForOfficer());
+const DEV_ACCOUNT_KEY = buildStorageKey(
+ "account-dev",
+ codecForOfficerAccount(),
+);
+
+export function useOfficer(): OfficerState {
+ const {lib:{exchange: api}} = useExchangeApiContext();
+ const [pref] = usePreferences();
+ pref.keepSessionAfterReload;
+ // dev account, is kept on reloaded.
+ const accountStorage = useLocalStorage(DEV_ACCOUNT_KEY);
+ const account = useMemo(() => {
+ if (!accountStorage.value) return undefined;
+
+ return {
+ id: accountStorage.value.id as OfficerId,
+ signingKey: decodeCrock(accountStorage.value.strKey) as SigningKey,
+ };
+ }, [accountStorage.value?.id, accountStorage.value?.strKey]);
+
+ const officerStorage = useLocalStorage(OFFICER_KEY);
+ const officer = useMemo(() => {
+ if (!officerStorage.value) return undefined;
+ return officerStorage.value;
+ }, [officerStorage.value?.account, officerStorage.value?.when.t_ms]);
+
+ if (officer === undefined) {
+ return {
+ state: "not-found",
+ create: async (pwd: string) => {
+ const resp = await api.getSeed()
+ const extraEntropy = resp.type === "ok" ? resp.body : new Uint8Array();
+
+ const { id, safe, signingKey } = await createNewOfficerAccount(
+ pwd,
+ extraEntropy,
+ );
+ officerStorage.update({
+ account: safe,
+ when: AbsoluteTime.now(),
+ });
+
+ // accountStorage.update({ id, signingKey });
+ const strKey = encodeCrock(signingKey);
+ accountStorage.update({ id, strKey });
+
+ return opFixedSuccess(id)
+ },
+ };
+ }
+
+ if (account === undefined) {
+ return {
+ state: "locked",
+ forget: () => {
+ officerStorage.reset();
+ return opFixedSuccess(undefined)
+ },
+ tryUnlock: async (pwd: string) => {
+ const ac = await unlockOfficerAccount(officer.account, pwd);
+ // accountStorage.update(ac);
+ accountStorage.update({
+ id: ac.id,
+ strKey: encodeCrock(ac.signingKey),
+ });
+ return opFixedSuccess(undefined)
+ },
+ };
+ }
+
+ return {
+ state: "ready",
+ account,
+ lock: () => {
+ accountStorage.reset();
+ return opFixedSuccess(undefined)
+ },
+ forget: () => {
+ officerStorage.reset();
+ accountStorage.reset();
+ return opFixedSuccess(undefined)
+ },
+ };
+}