commit 326d942d9634ee5f2a7b7874be06bc6c8ece167a
parent b8df370f71a002b50adf38820a04cbf813a71515
Author: Sebastian <sebasjm@gmail.com>
Date: Thu, 9 Oct 2025 14:22:23 -0300
fixes #10250 support new info
Diffstat:
1 file changed, 56 insertions(+), 12 deletions(-)
diff --git a/packages/bank-ui/src/pages/SolveMFA.tsx b/packages/bank-ui/src/pages/SolveMFA.tsx
@@ -1,4 +1,5 @@
import {
+ AbsoluteTime,
Challenge,
ChallengeResponse,
HttpStatusCode,
@@ -19,10 +20,8 @@ import {
useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { useSessionState } from "../hooks/session.js";
+import { useState, useEffect } from "preact/hooks";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
-import { AbsoluteTime } from "@gnu-taler/taler-util";
export interface Props {
onCompleted(challenges: string[]): Promise<NotificationMessage | undefined>;
@@ -37,9 +36,11 @@ function SolveChallenge({
onCancel,
onSolved,
username,
+ expiration,
}: {
onCancel: () => void;
challenge: Challenge;
+ expiration: AbsoluteTime;
onSolved: () => void;
username: string;
}): VNode {
@@ -50,10 +51,26 @@ function SolveChallenge({
} = useBankCoreApiContext();
const [notification, notifyOnError] = useLocalNotificationBetter();
+ const [showExpired, setExpired] = useState(
+ expiration !== undefined && AbsoluteTime.isExpired(expiration),
+ );
+
const errors = undefinedIfEmpty({
code: !tanCode ? i18n.str`Required` : undefined,
});
+ useEffect(() => {
+ if (showExpired) return;
+ const remain = AbsoluteTime.remaining(expiration).d_ms;
+ if (remain === "forever") return;
+ const handler = setTimeout(() => {
+ setExpired(true);
+ }, remain);
+ return () => {
+ clearTimeout(handler);
+ };
+ }, []);
+
const doVerification = !tanCode
? undefined
: notifyOnError(
@@ -162,6 +179,23 @@ function SolveChallenge({
</div>
</div>
</form>
+ {expiration.t_ms === "never" ? undefined : (
+ <p class="text-gray-400 text-sm mt-2">
+ <i18n.Translate>
+ It will expired at{" "}
+ <Time format="HH:mm" timestamp={expiration} />
+ </i18n.Translate>
+ </p>
+ )}
+ {showExpired ? (
+ <p class="text-sm">
+ <i18n.Translate>
+ The challenge is expired and can't be solved but you can go
+ back and create a new challenge.
+ </i18n.Translate>
+ </p>
+ ) : undefined}
+
<div class="mt-6 mb-4 flex justify-between">
<button
type="button"
@@ -198,7 +232,10 @@ export function SolveMFAChallenges({
const { i18n } = useTranslationContext();
const [solved, setSolved] = useState<string[]>([]);
- const [selected, setSelected] = useState<Challenge>();
+ const [selected, setSelected] = useState<{
+ ch: Challenge;
+ expiration: AbsoluteTime;
+ }>();
const [notification, notifyOnError] = useLocalNotificationBetter();
const {
lib: { bank: api },
@@ -216,11 +253,12 @@ export function SolveMFAChallenges({
return (
<SolveChallenge
onCancel={() => setSelected(undefined)}
- challenge={selected}
+ challenge={selected.ch}
+ expiration={selected.expiration}
username={username}
onSolved={() => {
setSelected(undefined);
- setSolved([...solved, selected.challenge_id]);
+ setSolved([...solved, selected.ch.challenge_id]);
}}
/>
);
@@ -247,7 +285,12 @@ export function SolveMFAChallenges({
// ),
});
}
- setSelected(ch);
+ setSelected({
+ ch,
+ expiration: !success.body.solve_expiration
+ ? AbsoluteTime.never()
+ : AbsoluteTime.fromProtocolTimestamp(success.body.solve_expiration),
+ });
},
(fail) => {
switch (fail.case) {
@@ -328,10 +371,8 @@ export function SolveMFAChallenges({
</span>
</h2>
{currentChallenge.challenges.map((challenge) => {
- const time = AbsoluteTime.now();
- const alreadySent = AbsoluteTime.isExpired(time);
- // const time = retransmission[challenge.tan_channel];
- // const alreadySent = !AbsoluteTime.isExpired(time);
+ const time = retransmission[challenge.tan_channel];
+ const alreadySent = !AbsoluteTime.isExpired(time);
const noNeedToComplete =
hasSolvedEnough ||
solved.indexOf(challenge.challenge_id) !== -1;
@@ -339,7 +380,10 @@ export function SolveMFAChallenges({
const doSelect = noNeedToComplete
? undefined
: async () => {
- setSelected(challenge);
+ setSelected({
+ ch: challenge,
+ expiration: AbsoluteTime.never(),
+ });
};
const doSend =