aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/challenger-ui/src/Routing.tsx1
-rw-r--r--packages/challenger-ui/src/app.tsx25
-rw-r--r--packages/challenger-ui/src/hooks/challenge.ts2
-rw-r--r--packages/challenger-ui/src/pages/AnswerChallenge.tsx44
-rw-r--r--packages/challenger-ui/src/pages/AskChallenge.tsx135
-rw-r--r--packages/challenger-ui/src/pages/Setup.tsx2
-rw-r--r--packages/taler-util/src/http-client/challenger.ts18
-rw-r--r--packages/web-util/src/context/activity.ts2
-rw-r--r--packages/web-util/src/context/challenger-api.ts14
9 files changed, 157 insertions, 86 deletions
diff --git a/packages/challenger-ui/src/Routing.tsx b/packages/challenger-ui/src/Routing.tsx
index f1f4d82d2..6711e4cae 100644
--- a/packages/challenger-ui/src/Routing.tsx
+++ b/packages/challenger-ui/src/Routing.tsx
@@ -234,6 +234,7 @@ function PublicRounting(): VNode {
>
<AnswerChallenge
nonce={location.values.nonce}
+ routeAsk={publicPages.ask}
onComplete={() => {
navigateTo(
publicPages.completed.url({
diff --git a/packages/challenger-ui/src/app.tsx b/packages/challenger-ui/src/app.tsx
index d85893c07..2b5c5c815 100644
--- a/packages/challenger-ui/src/app.tsx
+++ b/packages/challenger-ui/src/app.tsx
@@ -15,6 +15,9 @@
*/
import {
+ CacheEvictor,
+ ChallengerCacheEviction,
+ assertUnreachable,
canonicalizeBaseUrl,
getGlobalLogLevel,
setGlobalLogLevelFromString,
@@ -33,13 +36,28 @@ import { Routing } from "./Routing.js";
// import { BrowserHashNavigationProvider } from "./context/navigation.js";
import { SettingsProvider } from "./context/settings.js";
// import { TalerWalletIntegrationBrowserProvider } from "./context/wallet-integration.js";
-import { h } from "preact";
+import { VNode, h } from "preact";
import { strings } from "./i18n/strings.js";
import { ChallengerUiSettings, fetchSettings } from "./settings.js";
import { Frame } from "./pages/Frame.js";
+import { revalidateChallengeSession } from "./hooks/challenge.js";
const WITH_LOCAL_STORAGE_CACHE = false;
-export function App() {
+const evictBankSwrCache: CacheEvictor<ChallengerCacheEviction> = {
+ async notifySuccess(op) {
+ switch (op) {
+ case ChallengerCacheEviction.CREATE_CHALLENGE: {
+ await Promise.all([revalidateChallengeSession()]);
+ return;
+ }
+ default: {
+ assertUnreachable(op);
+ }
+ }
+ },
+};
+
+export function App(): VNode {
const [settings, setSettings] = useState<ChallengerUiSettings>();
useEffect(() => {
fetchSettings(setSettings);
@@ -60,6 +78,9 @@ export function App() {
<ChallengerApiProvider
baseUrl={new URL("/", baseUrl)}
frameOnError={Frame}
+ evictors={{
+ challenger: evictBankSwrCache,
+ }}
>
<SWRConfig
value={{
diff --git a/packages/challenger-ui/src/hooks/challenge.ts b/packages/challenger-ui/src/hooks/challenge.ts
index 3df10e21e..846242816 100644
--- a/packages/challenger-ui/src/hooks/challenge.ts
+++ b/packages/challenger-ui/src/hooks/challenge.ts
@@ -35,7 +35,7 @@ export function useChallengeSession(
session: SessionId | undefined,
) {
const {
- lib: { bank: api },
+ lib: { challenger: api },
} = useChallengerApiContext();
async function fetcher([n, c, r, s]: [string, string, string, string]) {
diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx
index 62b7e775d..5e7973b3d 100644
--- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx
@@ -22,6 +22,7 @@ import {
Attention,
Button,
LocalNotificationBanner,
+ RouteDefinition,
ShowInputErrorLabel,
useChallengerApiContext,
useLocalNotificationHandler,
@@ -36,9 +37,10 @@ export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
type Props = {
nonce: string;
onComplete: () => void;
+ routeAsk: RouteDefinition<{ nonce: string }>;
};
-export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
+export function AnswerChallenge({ nonce, onComplete, routeAsk }: Props): VNode {
const { lib } = useChallengerApiContext();
const { i18n } = useTranslationContext();
const { state, accepted, completed } = useSessionState();
@@ -54,7 +56,9 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
? undefined
: !state.lastStatus
? undefined
- : ((state.lastStatus.last_address as any)["email"] as string);
+ : !state.lastStatus.last_address
+ ? undefined
+ : state.lastStatus.last_address["email"];
const onSendAgain =
!state || lastEmail === undefined
@@ -62,7 +66,7 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
: withErrorHandler(
async () => {
if (!lastEmail) return;
- return await lib.bank.challenge(nonce, { email: lastEmail });
+ return await lib.challenger.challenge(nonce, { email: lastEmail });
},
(ok) => {
if ("redirectURL" in ok.body) {
@@ -93,11 +97,11 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
);
const onCheck =
- lastTryError && lastTryError.exhausted
+ errors !== undefined || (lastTryError && lastTryError.exhausted)
? undefined
: withErrorHandler(
async () => {
- return lib.bank.solve(nonce, { pin: pin! });
+ return lib.challenger.solve(nonce, { pin: pin! });
},
(ok) => {
completed(ok.body.redirectURL as URL);
@@ -141,7 +145,7 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
<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>
- Please enter the TAN you received to authenticate.
+ Enter the TAN you received to authenticate.
</i18n.Translate>
</h2>
<p class="mt-2 text-lg leading-8 text-gray-600">
@@ -220,15 +224,25 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
<i18n.Translate>Check</i18n.Translate>
</Button>
</div>
- <div class="mt-10">
- <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 again</i18n.Translate>
- </Button>
+ <div class="mt-10 flex justify-between">
+ <div>
+ <a
+ href={routeAsk.url({ nonce })}
+ class="relative disabled:bg-gray-100 disabled:text-gray-500 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>
+ </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>
+ </div>
</div>
</form>
</div>
diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx
index 76fe6f00a..aaca51db7 100644
--- a/packages/challenger-ui/src/pages/AskChallenge.tsx
+++ b/packages/challenger-ui/src/pages/AskChallenge.tsx
@@ -49,27 +49,26 @@ export function AskChallenge({
const prevEmail =
!status || !status.last_address ? undefined : status.last_address["email"];
const regexEmail =
- !status || !status.restrictions
- ? undefined
- : status.restrictions["email"];
+ !status || !status.restrictions ? undefined : status.restrictions["email"];
const { lib } = useChallengerApiContext();
const { i18n } = useTranslationContext();
const [notification, withErrorHandler] = useLocalNotificationHandler();
- const [email, setEmail] = useState<string | undefined>(prevEmail);
+ const [email, setEmail] = useState<string | undefined>();
const [repeat, setRepeat] = useState<string | undefined>();
+ const regexTest =
+ regexEmail && regexEmail.regex ? new RegExp(regexEmail.regex) : EMAIL_REGEX;
+ const regexHint =
+ regexEmail && regexEmail.hint ? regexEmail.hint : i18n.str`invalid email`;
+
const errors = undefinedIfEmpty({
email: !email
? i18n.str`required`
- : regexEmail && regexEmail.regex
- ? !new RegExp(regexEmail.regex).test(email)
- ? regexEmail.hint
- ? regexEmail.hint
- : `invalid`
- : undefined
- : !EMAIL_REGEX.test(email)
- ? i18n.str`invalid email`
+ : !regexTest.test(email)
+ ? regexHint
+ : prevEmail !== undefined && email === prevEmail
+ ? i18n.str`email should be different`
: undefined,
repeat: !repeat
? i18n.str`required`
@@ -82,7 +81,7 @@ export function AskChallenge({
? undefined
: withErrorHandler(
async () => {
- return lib.bank.challenge(nonce, { email: email! });
+ return lib.challenger.challenge(nonce, { email: email! });
},
(ok) => {
if ("redirectURL" in ok.body) {
@@ -136,11 +135,9 @@ export function AskChallenge({
<Fragment>
<Attention title={i18n.str`A code has been sent to ${prevEmail}`}>
<i18n.Translate>
- You can change the destination or{" "}
- <a href={routeSolveChallenge.url({ nonce })}>
- <i18n.Translate>complete the challenge here</i18n.Translate>
+ <a href={routeSolveChallenge.url({ nonce })} class="underline">
+ <i18n.Translate>Complete the challenge here.</i18n.Translate>
</a>
- .
</i18n.Translate>
</Attention>
</Fragment>
@@ -171,8 +168,9 @@ export function AskChallenge({
onChange={(e) => {
setEmail(e.currentTarget.value);
}}
+ placeholder={prevEmail}
readOnly={status.fix_address}
- class="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ class="block w-full read-only:bg-slate-200 rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
/>
<ShowInputErrorLabel
message={errors?.email}
@@ -181,50 +179,71 @@ export function AskChallenge({
</div>
</div>
- <div class="sm:col-span-2">
- <label
- for="repeat-email"
- class="block text-sm font-semibold leading-6 text-gray-900"
- >
- <i18n.Translate>Repeat email</i18n.Translate>
- </label>
- <div class="mt-2.5">
- <input
- type="email"
- name="repeat-email"
- id="repeat-email"
- value={repeat}
- onChange={(e) => {
- setRepeat(e.currentTarget.value);
- }}
- autocomplete="email"
- class="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
- />
- <ShowInputErrorLabel
- message={errors?.repeat}
- isDirty={repeat !== undefined}
- />
+ {status.fix_address ? undefined : (
+ <div class="sm:col-span-2">
+ <label
+ for="repeat-email"
+ class="block text-sm font-semibold leading-6 text-gray-900"
+ >
+ <i18n.Translate>Repeat email</i18n.Translate>
+ </label>
+ <div class="mt-2.5">
+ <input
+ type="email"
+ name="repeat-email"
+ id="repeat-email"
+ value={repeat}
+ onChange={(e) => {
+ setRepeat(e.currentTarget.value);
+ }}
+ autocomplete="email"
+ class="block w-full rounded-md border-0 px-3.5 py-2 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
+ />
+ <ShowInputErrorLabel
+ message={errors?.repeat}
+ isDirty={repeat !== undefined}
+ />
+ </div>
</div>
- </div>
+ )}
- <p class="mt-3 text-sm leading-6 text-gray-400">
- <i18n.Translate>
- You can change your email address another {status.changes_left}{" "}
- times.
- </i18n.Translate>
- </p>
+ {!status.changes_left ? (
+ <p class="mt-3 text-sm leading-6 text-gray-400">
+ <i18n.Translate>No more changes left</i18n.Translate>
+ </p>
+ ) : (
+ <p class="mt-3 text-sm leading-6 text-gray-400">
+ <i18n.Translate>
+ You can change your email address another{" "}
+ {status.changes_left} times.
+ </i18n.Translate>
+ </p>
+ )}
</div>
- <div class="mt-10">
- <Button
- type="submit"
- disabled={!onSend}
- 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={onSend}
- >
- <i18n.Translate>Send email</i18n.Translate>
- </Button>
- </div>
+ {!prevEmail ? (
+ <div class="mt-10">
+ <Button
+ type="submit"
+ disabled={!onSend}
+ 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={onSend}
+ >
+ <i18n.Translate>Send email</i18n.Translate>
+ </Button>
+ </div>
+ ) : (
+ <div class="mt-10">
+ <Button
+ type="submit"
+ disabled={!onSend}
+ 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={onSend}
+ >
+ <i18n.Translate>Change email</i18n.Translate>
+ </Button>
+ </div>
+ )}
</form>
</div>
</Fragment>
diff --git a/packages/challenger-ui/src/pages/Setup.tsx b/packages/challenger-ui/src/pages/Setup.tsx
index 400c9b780..f431835aa 100644
--- a/packages/challenger-ui/src/pages/Setup.tsx
+++ b/packages/challenger-ui/src/pages/Setup.tsx
@@ -36,7 +36,7 @@ export function Setup({ clientId, onCreated }: Props): VNode {
const onStart = withErrorHandler(
async () => {
- return lib.bank.setup(clientId, "secret-token:chal-secret" as AccessToken);
+ return lib.challenger.setup(clientId, "secret-token:chal-secret" as AccessToken);
},
(ok) => {
start({
diff --git a/packages/taler-util/src/http-client/challenger.ts b/packages/taler-util/src/http-client/challenger.ts
index 8d23ed273..aa530570d 100644
--- a/packages/taler-util/src/http-client/challenger.ts
+++ b/packages/taler-util/src/http-client/challenger.ts
@@ -1,6 +1,7 @@
import { HttpRequestLibrary, readTalerErrorResponse } from "../http-common.js";
import { HttpStatusCode } from "../http-status-codes.js";
import { createPlatformHttpLib } from "../http.js";
+import { TalerCoreBankCacheEviction } from "../index.node.js";
import { LibtoolVersion } from "../libtool-version.js";
import {
FailCasesByMethod,
@@ -22,24 +23,31 @@ import {
codecForChallengerTermsOfServiceResponse,
codecForInvalidPinResponse,
} from "./types.js";
-import { makeBearerTokenAuthHeader } from "./utils.js";
+import { CacheEvictor, makeBearerTokenAuthHeader, nullEvictor } from "./utils.js";
export type ChallengerResultByMethod<prop extends keyof ChallengerHttpClient> =
ResultByMethod<ChallengerHttpClient, prop>;
export type ChallengerErrorsByMethod<prop extends keyof ChallengerHttpClient> =
FailCasesByMethod<ChallengerHttpClient, prop>;
+export enum ChallengerCacheEviction {
+ CREATE_CHALLENGE,
+}
+
/**
*/
export class ChallengerHttpClient {
httpLib: HttpRequestLibrary;
+ cacheEvictor: CacheEvictor<ChallengerCacheEviction>;
public readonly PROTOCOL_VERSION = "1:0:0";
constructor(
readonly baseUrl: string,
httpClient?: HttpRequestLibrary,
- ) {
+ cacheEvictor?: CacheEvictor<ChallengerCacheEviction>,
+ ) {
this.httpLib = httpClient ?? createPlatformHttpLib();
+ this.cacheEvictor = cacheEvictor ?? nullEvictor;
}
isCompatible(version: string): boolean {
@@ -146,8 +154,12 @@ export class ChallengerHttpClient {
redirect: "manual",
});
switch (resp.status) {
- case HttpStatusCode.Ok:
+ case HttpStatusCode.Ok: {
+ await this.cacheEvictor.notifySuccess(
+ ChallengerCacheEviction.CREATE_CHALLENGE,
+ );
return opSuccessFromHttp(resp, codecForChallengeCreateResponse());
+ }
case HttpStatusCode.Found:
const redirect = resp.headers.get("Location")!;
return opFixedSuccess<RedirectResult>({
diff --git a/packages/web-util/src/context/activity.ts b/packages/web-util/src/context/activity.ts
index 3cfd83bda..fd366cbe5 100644
--- a/packages/web-util/src/context/activity.ts
+++ b/packages/web-util/src/context/activity.ts
@@ -67,6 +67,6 @@ export interface BankLib {
}
export interface ChallengerLib {
- bank: ChallengerHttpClient;
+ challenger: ChallengerHttpClient;
}
diff --git a/packages/web-util/src/context/challenger-api.ts b/packages/web-util/src/context/challenger-api.ts
index 960f1db0d..8748f5f69 100644
--- a/packages/web-util/src/context/challenger-api.ts
+++ b/packages/web-util/src/context/challenger-api.ts
@@ -15,7 +15,9 @@
*/
import {
+ CacheEvictor,
ChallengerApi,
+ ChallengerCacheEviction,
ChallengerHttpClient,
LibtoolVersion,
ObservabilityEvent,
@@ -63,7 +65,9 @@ enum VersionHint {
NONE,
}
-type Evictors = Record<string, never>;
+type Evictors = {
+ challenger?: CacheEvictor<ChallengerCacheEviction>;
+}
type ConfigResult<T> =
| undefined
@@ -174,10 +178,10 @@ function buildChallengerApiClient(
},
});
- const bank = new ChallengerHttpClient(url.href, httpLib);
+ const challenger = new ChallengerHttpClient(url.href, httpLib, evictors.challenger);
async function getRemoteConfig(): Promise<ChallengerApi.ChallengerTermsOfServiceResponse> {
- const resp = await bank.getConfig();
+ const resp = await challenger.getConfig();
if (resp.type === "fail") {
throw TalerError.fromUncheckedDetail(resp.detail);
}
@@ -186,9 +190,9 @@ function buildChallengerApiClient(
return {
getRemoteConfig,
- VERSION: bank.PROTOCOL_VERSION,
+ VERSION: challenger.PROTOCOL_VERSION,
lib: {
- bank,
+ challenger,
},
onActivity: tracker.subscribe,
cancelRequest: httpLib.cancelRequest,