taler-typescript-core

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

commit 4693e3d01ea059ccbcf62d797c2322c10a32fd25
parent 5fefbf3179e828dbbed06ffdb66662a92501b7be
Author: Sebastian <sebasjm@gmail.com>
Date:   Thu,  4 Jul 2024 10:08:28 -0300

take session from params

Diffstat:
Mpackages/challenger-ui/src/Routing.tsx | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mpackages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx | 8+++-----
Mpackages/challenger-ui/src/hooks/challenge.ts | 2+-
Mpackages/challenger-ui/src/hooks/session.ts | 13++++---------
Mpackages/challenger-ui/src/pages/AnswerChallenge.tsx | 15+++++++--------
Mpackages/challenger-ui/src/pages/AskChallenge.tsx | 16+++++++++-------
Mpackages/challenger-ui/src/pages/CallengeCompleted.tsx | 8--------
7 files changed, 145 insertions(+), 98 deletions(-)

diff --git a/packages/challenger-ui/src/Routing.tsx b/packages/challenger-ui/src/Routing.tsx @@ -15,22 +15,20 @@ */ import { - Loading, urlPattern, useCurrentLocation, - useNavigationContext, + useNavigationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { assertUnreachable } from "@gnu-taler/taler-util"; import { useErrorBoundary } from "preact/hooks"; import { CheckChallengeIsUpToDate } from "./components/CheckChallengeIsUpToDate.js"; -import { SessionId, useSessionState } from "./hooks/session.js"; +import { SessionId } from "./hooks/session.js"; import { AnswerChallenge } from "./pages/AnswerChallenge.js"; import { AskChallenge } from "./pages/AskChallenge.js"; import { CallengeCompleted } from "./pages/CallengeCompleted.js"; import { Frame } from "./pages/Frame.js"; -import { NonceNotFound } from "./pages/NonceNotFound.js"; import { Setup } from "./pages/Setup.js"; export function Routing(): VNode { @@ -73,16 +71,19 @@ export function safeToURL(s: string | undefined): URL | undefined { } function PublicRounting(): VNode { - const location = useCurrentLocation(publicPages); + const loc = useCurrentLocation(publicPages); const { navigateTo } = useNavigationContext(); - const { start } = useSessionState(); useErrorBoundary((e) => { console.log("error", e); }); - if (location === undefined) { - return <NonceNotFound />; - } + const location: typeof loc = + loc.name === undefined + ? { + ...loc, + name: "authorize", + } + : loc; switch (location.name) { case "noinfo": { @@ -91,14 +92,13 @@ function PublicRounting(): VNode { case "setup": { const secret = safeGetParam(location.params, "secret"); const redirectURL = safeToURL( - safeGetParam(location.params, "redirect_url"), + safeGetParam(location.params, "redirect_uri"), ); return ( <Setup clientId={location.values.client} secret={secret} - // redirect_url=http://exchange.taler.test:1180/kyc-proof/kyc-provider-wallet&secret=chal-secret redirectURL={redirectURL} onCreated={() => { navigateTo(publicPages.ask.url({})); @@ -109,20 +109,33 @@ function PublicRounting(): VNode { case "authorize": { const clientId = safeGetParam(location.params, "client_id"); const redirectURL = safeToURL( - safeGetParam(location.params, "redirect_url"), + safeGetParam(location.params, "redirect_uri"), ); const state = safeGetParam(location.params, "state"); + const nonce = safeGetParam(location.params, "nonce"); const sessionId: SessionId | undefined = - !clientId || !redirectURL || !state + !clientId || !redirectURL || !state || !nonce ? undefined : { clientId, - nonce: location.values.nonce, + nonce: nonce, redirectURL: redirectURL.href, state, }; + if (!sessionId) { + return ( + <div> + one of the params is missing{" "} + {JSON.stringify( + { clientId, redirectURL, state, nonce }, + undefined, + 2, + )} + </div> + ); + } return ( <CheckChallengeIsUpToDate session={sessionId} @@ -148,64 +161,112 @@ function PublicRounting(): VNode { ); }} > - <Loading /> + No nonce has been found </CheckChallengeIsUpToDate> ); } case "ask": { + const clientId = safeGetParam(location.params, "client_id"); + const redirectURL = safeToURL( + safeGetParam(location.params, "redirect_uri"), + ); + const state = safeGetParam(location.params, "state"); + const nonce = safeGetParam(location.params, "nonce"); + + const sessionId: SessionId | undefined = + !clientId || !redirectURL || !state || !nonce + ? undefined + : { + clientId, + nonce: nonce, + redirectURL: redirectURL.href, + state, + }; + + if (!sessionId) { + return ( + <div> + one of the params is missing{" "} + {JSON.stringify(sessionId, undefined, 2)} + </div> + ); + } return ( - <CheckChallengeIsUpToDate> - <AskChallenge - focus - routeSolveChallenge={publicPages.answer} - onSendSuccesful={() => { - navigateTo( - publicPages.answer.url({ - nonce: location.values.nonce, - }), - ); - }} - // onCompleted={() => { - // navigateTo( - // publicPages.completed.url({ - // nonce: location.values.nonce, - // }), - // ); - // }} - /> - </CheckChallengeIsUpToDate> + <AskChallenge + session={sessionId} + focus + routeSolveChallenge={publicPages.answer} + onSendSuccesful={() => { + navigateTo( + publicPages.answer.url({ + nonce: location.values.nonce, + }), + ); + }} + // onCompleted={() => { + // navigateTo( + // publicPages.completed.url({ + // nonce: location.values.nonce, + // }), + // ); + // }} + /> ); } case "answer": { + const clientId = safeGetParam(location.params, "client_id"); + const redirectURL = safeToURL( + safeGetParam(location.params, "redirect_uri"), + ); + const state = safeGetParam(location.params, "state"); + const nonce = safeGetParam(location.params, "nonce"); + + const sessionId: SessionId | undefined = + !clientId || !redirectURL || !state || !nonce + ? undefined + : { + clientId, + nonce: nonce, + redirectURL: redirectURL.href, + state, + }; + + if (!sessionId) { + return ( + <div> + one of the params is missing{" "} + {JSON.stringify( + { clientId, redirectURL, state, nonce }, + undefined, + 2, + )} + </div> + ); + } return ( - <CheckChallengeIsUpToDate> - <AnswerChallenge - focus - routeAsk={publicPages.ask} - onComplete={() => { - navigateTo( - publicPages.completed.url({ - nonce: location.values.nonce, - }), - ); - }} - // onCompleted={() => { - // navigateTo( - // publicPages.completed.url({ - // nonce: location.values.nonce, - // }), - // ); - // }} - /> - </CheckChallengeIsUpToDate> + <AnswerChallenge + focus + session={sessionId} + routeAsk={publicPages.ask} + onComplete={() => { + navigateTo( + publicPages.completed.url({ + nonce: location.values.nonce, + }), + ); + }} + // onCompleted={() => { + // navigateTo( + // publicPages.completed.url({ + // nonce: location.values.nonce, + // }), + // ); + // }} + /> ); } case "completed": { - return ( - <CheckChallengeIsUpToDate> - <CallengeCompleted /> - </CheckChallengeIsUpToDate> - ); + return <CallengeCompleted />; } default: assertUnreachable(location); diff --git a/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx b/packages/challenger-ui/src/components/CheckChallengeIsUpToDate.tsx @@ -28,7 +28,7 @@ import { useChallengeSession } from "../hooks/challenge.js"; import { SessionId, useSessionState } from "../hooks/session.js"; interface Props { - session?: SessionId | undefined; + session: SessionId; children: ComponentChildren; onCompleted?: () => void; onChangeLeft?: () => void; @@ -44,9 +44,7 @@ export function CheckChallengeIsUpToDate({ const { state } = useSessionState(); const { i18n } = useTranslationContext(); - const id = session ?? state; - - const result = useChallengeSession(id); + const result = useChallengeSession(session); if (!result) { return <Loading />; @@ -103,7 +101,7 @@ export function CheckChallengeIsUpToDate({ </Attention> <div class="mt-2"> - <a href={id?.redirectURL ?? ""}>{id?.redirectURL}</a> + <a href={session.redirectURL ?? ""}>{session.redirectURL}</a> </div> </Fragment> ); diff --git a/packages/challenger-ui/src/hooks/challenge.ts b/packages/challenger-ui/src/hooks/challenge.ts @@ -30,7 +30,7 @@ export function revalidateChallengeSession() { ); } -export function useChallengeSession(session: SessionId | undefined) { +export function useChallengeSession(session: SessionId) { const { lib: { challenger: api }, } = useChallengerApiContext(); diff --git a/packages/challenger-ui/src/hooks/session.ts b/packages/challenger-ui/src/hooks/session.ts @@ -16,15 +16,15 @@ import { AbsoluteTime, + ChallengerApi, Codec, buildCodecForObject, codecForAbsoluteTime, codecForAny, + codecForList, codecForString, codecForStringURL, codecOptional, - ChallengerApi, - codecForList, } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; @@ -53,7 +53,7 @@ interface LastAddress { savedAt: AbsoluteTime; } -export type SessionState = SessionId & { +export type SessionState = { completedURL: string | undefined; lastAddress: Array<LastAddress> | undefined; }; @@ -66,10 +66,6 @@ export const codecForLastAddress = (): Codec<LastAddress> => export const codecForSessionState = (): Codec<SessionState> => buildCodecForObject<SessionState>() - .property("nonce", codecForString()) - .property("clientId", codecForString()) - .property("redirectURL", codecForStringURL()) - .property("state", codecForString()) .property("completedURL", codecOptional(codecForStringURL())) .property("lastAddress", codecOptional(codecForList(codecForLastAddress()))) .build("SessionState"); @@ -99,9 +95,8 @@ export function useSessionState(): SessionStateHandler { return { state, - start(info) { + start() { update({ - ...info, completedURL: undefined, lastAddress: state?.lastAddress ?? [], }); diff --git a/packages/challenger-ui/src/pages/AnswerChallenge.tsx b/packages/challenger-ui/src/pages/AnswerChallenge.tsx @@ -37,10 +37,11 @@ import { revalidateChallengeSession, useChallengeSession, } from "../hooks/challenge.js"; -import { useSessionState } from "../hooks/session.js"; +import { SessionId, useSessionState } from "../hooks/session.js"; type Props = { focus?: boolean; + session: SessionId, onComplete: () => void; routeAsk: RouteDefinition<EmptyObject>; }; @@ -63,10 +64,10 @@ function useReloadOnDeadline(deadline: AbsoluteTime): void { }, [deadline]); } -export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { +export function AnswerChallenge({ session, focus, onComplete, routeAsk }: Props): VNode { const { config, lib } = useChallengerApiContext(); const { i18n } = useTranslationContext(); - const { state, sent, failed, completed } = useSessionState(); + const { sent, failed, completed } = useSessionState(); const [notification, withErrorHandler] = useLocalNotificationHandler(); const [pin, setPin] = useState<string | undefined>(); const errors = undefinedIfEmpty({ @@ -80,7 +81,7 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { ? undefined : restrictionKeys[0]; - const result = useChallengeSession(state); + const result = useChallengeSession(session); const lastStatus = result && !(result instanceof TalerError) && result.type !== "fail" @@ -110,7 +111,6 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { const contact = lastAddr ? { [restrictionKey]: lastAddr } : undefined; const onSendAgain = - !state?.nonce || contact === undefined || lastStatus == undefined || lastStatus.pin_transmissions_left === 0 || @@ -119,7 +119,7 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { ? undefined : withErrorHandler( async () => { - return await lib.challenger.challenge(state.nonce, contact); + return await lib.challenger.challenge(session.nonce, contact); }, (ok) => { if (ok.body.type === "completed") { @@ -145,14 +145,13 @@ export function AnswerChallenge({ focus, onComplete, routeAsk }: Props): VNode { ); const onCheck = - !state?.nonce || errors !== undefined || lastStatus == undefined || lastStatus.auth_attempts_left === 0 ? undefined : withErrorHandler( async () => { - return lib.challenger.solve(state.nonce, { pin: pin! }); + return lib.challenger.solve(session.nonce, { pin: pin! }); }, (ok) => { if (ok.body.type === "completed") { diff --git a/packages/challenger-ui/src/pages/AskChallenge.tsx b/packages/challenger-ui/src/pages/AskChallenge.tsx @@ -14,11 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ import { - AbsoluteTime, EmptyObject, HttpStatusCode, TalerError, - TranslatedString, + TranslatedString } from "@gnu-taler/taler-util"; import { Attention, @@ -33,14 +32,15 @@ import { } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; import { useState } from "preact/hooks"; -import { useSessionState } from "../hooks/session.js"; -import { doAutoFocus } from "./AnswerChallenge.js"; import { useChallengeSession } from "../hooks/challenge.js"; +import { SessionId, useSessionState } from "../hooks/session.js"; +import { doAutoFocus } from "./AnswerChallenge.js"; export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/; type Props = { onSendSuccesful: () => void; + session: SessionId; routeSolveChallenge: RouteDefinition<EmptyObject>; focus?: boolean; }; @@ -48,6 +48,7 @@ type Props = { export function AskChallenge({ onSendSuccesful, routeSolveChallenge, + session, focus, }: Props): VNode { const { state, sent, saveAddress, completed } = useSessionState(); @@ -67,7 +68,7 @@ export function AskChallenge({ ? undefined : restrictionKeys[0]; - const result = useChallengeSession(state); + const result = useChallengeSession(session); if (!restrictionKey) { return ( @@ -117,11 +118,11 @@ export function AskChallenge({ : state.lastAddress.filter((d) => !!d.address[restrictionKey]); const onSend = - errors || !contact || !state?.nonce + errors || !contact ? undefined : withErrorHandler( async () => { - return lib.challenger.challenge(state.nonce, contact); + return lib.challenger.challenge(session.nonce, contact); }, (ok) => { if (ok.body.type === "completed") { @@ -210,6 +211,7 @@ export function AskChallenge({ return ( <label data-checked={addrIndex === idx} + key={idx} class="relative flex border-gray-200 data-[checked=true]:z-10 data-[checked=true]:bg-indigo-50 cursor-pointer flex-col rounded-tl-md rounded-tr-md border p-4 focus:outline-none md:grid md:grid-cols-2 md:pl-4 md:pr-6" > <span class="flex items-center text-sm"> diff --git a/packages/challenger-ui/src/pages/CallengeCompleted.tsx b/packages/challenger-ui/src/pages/CallengeCompleted.tsx @@ -13,20 +13,12 @@ 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 { 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"; export function CallengeCompleted(): VNode { const { state } = useSessionState(); - const result = useChallengeSession(state); - - const lastStatus = - result && !(result instanceof TalerError) && result.type !== "fail" - ? result.body - : undefined; const { i18n } = useTranslationContext();