diff options
author | Sebastian <sebasjm@gmail.com> | 2024-07-01 15:04:42 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2024-07-01 15:04:42 -0300 |
commit | da3208f85716af2b150a4bb717092d5fac3bec27 (patch) | |
tree | 9daf810d456b1a2e37fea953054f1e59422f0484 | |
parent | 231e264e94aa559c8922a14cc1d5392c7a165ed0 (diff) | |
download | wallet-core-da3208f85716af2b150a4bb717092d5fac3bec27.tar.xz |
update cache when sending code and checking code
-rw-r--r-- | packages/challenger-ui/src/Routing.tsx | 2 | ||||
-rw-r--r-- | packages/challenger-ui/src/app.tsx | 17 | ||||
-rw-r--r-- | packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx | 23 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/AnswerChallenge.tsx | 215 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/CallengeCompleted.tsx | 25 | ||||
-rw-r--r-- | packages/challenger-ui/src/pages/Frame.tsx | 6 | ||||
-rw-r--r-- | packages/taler-util/src/http-client/challenger.ts | 12 |
7 files changed, 185 insertions, 115 deletions
diff --git a/packages/challenger-ui/src/Routing.tsx b/packages/challenger-ui/src/Routing.tsx index 179263286..74c1687bb 100644 --- a/packages/challenger-ui/src/Routing.tsx +++ b/packages/challenger-ui/src/Routing.tsx @@ -98,7 +98,7 @@ function PublicRounting(): VNode { <Setup clientId={location.values.client} secret={secret} - // "http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet" + // redirect_url=http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet&secret=chal-secret redirectURL={redirectURL} onCreated={() => { navigateTo(publicPages.ask.url({})); diff --git a/packages/challenger-ui/src/app.tsx b/packages/challenger-ui/src/app.tsx index 07b0fe261..655e46a5c 100644 --- a/packages/challenger-ui/src/app.tsx +++ b/packages/challenger-ui/src/app.tsx @@ -29,18 +29,15 @@ import { TalerWalletIntegrationBrowserProvider, TranslationProvider, } from "@gnu-taler/web-util/browser"; +import { VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; import { SWRConfig } from "swr"; import { Routing } from "./Routing.js"; -// import { BankCoreApiProvider } from "./context/config.js"; -// import { BrowserHashNavigationProvider } from "./context/navigation.js"; import { SettingsProvider } from "./context/settings.js"; -// import { TalerWalletIntegrationBrowserProvider } from "./context/wallet-integration.js"; -import { VNode, h } from "preact"; +import { revalidateChallengeSession } from "./hooks/challenge.js"; import { strings } from "./i18n/strings.js"; -import { ChallengerUiSettings, fetchSettings } from "./settings.js"; import { Frame } from "./pages/Frame.js"; -import { revalidateChallengeSession } from "./hooks/challenge.js"; +import { ChallengerUiSettings, fetchSettings } from "./settings.js"; const WITH_LOCAL_STORAGE_CACHE = false; @@ -83,11 +80,9 @@ export function App(): VNode { <ChallengerApiProvider baseUrl={new URL("/", baseUrl)} frameOnError={Frame} - evictors={ - { - // challenger: evictBankSwrCache, - } - } + evictors={{ + challenger: evictBankSwrCache, + }} > <SWRConfig value={{ diff --git a/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx index 1ff7197bf..8ceb969b5 100644 --- a/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx +++ b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx @@ -44,7 +44,9 @@ export function CheckChallengeIsUpToDate({ const { state } = useSessionState(); const { i18n } = useTranslationContext(); - const result = useChallengeSession(session ?? state); + const id = session ?? state; + + const result = useChallengeSession(id); if (!result) { return <Loading />; @@ -87,6 +89,25 @@ export function CheckChallengeIsUpToDate({ </Attention> ); } + case HttpStatusCode.TooManyRequests: { + return ( + <Fragment> + <Attention + type="danger" + title={i18n.str`Can't complete this challenge`} + > + <i18n.Translate> + There have been too many attempts to request challenge + transmissions and check the TAN code. + </i18n.Translate> + </Attention> + + <div class="mt-2"> + <a href={id?.redirectURL ?? ""}>{id?.redirectURL}</a> + </div> + </Fragment> + ); + } default: assertUnreachable(result); } diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx index 1576f2cf2..265bda038 100644 --- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx +++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx @@ -15,11 +15,9 @@ */ import { AbsoluteTime, - ChallengerApi, EmptyObject, HttpStatusCode, TalerError, - TalerProtocolTimestamp, assertUnreachable, } from "@gnu-taler/taler-util"; import { @@ -35,8 +33,11 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useEffect, useState } from "preact/hooks"; +import { + revalidateChallengeSession, + useChallengeSession, +} from "../hooks/challenge.js"; import { useSessionState } from "../hooks/session.js"; -import { useChallengeSession } from "../hooks/challenge.js"; export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; @@ -48,19 +49,20 @@ type Props = { function useReloadOnDeadline(deadline: AbsoluteTime): void { const [, set] = useState(false); + function toggle(): void { + set((s) => !s); + } useEffect(() => { if (AbsoluteTime.isExpired(deadline)) { return; } const diff = AbsoluteTime.difference(AbsoluteTime.now(), deadline); if (diff.d_ms === "forever") return; - const p = setTimeout(() => { - set(true); - }, diff.d_ms); + const timer = setTimeout(toggle, diff.d_ms); return () => { - clearTimeout(p); + clearTimeout(timer); }; - }, []); + }, [deadline]); } export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { @@ -98,7 +100,7 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { !state?.nonce || contact === undefined || lastStatus == undefined || - lastStatus.auth_attempts_left === 0 || + lastStatus.pin_transmissions_left === 0 || !deadline || !AbsoluteTime.isExpired(deadline) ? undefined @@ -112,7 +114,6 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { } else { sent(ok.body); } - return undefined; }, (fail) => { switch (fail.case) { @@ -153,14 +154,17 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { case HttpStatusCode.BadRequest: return i18n.str`The request was not accepted, try reloading the app.`; case HttpStatusCode.Forbidden: { + revalidateChallengeSession(); return i18n.str`Invalid pin.`; } case HttpStatusCode.NotFound: return i18n.str`Challenge not found.`; case HttpStatusCode.NotAcceptable: return i18n.str`Server templates are missing due to misconfiguration.`; - case HttpStatusCode.TooManyRequests: + case HttpStatusCode.TooManyRequests: { + revalidateChallengeSession(); return i18n.str`There have been too many attempts to request challenge transmissions.`; + } case HttpStatusCode.InternalServerError: return i18n.str`Server is not able to respond due to internal problems.`; default: @@ -168,6 +172,110 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { } }, ); + const cantTryAnymore = lastStatus?.auth_attempts_left === 0; + + function LastContactSent(): VNode { + return ( + <p class="mt-2 text-lg leading-8 text-gray-600"> + {!lastStatus || !deadline || AbsoluteTime.isExpired(deadline) ? ( + <i18n.Translate> + Last TAN code was sent to your address "{contact?.email} + " is not valid anymore. + </i18n.Translate> + ) : ( + <Attention + title={i18n.str`A TAN code was sent to your address "${contact?.email}"`} + > + <i18n.Translate> + You should wait until " + <Time format="dd/MM/yyyy HH:mm:ss" timestamp={deadline} /> + " to send a new one. + </i18n.Translate> + </Attention> + )} + </p> + ); + } + + function TryAnotherCode(): VNode { + return ( + <div class="mx-auto mt-4 max-w-xl flex justify-between"> + <div> + <a + data-disabled={unableToChangeAddr} + href={unableToChangeAddr ? undefined : routeAsk.url({})} + class="relative data-[disabled=true]:bg-gray-300 data-[disabled=true]:text-white data-[disabled=true]:cursor-default inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0" + > + <i18n.Translate>Try with another address</i18n.Translate> + </a> + {lastStatus === undefined ? undefined : ( + <p class="mt-2 text-sm leading-6 text-gray-400"> + {lastStatus.changes_left < 1 ? ( + <i18n.Translate> + You can't change the email anymore. + </i18n.Translate> + ) : lastStatus.changes_left === 1 ? ( + <i18n.Translate> + You can change the email one last time. + </i18n.Translate> + ) : ( + <i18n.Translate> + You can change the email {lastStatus.changes_left} more times. + </i18n.Translate> + )} + </p> + )} + </div> + <div> + <Button + type="submit" + disabled={!onSendAgain} + class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center 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" + handler={onSendAgain} + > + <i18n.Translate>Send new code</i18n.Translate> + </Button> + {lastStatus === undefined ? undefined : ( + <p class="mt-2 text-sm leading-6 text-gray-400"> + {lastStatus.pin_transmissions_left < 1 ? ( + <i18n.Translate> + We can't send you the code anymore. + </i18n.Translate> + ) : lastStatus.pin_transmissions_left === 1 ? ( + <i18n.Translate> + We can send the code one last time. + </i18n.Translate> + ) : ( + <i18n.Translate> + We can send the code {lastStatus.pin_transmissions_left} more + times. + </i18n.Translate> + )} + </p> + )} + </div> + </div> + ); + } + + if (cantTryAnymore) { + return ( + <Fragment> + <LocalNotificationBanner notification={notification} /> + <div class="isolate bg-white px-6 py-12"> + <div class="mx-auto max-w-2xl text-center"> + <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl"> + <i18n.Translate>Last TAN code can not be used.</i18n.Translate> + </h2> + + <LastContactSent /> + </div> + + <TryAnotherCode /> + </div> + </Fragment> + ); + } return ( <Fragment> @@ -180,29 +288,8 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { Enter the TAN you received to authenticate. </i18n.Translate> </h2> - <p class="mt-2 text-lg leading-8 text-gray-600"> - {!lastStatus || !deadline || AbsoluteTime.isExpired(deadline) ? ( - <i18n.Translate> - Last TAN code was sent to your address "{contact?.email} - ". - </i18n.Translate> - ) : ( - <Attention title={i18n.str`Unable send the code again`}> - <i18n.Translate> - We recently already sent a TAN to your address " - {contact?.email}". A new TAN will not be transmitted - again before " - <Time - format="dd/MM/yyyy HH:mm:ss" - timestamp={AbsoluteTime.fromProtocolTimestamp( - lastStatus.retransmission_time, - )} - /> - ". - </i18n.Translate> - </Attention> - )} - </p> + <LastContactSent /> + {lastStatus === undefined ? undefined : ( <p class="mt-2 text-lg leading-8 text-gray-600"> {lastStatus.auth_attempts_left < 1 ? ( @@ -222,6 +309,7 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { </p> )} </div> + <form method="POST" class="mx-auto mt-4 max-w-xl" @@ -270,64 +358,9 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { <i18n.Translate>Check</i18n.Translate> </Button> </div> - <div class="mt-10 flex justify-between"> - <div> - <a - data-disabled={unableToChangeAddr} - href={unableToChangeAddr ? undefined : routeAsk.url({})} - class="relative data-[disabled=true]:bg-gray-300 data-[disabled=true]:text-white data-[disabled=true]:cursor-default inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0" - > - <i18n.Translate>Change email</i18n.Translate> - </a> - {lastStatus === undefined ? undefined : ( - <p class="mt-2 text-sm leading-6 text-gray-400"> - {lastStatus.changes_left < 1 ? ( - <i18n.Translate> - You can't change the email anymore. - </i18n.Translate> - ) : lastStatus.changes_left === 1 ? ( - <i18n.Translate> - You can change the email one last time. - </i18n.Translate> - ) : ( - <i18n.Translate> - You can change the email {lastStatus.changes_left} more - times. - </i18n.Translate> - )} - </p> - )} - </div> - <div> - <Button - type="submit" - disabled={!onSendAgain} - class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center 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" - handler={onSendAgain} - > - <i18n.Translate>Send code again</i18n.Translate> - </Button> - {lastStatus === undefined ? undefined : ( - <p class="mt-2 text-sm leading-6 text-gray-400"> - {lastStatus.pin_transmissions_left < 1 ? ( - <i18n.Translate> - We can't send you the code anymore. - </i18n.Translate> - ) : lastStatus.pin_transmissions_left === 1 ? ( - <i18n.Translate> - We can send the code one last time. - </i18n.Translate> - ) : ( - <i18n.Translate> - We can send the code {lastStatus.pin_transmissions_left}{" "} - more times. - </i18n.Translate> - )} - </p> - )} - </div> - </div> </form> + + <TryAnotherCode /> </div> </Fragment> ); diff --git a/packages/challenger-ui/src/pages/CallengeCompleted.tsx b/packages/challenger-ui/src/pages/CallengeCompleted.tsx index e897bae5b..67b26b452 100644 --- a/packages/challenger-ui/src/pages/CallengeCompleted.tsx +++ b/packages/challenger-ui/src/pages/CallengeCompleted.tsx @@ -13,13 +13,13 @@ 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 { VNode, h } from "preact"; +import { TalerError } from "@gnu-taler/taler-util"; +import { Attention, useTranslationContext } from "@gnu-taler/web-util/browser"; +import { Fragment, VNode, h } from "preact"; import { useChallengeSession } from "../hooks/challenge.js"; import { useSessionState } from "../hooks/session.js"; -import { TalerError } from "@gnu-taler/taler-util"; -type Props = {}; -export function CallengeCompleted({}: Props): VNode { +export function CallengeCompleted(): VNode { const { state } = useSessionState(); const result = useChallengeSession(state); @@ -28,5 +28,20 @@ export function CallengeCompleted({}: Props): VNode { ? result.body : undefined; - return <div>completed {lastStatus}</div>; + const { i18n } = useTranslationContext(); + + return ( + <div class="m-4"> + <Attention + title={i18n.str`Challenge completed`} + type="success" + > + <i18n.Translate> + You will be redirected to <a href={state?.completedURL} class="break-all">" + {state?.completedURL} + "</a> + </i18n.Translate> + </Attention> + </div> + ); } diff --git a/packages/challenger-ui/src/pages/Frame.tsx b/packages/challenger-ui/src/pages/Frame.tsx index dd2a13d8c..7f81b9d77 100644 --- a/packages/challenger-ui/src/pages/Frame.tsx +++ b/packages/challenger-ui/src/pages/Frame.tsx @@ -14,6 +14,7 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { TranslatedString } from "@gnu-taler/taler-util"; import { Footer, Header, @@ -22,15 +23,14 @@ import { notifyException, useTranslationContext, } from "@gnu-taler/web-util/browser"; -import { ComponentChildren, Fragment, h, VNode } from "preact"; -import { useSettingsContext } from "../context/settings.js"; +import { ComponentChildren, Fragment, VNode, h } from "preact"; import { useEffect, useErrorBoundary } from "preact/hooks"; -import { TranslatedString } from "@gnu-taler/taler-util"; import { getAllBooleanPreferences, getLabelForPreferences, usePreferences, } from "../context/preferences.js"; +import { useSettingsContext } from "../context/settings.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts index 6a920749c..951bad845 100644 --- a/packages/taler-util/src/http-client/challenger.ts +++ b/packages/taler-util/src/http-client/challenger.ts @@ -8,7 +8,7 @@ import { opKnownAlternativeFailure, opKnownHttpFailure, opSuccessFromHttp, - opUnknownFailure + opUnknownFailure, } from "../operation.js"; import { AccessToken, @@ -19,7 +19,7 @@ import { codecForChallengeStatus, codecForChallengerAuthResponse, codecForChallengerInfoResponse, - codecForChallengerTermsOfServiceResponse + codecForChallengerTermsOfServiceResponse, } from "./types.js"; import { CacheEvictor, @@ -131,6 +131,8 @@ export class ChallengerHttpClient { return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotAcceptable: return opKnownHttpFailure(resp.status, resp); + case HttpStatusCode.TooManyRequests: + return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.InternalServerError: return opKnownHttpFailure(resp.status, resp); default: @@ -203,7 +205,11 @@ export class ChallengerHttpClient { case HttpStatusCode.BadRequest: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.Forbidden: - return opKnownAlternativeFailure(resp, HttpStatusCode.Forbidden, codecForChallengeInvalidPinResponse()); + return opKnownAlternativeFailure( + resp, + HttpStatusCode.Forbidden, + codecForChallengeInvalidPinResponse(), + ); case HttpStatusCode.NotFound: return opKnownHttpFailure(resp.status, resp); case HttpStatusCode.NotAcceptable: |