From 5db4cb99e3f16c8471117ca7443bc323180e67ec Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 22 Apr 2024 12:18:41 -0300 Subject: fix #8393 --- packages/challenger-ui/src/Routing.tsx | 39 ++++++++- .../src/components/CheckChallengeIsUpToDate.tsx | 11 ++- packages/challenger-ui/src/hooks/session.ts | 28 +++---- .../challenger-ui/src/pages/AnswerChallenge.tsx | 24 ++++-- packages/challenger-ui/src/pages/AskChallenge.tsx | 98 +++++++++++----------- .../challenger-ui/src/pages/CallengeCompleted.tsx | 3 +- packages/taler-util/src/http-client/types.ts | 19 +++-- 7 files changed, 140 insertions(+), 82 deletions(-) diff --git a/packages/challenger-ui/src/Routing.tsx b/packages/challenger-ui/src/Routing.tsx index eae182be5..f1f4d82d2 100644 --- a/packages/challenger-ui/src/Routing.tsx +++ b/packages/challenger-ui/src/Routing.tsx @@ -44,6 +44,10 @@ export function Routing(): VNode { } const publicPages = { + noinfo: urlPattern<{ nonce: string }>( + /\/noinfo\/(?[a-zA-Z0-9]+)/, + ({ nonce }) => `#/noinfo/${nonce}`, + ), authorize: urlPattern<{ nonce: string }>( /\/authorize\/(?[a-zA-Z0-9]+)/, ({ nonce }) => `#/authorize/${nonce}`, @@ -93,6 +97,9 @@ function PublicRounting(): VNode { } switch (location.name) { + case "noinfo": { + return
no info
; + } case "setup": { return ( { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} onCompleted={() => { start(sessionId); navigateTo( @@ -170,6 +184,13 @@ function PublicRounting(): VNode { return ( { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} onCompleted={() => { navigateTo( publicPages.completed.url({ @@ -196,6 +217,13 @@ function PublicRounting(): VNode { return ( { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} onCompleted={() => { navigateTo( publicPages.completed.url({ @@ -219,7 +247,16 @@ function PublicRounting(): VNode { } case "completed": { return ( - + { + navigateTo( + publicPages.noinfo.url({ + nonce: location.values.nonce, + }), + ); + }} + > ); diff --git a/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx index 04556696b..70e41bf1e 100644 --- a/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx +++ b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx @@ -16,7 +16,7 @@ import { HttpStatusCode, TalerError, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { Attention, @@ -34,6 +34,7 @@ interface Props { onCompleted?: () => void; onChangeLeft?: () => void; onNoMoreChanges?: () => void; + onNoInfo: () => void; } export function CheckChallengeIsUpToDate({ sessionId: sessionFromParam, @@ -42,6 +43,7 @@ export function CheckChallengeIsUpToDate({ onCompleted, onChangeLeft, onNoMoreChanges, + onNoInfo, }: Props): VNode { const { state, updateStatus } = useSessionState(); const { i18n } = useTranslationContext(); @@ -57,12 +59,17 @@ export function CheckChallengeIsUpToDate({ }; const result = useChallengeSession(nonce, sessionId); + console.log("asd"); + if (!sessionId) { + onNoInfo(); + return ; + } if (!result) { return ; } if (result instanceof TalerError) { - return
; + return
{JSON.stringify(result, undefined, 2)}
; } if (result.type === "fail") { diff --git a/packages/challenger-ui/src/hooks/session.ts b/packages/challenger-ui/src/hooks/session.ts index 4d0ffeccf..ed7ea8986 100644 --- a/packages/challenger-ui/src/hooks/session.ts +++ b/packages/challenger-ui/src/hooks/session.ts @@ -45,9 +45,8 @@ export type LastChallengeResponse = { }; export type SessionState = SessionId & { - email: string | undefined; lastTry: LastChallengeResponse | undefined; - challengeStatus: ChallengerApi.ChallengeStatus | undefined; + lastStatus: ChallengerApi.ChallengeStatus | undefined; completedURL: string | undefined; }; export const codecForLastChallengeResponse = (): Codec => @@ -63,15 +62,14 @@ export const codecForSessionState = (): Codec => .property("redirectURL", codecForStringURL()) .property("completedURL", codecOptional(codecForStringURL())) .property("state", codecForString()) - .property("challengeStatus", codecOptional(codecForChallengeStatus())) + .property("lastStatus", codecOptional(codecForChallengeStatus())) .property("lastTry", codecOptional(codecForLastChallengeResponse())) - .property("email", codecOptional(codecForString())) .build("SessionState"); export interface SessionStateHandler { state: SessionState | undefined; start(s: SessionId): void; - accepted(e: string, l: LastChallengeResponse): void; + accepted(l: LastChallengeResponse): void; completed(e: URL): void; updateStatus(s: ChallengerApi.ChallengeStatus): void; } @@ -96,16 +94,14 @@ export function useSessionState(): SessionStateHandler { ...info, lastTry: undefined, completedURL: undefined, - challengeStatus: undefined, - email: undefined, + lastStatus: undefined, }); cleanAllCache(); }, - accepted(email, lastTry) { + accepted(lastTry) { if (!state) return; update({ ...state, - email, lastTry, }); }, @@ -118,23 +114,23 @@ export function useSessionState(): SessionStateHandler { }, updateStatus(st: ChallengerApi.ChallengeStatus) { if (!state) return; - if (!state.challengeStatus) { + if (!state.lastStatus) { update({ ...state, - challengeStatus: st, + lastStatus: st, }); return; } // current status - const cu = state.challengeStatus; + const ls = state.lastStatus; if ( - cu.changes_left !== st.changes_left || - cu.fix_address !== st.fix_address || - cu.last_address !== st.last_address + ls.changes_left !== st.changes_left || + ls.fix_address !== st.fix_address || + ls.last_address !== st.last_address ) { update({ ...state, - challengeStatus: st, + lastStatus: st, }); return; } diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx index bad6d70de..62b7e775d 100644 --- a/packages/challenger-ui/src/pages/AnswerChallenge.tsx +++ b/packages/challenger-ui/src/pages/AnswerChallenge.tsx @@ -16,7 +16,7 @@ import { ChallengerApi, HttpStatusCode, - assertUnreachable + assertUnreachable, } from "@gnu-taler/taler-util"; import { Attention, @@ -25,7 +25,7 @@ import { ShowInputErrorLabel, useChallengerApiContext, useLocalNotificationHandler, - useTranslationContext + useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; @@ -50,19 +50,25 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode { pin: !pin ? i18n.str`Can't be empty` : undefined, }); + const lastEmail = !state + ? undefined + : !state.lastStatus + ? undefined + : ((state.lastStatus.last_address as any)["email"] as string); + const onSendAgain = - !state || state.email === undefined + !state || lastEmail === undefined ? undefined : withErrorHandler( async () => { - if (!state?.email) return; - return await lib.bank.challenge(nonce, { email: state.email }); + if (!lastEmail) return; + return await lib.bank.challenge(nonce, { email: lastEmail }); }, (ok) => { if ("redirectURL" in ok.body) { completed(ok.body.redirectURL); } else { - accepted(state.email!, { + accepted({ attemptsLeft: ok.body.attempts_left, nextSend: ok.body.next_tx_time, transmitted: ok.body.transmitted, @@ -141,13 +147,13 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {

{state.lastTry.transmitted ? ( - A TAN was sent to your address "{state.email}". + A TAN was sent to your address "{lastEmail}". ) : ( We recently already sent a TAN to your address " - {state.email}". A new TAN will not be transmitted again + {lastEmail}". A new TAN will not be transmitted again before "{state.lastTry.nextSend}". @@ -227,7 +233,7 @@ export function AnswerChallenge({ nonce, onComplete }: Props): VNode {

- ) + ); } export function undefinedIfEmpty(obj: T): T | undefined { diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx index 813636fa4..76fe6f00a 100644 --- a/packages/challenger-ui/src/pages/AskChallenge.tsx +++ b/packages/challenger-ui/src/pages/AskChallenge.tsx @@ -13,9 +13,7 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see */ -import { - HttpStatusCode -} from "@gnu-taler/taler-util"; +import { HttpStatusCode } from "@gnu-taler/taler-util"; import { Attention, Button, @@ -24,7 +22,7 @@ import { ShowInputErrorLabel, useChallengerApiContext, useLocalNotificationHandler, - useTranslationContext + useTranslationContext, } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; @@ -38,24 +36,22 @@ export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; type Props = { nonce: string; onSendSuccesful: () => void; - routeSolveChallenge: RouteDefinition<{nonce:string}>, + routeSolveChallenge: RouteDefinition<{ nonce: string }>; }; -export function AskChallenge({ nonce, onSendSuccesful,routeSolveChallenge }: Props): VNode { +export function AskChallenge({ + nonce, + onSendSuccesful, + routeSolveChallenge, +}: Props): VNode { const { state, accepted, completed } = useSessionState(); - const status = state?.challengeStatus; + const status = state?.lastStatus; const prevEmail = - !status || !status.last_address - ? undefined - : ((status.last_address as any)["email"] as string); + !status || !status.last_address ? undefined : status.last_address["email"]; const regexEmail = !status || !status.restrictions ? undefined - : ((status.restrictions as any)["email"] as { - regex?: string; - hint?: string; - hint_i18n?: string; - }); + : status.restrictions["email"]; const { lib } = useChallengerApiContext(); const { i18n } = useTranslationContext(); @@ -82,37 +78,39 @@ export function AskChallenge({ nonce, onSendSuccesful,routeSolveChallenge }: Pro : undefined, }); - const onSend = errors? undefined : withErrorHandler( - async () => { - return lib.bank.challenge(nonce, { email: email! }); - }, - (ok) => { - if ("redirectURL" in ok.body) { - completed(ok.body.redirectURL); - } else { - accepted(email!, { - attemptsLeft: ok.body.attempts_left, - nextSend: ok.body.next_tx_time, - transmitted: ok.body.transmitted, - }); - } - onSendSuccesful(); - }, - (fail) => { - switch (fail.case) { - case HttpStatusCode.BadRequest: - return i18n.str``; - case HttpStatusCode.NotFound: - return i18n.str``; - case HttpStatusCode.NotAcceptable: - return i18n.str``; - case HttpStatusCode.TooManyRequests: - return i18n.str``; - case HttpStatusCode.InternalServerError: - return i18n.str``; - } - }, - ); + const onSend = errors + ? undefined + : withErrorHandler( + async () => { + return lib.bank.challenge(nonce, { email: email! }); + }, + (ok) => { + if ("redirectURL" in ok.body) { + completed(ok.body.redirectURL); + } else { + accepted({ + attemptsLeft: ok.body.attempts_left, + nextSend: ok.body.next_tx_time, + transmitted: ok.body.transmitted, + }); + } + onSendSuccesful(); + }, + (fail) => { + switch (fail.case) { + case HttpStatusCode.BadRequest: + return i18n.str``; + case HttpStatusCode.NotFound: + return i18n.str``; + case HttpStatusCode.NotAcceptable: + return i18n.str``; + case HttpStatusCode.TooManyRequests: + return i18n.str``; + case HttpStatusCode.InternalServerError: + return i18n.str``; + } + }, + ); if (!status) { return
no status loaded
; @@ -136,9 +134,13 @@ export function AskChallenge({ nonce, onSendSuccesful,routeSolveChallenge }: Pro {state.lastTry && ( - + - You can change the destination or complete the challenge here. + You can change the destination or{" "} + + complete the challenge here + + . diff --git a/packages/challenger-ui/src/pages/CallengeCompleted.tsx b/packages/challenger-ui/src/pages/CallengeCompleted.tsx index 24a05c67f..f8cd7ce60 100644 --- a/packages/challenger-ui/src/pages/CallengeCompleted.tsx +++ b/packages/challenger-ui/src/pages/CallengeCompleted.tsx @@ -19,7 +19,8 @@ type Props = { nonce: string; } export function CallengeCompleted({nonce}:Props):VNode { + return
- completed + completed {nonce}
} \ No newline at end of file diff --git a/packages/taler-util/src/http-client/types.ts b/packages/taler-util/src/http-client/types.ts index 329acd484..a2f709769 100644 --- a/packages/taler-util/src/http-client/types.ts +++ b/packages/taler-util/src/http-client/types.ts @@ -18,6 +18,7 @@ import { import { PaytoString, codecForPaytoString } from "../payto.js"; import { AmountString, + InternationalizedString, codecForInternationalizedString, codecForLocation, } from "../taler-types.js"; @@ -223,7 +224,9 @@ export function createRFC8959AccessToken(token: string): AccessToken { * @param clientSecret * @returns */ -export function createClientSecretAccessToken(clientSecret: string): AccessToken { +export function createClientSecretAccessToken( + clientSecret: string, +): AccessToken { return clientSecret as AccessToken; } @@ -1516,9 +1519,9 @@ export const codecForChallengeSetupResponse = export const codecForChallengeStatus = (): Codec => buildCodecForObject() - .property("restrictions", codecForAny()) + .property("restrictions", codecOptional(codecForMap(codecForAny()))) .property("fix_address", codecForBoolean()) - .property("last_address", codecForAny()) + .property("last_address", codecOptional(codecForMap(codecForAny()))) .property("changes_left", codecForNumber()) .build("ChallengerApi.ChallengeStatus"); export const codecForChallengeCreateResponse = @@ -5279,6 +5282,12 @@ export namespace ChallengerApi { nonce: string; } + export interface Restriction { + regex?: string; + hint?: string; + hint_i18n?: InternationalizedString; + } + export interface ChallengeStatus { // Object; map of keys (names of the fields of the address // to be entered by the user) to objects with a "regex" (string) @@ -5288,7 +5297,7 @@ export namespace ChallengerApi { // by the user does not match the regex. Keys that are not mapped // to such an object have no restriction on the value provided by // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. - restrictions: Object; + restrictions: Record | undefined; // indicates if the given address cannot be changed anymore, the // form should be read-only if set to true. @@ -5296,7 +5305,7 @@ export namespace ChallengerApi { // form values from the previous submission if available, details depend // on the ADDRESS_TYPE, should be used to pre-populate the form - last_address: Object; + last_address: Record | undefined; // number of times the address can still be changed, may or may not be // shown to the user -- cgit v1.2.3