diff options
Diffstat (limited to 'packages/aml-backoffice-ui/src/hooks/officer.ts')
-rw-r--r-- | packages/aml-backoffice-ui/src/hooks/officer.ts | 163 |
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) + }, + }; +} |