aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-07-01 15:04:42 -0300
committerSebastian <sebasjm@gmail.com>2024-07-01 15:04:42 -0300
commitda3208f85716af2b150a4bb717092d5fac3bec27 (patch)
tree9daf810d456b1a2e37fea953054f1e59422f0484
parent231e264e94aa559c8922a14cc1d5392c7a165ed0 (diff)
downloadwallet-core-da3208f85716af2b150a4bb717092d5fac3bec27.tar.xz
update cache when sending code and checking code
-rw-r--r--packages/challenger-ui/src/Routing.tsx2
-rw-r--r--packages/challenger-ui/src/app.tsx17
-rw-r--r--packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx23
-rw-r--r--packages/challenger-ui/src/pages/AnswerChallenge.tsx215
-rw-r--r--packages/challenger-ui/src/pages/CallengeCompleted.tsx25
-rw-r--r--packages/challenger-ui/src/pages/Frame.tsx6
-rw-r--r--packages/taler-util/src/http-client/challenger.ts12
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 &quot;{contact?.email}
+ &quot; 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 &quot;
+ <Time format="dd/MM/yyyy HH:mm:ss" timestamp={deadline} />
+ &quot; 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&#39;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&#39;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 &quot;{contact?.email}
- &quot;.
- </i18n.Translate>
- ) : (
- <Attention title={i18n.str`Unable send the code again`}>
- <i18n.Translate>
- We recently already sent a TAN to your address &quot;
- {contact?.email}&quot;. A new TAN will not be transmitted
- again before &quot;
- <Time
- format="dd/MM/yyyy HH:mm:ss"
- timestamp={AbsoluteTime.fromProtocolTimestamp(
- lastStatus.retransmission_time,
- )}
- />
- &quot;.
- </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&#39;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&#39;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">&quot;
+ {state?.completedURL}
+ &quot;</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: