taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit da61377ca9318381aa8a4127e55f0ee616b5a070
parent 847a8dd65c74fdd3cf00d4ee5f0fe5cbb783ca65
Author: Sebastian <sebasjm@gmail.com>
Date:   Tue, 17 Jun 2025 12:02:20 -0300

fix #9572

Diffstat:
Mpackages/aml-backoffice-ui/src/Routing.tsx | 3++-
Mpackages/aml-backoffice-ui/src/hooks/officer.ts | 50++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 46 insertions(+), 7 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/Routing.tsx b/packages/aml-backoffice-ui/src/Routing.tsx @@ -27,7 +27,7 @@ import { assertUnreachable } from "@gnu-taler/taler-util"; import { useEffect } from "preact/hooks"; import { ExchangeAmlFrame } from "./ExchangeAmlFrame.js"; import { useCurrentDecisionRequest } from "./hooks/decision-request.js"; -import { useOfficer } from "./hooks/officer.js"; +import { useExpireSessionAfter1hr, useOfficer } from "./hooks/officer.js"; import { AccountDetails } from "./pages/AccountDetails.js"; import { AccountList } from "./pages/AccountList.js"; import { Dashboard } from "./pages/Dashboard.js"; @@ -151,6 +151,7 @@ function PrivateRouting(): VNode { navigateTo(privatePages.dashboard.url({})); } }, [location]); + useExpireSessionAfter1hr() switch (location.name) { case undefined: { diff --git a/packages/aml-backoffice-ui/src/hooks/officer.ts b/packages/aml-backoffice-ui/src/hooks/officer.ts @@ -16,6 +16,7 @@ import { AbsoluteTime, Codec, + Duration, EddsaPrivP, LockedAccount, OfficerAccount, @@ -35,9 +36,14 @@ import { useExchangeApiContext, useLocalStorage, } from "@gnu-taler/web-util/browser"; -import { useMemo } from "preact/hooks"; +import { useEffect, useMemo } from "preact/hooks"; import { usePreferences } from "./preferences.js"; +const DEFAULT_SESSION_DURATION = Duration.fromSpec({ + // seconds: 10, + hours: 1 +}); + export interface Officer { account: LockedAccount; when: AbsoluteTime; @@ -48,18 +54,20 @@ const codecForLockedAccount = codecForString() as Codec<LockedAccount>; type OfficerAccountString = { id: string; strKey: string; + unlocked: AbsoluteTime; }; export const codecForOfficerAccount = (): Codec<OfficerAccountString> => buildCodecForObject<OfficerAccountString>() - .property("id", codecForString()) // FIXME - .property("strKey", codecForString()) // FIXME + .property("id", codecForString()) + .property("strKey", codecForString()) + .property("unlocked", codecForAbsoluteTime) .build("OfficerAccount"); export const codecForOfficer = (): Codec<Officer> => buildCodecForObject<Officer>() - .property("account", codecForLockedAccount) // FIXME - .property("when", codecForAbsoluteTime) // FIXME + .property("account", codecForLockedAccount) + .property("when", codecForAbsoluteTime) .build("Officer"); export type OfficerState = OfficerNotReady | OfficerReady; @@ -78,6 +86,7 @@ interface OfficerReady { account: OfficerAccount; forget: () => OperationOk<void>; lock: () => OperationOk<void>; + expiration: AbsoluteTime; } const OFFICER_KEY = buildStorageKey("officer", codecForOfficer()); @@ -100,6 +109,7 @@ export function useOfficer(): OfficerState { return { id: accountStorage.value.id as OfficerId, signingKey: decodeCrock(accountStorage.value.strKey) as EddsaPrivP, + unlocked: accountStorage.value.unlocked }; }, [accountStorage.value?.id, accountStorage.value?.strKey]); @@ -127,7 +137,7 @@ export function useOfficer(): OfficerState { // accountStorage.update({ id, signingKey }); const strKey = encodeCrock(signingKey); - accountStorage.update({ id, strKey }); + accountStorage.update({ id, strKey, unlocked: AbsoluteTime.now() }); return opFixedSuccess(id); }, @@ -147,15 +157,22 @@ export function useOfficer(): OfficerState { accountStorage.update({ id: ac.id, strKey: encodeCrock(ac.signingKey), + unlocked: AbsoluteTime.now(), }); return opFixedSuccess(undefined); }, }; } + const expiration = AbsoluteTime.addDuration( + account.unlocked, + DEFAULT_SESSION_DURATION, + ); + return { state: "ready", account, + expiration, lock: () => { accountStorage.reset(); return opFixedSuccess(undefined); @@ -167,3 +184,24 @@ export function useOfficer(): OfficerState { }, }; } + +export function useExpireSessionAfter1hr() { + const officer = useOfficer(); + + const session = officer.state !== "ready" ? undefined : officer; + + useEffect(() => { + if (!session) return; + const timeLeftBeforeExpiration = Duration.getRemaining(session.expiration); + + if (timeLeftBeforeExpiration.d_ms === "forever") return; + + const remain = timeLeftBeforeExpiration.d_ms; + const timeoutId = setTimeout(async () => { + session.lock(); + }, remain); + return () => { + clearTimeout(timeoutId); + }; + }, [session]); +}