taler-typescript-core

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

commit b8df370f71a002b50adf38820a04cbf813a71515
parent 70cb3f785671fac02b01e9b3aff1fccc4cfcfb55
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu,  9 Oct 2025 13:35:57 -0300

fixes #10250 support new info

Diffstat:
Mpackages/bank-ui/src/pages/SolveMFA.tsx | 44+++++++++++++++++++++++++++++++++++++++-----
Mpackages/merchant-backoffice-ui/src/components/SolveMFA.tsx | 10+++++-----
Mpackages/taler-util/src/http-client/bank-core.ts | 12++++++++++--
Mpackages/taler-util/src/http-client/merchant.ts | 6++++--
4 files changed, 58 insertions(+), 14 deletions(-)

diff --git a/packages/bank-ui/src/pages/SolveMFA.tsx b/packages/bank-ui/src/pages/SolveMFA.tsx @@ -12,6 +12,7 @@ import { makeSafeCall, NotificationMessage, ShowInputErrorLabel, + Time, undefinedIfEmpty, useBankCoreApiContext, useLocalNotificationBetter, @@ -21,6 +22,7 @@ import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useSessionState } from "../hooks/session.js"; import { doAutoFocus } from "./PaytoWireTransferForm.js"; +import { AbsoluteTime } from "@gnu-taler/taler-util"; export interface Props { onCompleted(challenges: string[]): Promise<NotificationMessage | undefined>; @@ -66,6 +68,8 @@ function SolveChallenge({ }, (resp) => { switch (resp.case) { + case TalerErrorCode.BANK_TRANSACTION_NOT_FOUND: + return i18n.str`Unknown challenge.`; case HttpStatusCode.Unauthorized: return i18n.str`Failed to validate the verification code.`; case HttpStatusCode.TooManyRequests: @@ -199,6 +203,14 @@ export function SolveMFAChallenges({ const { lib: { bank: api }, } = useBankCoreApiContext(); + // FIXME: we should save here also the expiration of the + // tan channel to be used when the user press "i have the code" + const [retransmission, setRetransmission] = useState< + Record<TanChannel, AbsoluteTime> + >({ + email: AbsoluteTime.now(), + sms: AbsoluteTime.now(), + }); if (selected) { return ( @@ -226,6 +238,15 @@ export function SolveMFAChallenges({ i18n, (user: string, ch: Challenge) => api.sendChallenge(user, ch.challenge_id), (success, user, ch) => { + if (success.body.earliest_retransmission) { + setRetransmission({ + ...retransmission, + [ch.tan_channel]: AbsoluteTime.now(), + // AbsoluteTime.fromProtocolTimestamp( + // success.body.earliest_retransmission, + // ), + }); + } setSelected(ch); }, (fail) => { @@ -233,13 +254,13 @@ export function SolveMFAChallenges({ case HttpStatusCode.Unauthorized: return i18n.str`Failed to send the verification code.`; case HttpStatusCode.Forbidden: - return i18n.str`Failed to send the verification code.`; + return i18n.str`The request was valid, but the server is refusing action.`; case HttpStatusCode.NotFound: - return i18n.str`Failed to send the verification code.`; + return i18n.str`The backend is not aware of the specified MFA challenge.`; case HttpStatusCode.TooManyRequests: - return i18n.str`Failed to send the verification code.`; + return i18n.str`It is too early to request another transmission of the challenge.`; case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED: - return i18n.str`Failed to send the verification code.`; + return i18n.str`Code transmission failed.`; } }, ), @@ -307,6 +328,10 @@ 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 noNeedToComplete = hasSolvedEnough || solved.indexOf(challenge.challenge_id) !== -1; @@ -361,12 +386,21 @@ export function SolveMFAChallenges({ type="submit" name="send again" class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600" - onClick={doSend} + onClick={alreadySent ? undefined : doSend} > <i18n.Translate>Send me a message</i18n.Translate> </ButtonBetter> </div> </dd> + {alreadySent && time.t_ms !== "never" ? ( + <p class="text-sm text-gray-600"> + <i18n.Translate> + You have to wait until{" "} + <Time format="HH:mm" timestamp={time} /> to send a + new code. + </i18n.Translate> + </p> + ) : undefined} </div> </dl> </div> diff --git a/packages/merchant-backoffice-ui/src/components/SolveMFA.tsx b/packages/merchant-backoffice-ui/src/components/SolveMFA.tsx @@ -307,7 +307,7 @@ export function SolveMFAChallenges({ } case HttpStatusCode.Forbidden: { setNotif({ - message: i18n.str`Failed to send the verification code.`, + message: i18n.str`The request was valid, but the server is refusing action.`, type: "ERROR", description: resp.detail?.hint, }); @@ -315,7 +315,7 @@ export function SolveMFAChallenges({ } case TalerErrorCode.MERCHANT_TAN_CHALLENGE_UNKNOWN: { setNotif({ - message: i18n.str`Failed to send the verification code.`, + message: i18n.str`The backend is not aware of the specified MFA challenge.`, type: "ERROR", description: resp.detail?.hint, }); @@ -323,7 +323,7 @@ export function SolveMFAChallenges({ } case TalerErrorCode.MERCHANT_TAN_MFA_HELPER_EXEC_FAILED: { setNotif({ - message: i18n.str`Failed to send the verification code.`, + message: i18n.str`The backend failed to launch a helper process required for the multi-factor authentication step.`, type: "ERROR", description: resp.detail?.hint, }); @@ -331,7 +331,7 @@ export function SolveMFAChallenges({ } case TalerErrorCode.MERCHANT_TAN_CHALLENGE_SOLVED: { setNotif({ - message: i18n.str`Failed to send the verification code.`, + message: i18n.str`The challenge was already solved.`, type: "ERROR", description: resp.detail?.hint, }); @@ -339,7 +339,7 @@ export function SolveMFAChallenges({ } case TalerErrorCode.MERCHANT_TAN_TOO_EARLY: { setNotif({ - message: i18n.str`Failed to send the verification code.`, + message: i18n.str`It is too early to request another transmission of the challenge.`, type: "ERROR", description: resp.detail?.hint, }); diff --git a/packages/taler-util/src/http-client/bank-core.ts b/packages/taler-util/src/http-client/bank-core.ts @@ -32,6 +32,8 @@ import { codecForTalerCommonConfigResponse, codecForTokenInfoList, codecForTokenSuccessResponse, + codecOptional, + codecOptionalDefault, opKnownAlternativeHttpFailure, opKnownHttpFailure, opKnownTalerFailure, @@ -82,7 +84,9 @@ import { codecForWithdrawalPublicInfo, } from "../types-taler-corebank.js"; import { + ChallengeRequestResponse, ChallengeSolveRequest, + codecForChallengeRequestResponse, codecForChallengeResponse, } from "../types-taler-merchant.js"; import { @@ -1296,8 +1300,10 @@ export class TalerCoreBankHttpClient { method: "POST", }); switch (resp.status) { + case HttpStatusCode.Ok: + return opSuccessFromHttp(resp, codecForChallengeRequestResponse()); case HttpStatusCode.NoContent: - return opEmptySuccess(); + return opFixedSuccess<ChallengeRequestResponse>({}); case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Forbidden: @@ -1347,6 +1353,8 @@ export class TalerCoreBankHttpClient { switch (details.code) { case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: return opKnownTalerFailure(details.code, details); + case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED: + return opKnownTalerFailure(details.code, details); default: return opUnknownHttpFailure(resp, details); } @@ -1354,7 +1362,7 @@ export class TalerCoreBankHttpClient { case HttpStatusCode.NotFound: { const details = await readTalerErrorResponse(resp); switch (details.code) { - case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED: + case TalerErrorCode.BANK_TRANSACTION_NOT_FOUND: return opKnownTalerFailure(details.code, details); case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED: return opKnownTalerFailure(details.code, details); diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -17,6 +17,7 @@ import { AccessToken, CancellationToken, + ChallengeRequestResponse, ChallengeSolveRequest, FailCasesByMethod, HttpStatusCode, @@ -71,6 +72,7 @@ import { codecForWalletTemplateDetails, codecForWebhookDetails, codecForWebhookSummaryResponse, + codecOptionalDefault, opEmptySuccess, opFixedSuccess, opKnownAlternativeHttpFailure, @@ -2529,10 +2531,10 @@ export class TalerMerchantInstanceHttpClient { body: {}, }); switch (resp.status) { - case HttpStatusCode.NoContent: - return opSuccessFromHttp(resp, codecForChallengeRequestResponse()); case HttpStatusCode.Ok: return opSuccessFromHttp(resp, codecForChallengeRequestResponse()); + case HttpStatusCode.NoContent: + return opFixedSuccess<ChallengeRequestResponse>({}); case HttpStatusCode.Unauthorized: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Forbidden: