commit da61377ca9318381aa8a4127e55f0ee616b5a070
parent 847a8dd65c74fdd3cf00d4ee5f0fe5cbb783ca65
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 17 Jun 2025 12:02:20 -0300
fix #9572
Diffstat:
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]);
+}