From 44820f06be979d3a30d2e66c430e212e28ca6052 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 10 Jan 2024 14:44:10 -0300 Subject: save challenge id when 2fa is required --- packages/demobank-ui/src/hooks/bank-state.ts | 64 ++++++++++++++++++++++ packages/demobank-ui/src/hooks/preferences.ts | 8 +-- packages/demobank-ui/src/pages/BankFrame.tsx | 4 +- .../demobank-ui/src/pages/OperationState/state.ts | 18 +++--- .../demobank-ui/src/pages/OperationState/views.tsx | 4 +- packages/demobank-ui/src/pages/PaymentOptions.tsx | 5 +- .../src/pages/PaytoWireTransferForm.tsx | 4 +- .../demobank-ui/src/pages/WalletWithdrawForm.tsx | 10 ++-- .../src/pages/WithdrawalConfirmationQuestion.tsx | 4 +- .../src/pages/WithdrawalOperationPage.tsx | 9 +-- .../src/pages/account/ShowAccountDetails.tsx | 4 +- .../src/pages/account/UpdateAccountPassword.tsx | 4 +- .../demobank-ui/src/pages/admin/RemoveAccount.tsx | 4 +- .../src/pages/business/CreateCashout.tsx | 4 +- 14 files changed, 113 insertions(+), 33 deletions(-) create mode 100644 packages/demobank-ui/src/hooks/bank-state.ts diff --git a/packages/demobank-ui/src/hooks/bank-state.ts b/packages/demobank-ui/src/hooks/bank-state.ts new file mode 100644 index 000000000..addbbfc0f --- /dev/null +++ b/packages/demobank-ui/src/hooks/bank-state.ts @@ -0,0 +1,64 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +import { + Codec, + buildCodecForObject, + codecForString, + codecOptional +} from "@gnu-taler/taler-util"; +import { buildStorageKey, useLocalStorage } from "@gnu-taler/web-util/browser"; + +interface BankState { + currentWithdrawalOperationId: string | undefined; + currentChallengeId: string | undefined; +} + +export const codecForBankState = (): Codec => + buildCodecForObject() + .property("currentWithdrawalOperationId", codecOptional(codecForString())) + .property("currentChallengeId", codecOptional(codecForString())) + .build("BankState"); + +const defaultBankState: BankState = { + currentWithdrawalOperationId: undefined, + currentChallengeId: undefined, +}; + +const BANK_STATE_KEY = buildStorageKey( + "bank-state", + codecForBankState(), +); + +export function useBankState(): [ + Readonly, + (key: T, value: BankState[T]) => void, + () => void, +] { + const { value, update } = useLocalStorage( + BANK_STATE_KEY, + defaultBankState, + ); + + function updateField(k: T, v: BankState[T]) { + const newValue = { ...value, [k]: v }; + update(newValue); + } + function reset() { + update(defaultBankState) + } + return [value, updateField, reset]; +} diff --git a/packages/demobank-ui/src/hooks/preferences.ts b/packages/demobank-ui/src/hooks/preferences.ts index a1525ac80..d303ac0d8 100644 --- a/packages/demobank-ui/src/hooks/preferences.ts +++ b/packages/demobank-ui/src/hooks/preferences.ts @@ -19,14 +19,11 @@ import { TranslatedString, buildCodecForObject, codecForBoolean, - codecForNumber, - codecForString, - codecOptional + codecForNumber } from "@gnu-taler/taler-util"; import { buildStorageKey, useLocalStorage, useTranslationContext } from "@gnu-taler/web-util/browser"; interface Preferences { - currentWithdrawalOperationId: string | undefined; showWithdrawalSuccess: boolean; showDemoDescription: boolean; showInstallWallet: boolean; @@ -42,7 +39,6 @@ export function getAllBooleanPreferences(): Array { export function getLabelForPreferences(k: keyof Preferences, i18n: ReturnType["i18n"]): TranslatedString { switch (k) { - case "currentWithdrawalOperationId": return i18n.str`Current withdrawal operation` case "maxWithdrawalAmount": return i18n.str`Max withdrawal amount` case "showWithdrawalSuccess": return i18n.str`Show withdrawal confirmation` case "showDemoDescription": return i18n.str`Show demo description` @@ -54,7 +50,6 @@ export function getLabelForPreferences(k: keyof Preferences, i18n: ReturnType => buildCodecForObject() - .property("currentWithdrawalOperationId", codecOptional(codecForString())) .property("showWithdrawalSuccess", (codecForBoolean())) .property("showDemoDescription", (codecForBoolean())) .property("showInstallWallet", (codecForBoolean())) @@ -64,7 +59,6 @@ export const codecForPreferences = (): Codec => .build("Settings"); const defaultPreferences: Preferences = { - currentWithdrawalOperationId: undefined, showWithdrawalSuccess: true, showDemoDescription: true, showInstallWallet: true, diff --git a/packages/demobank-ui/src/pages/BankFrame.tsx b/packages/demobank-ui/src/pages/BankFrame.tsx index 737a00b57..9e591e95d 100644 --- a/packages/demobank-ui/src/pages/BankFrame.tsx +++ b/packages/demobank-ui/src/pages/BankFrame.tsx @@ -24,6 +24,7 @@ import { getAllBooleanPreferences, getLabelForPreferences, usePreferences } from import { RenderAmount } from "./PaytoWireTransferForm.js"; import { useSettingsContext } from "../context/settings.js"; import { useBankCoreApiContext } from "../context/config.js"; +import { useBankState } from "../hooks/bank-state.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; const VERSION = typeof __VERSION__ !== "undefined" ? __VERSION__ : undefined; @@ -40,6 +41,7 @@ export function BankFrame({ const backend = useBackendState(); const settings = useSettingsContext(); const [preferences, updatePreferences] = usePreferences(); + const [, , resetBankState] = useBankState() const [error, resetError] = useErrorBoundary(); @@ -65,7 +67,7 @@ export function BankFrame({ iconLinkURL={settings.iconLinkURL ?? "#"} onLogout={backend.state.status !== "loggedIn" ? undefined : () => { backend.logOut() - updatePreferences("currentWithdrawalOperationId", undefined); + resetBankState(); }} sites={!settings.topNavSites ? [] : Object.entries(settings.topNavSites)} supportedLangs={["en", "es", "de"]} diff --git a/packages/demobank-ui/src/pages/OperationState/state.ts b/packages/demobank-ui/src/pages/OperationState/state.ts index 477146d1e..b214a400d 100644 --- a/packages/demobank-ui/src/pages/OperationState/state.ts +++ b/packages/demobank-ui/src/pages/OperationState/state.ts @@ -24,9 +24,11 @@ import { useBackendState } from "../../hooks/backend.js"; import { usePreferences } from "../../hooks/preferences.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { Props, State } from "./index.js"; +import { useBankState } from "../../hooks/bank-state.js"; export function useComponentState({ currency, onClose }: Props): utils.RecursiveState { - const [settings, updateSettings] = usePreferences() + const [settings] = usePreferences() + const [bankState, updateBankState] = useBankState(); const { state: credentials } = useBackendState() const creds = credentials.status !== "loggedIn" ? undefined : credentials const { api } = useBankCoreApiContext() @@ -46,11 +48,11 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive setFailure(resp) return; } - updateSettings("currentWithdrawalOperationId", resp.body.withdrawal_id) + updateBankState("currentWithdrawalOperationId", resp.body.withdrawal_id) } - const withdrawalOperationId = settings.currentWithdrawalOperationId + const withdrawalOperationId = bankState.currentWithdrawalOperationId useEffect(() => { if (withdrawalOperationId === undefined) { doSilentStart() @@ -77,7 +79,7 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive if (!creds) return; const resp = await api.abortWithdrawalById(creds, wid); if (resp.type === "ok") { - updateSettings("currentWithdrawalOperationId", undefined) + updateBankState("currentWithdrawalOperationId", undefined) onClose(); } else { return resp; @@ -140,7 +142,7 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive status: "aborted", error: undefined, onClose: async () => { - updateSettings("currentWithdrawalOperationId", undefined) + updateBankState("currentWithdrawalOperationId", undefined) onClose() }, } @@ -155,7 +157,7 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive status: "aborted", error: undefined, onClose: async () => { - updateSettings("currentWithdrawalOperationId", undefined) + updateBankState("currentWithdrawalOperationId", undefined) onClose() }, } @@ -163,14 +165,14 @@ export function useComponentState({ currency, onClose }: Props): utils.Recursive if (data.status === "confirmed") { if (!settings.showWithdrawalSuccess) { - updateSettings("currentWithdrawalOperationId", undefined) + updateBankState("currentWithdrawalOperationId", undefined) onClose() } return { status: "confirmed", error: undefined, onClose: async () => { - updateSettings("currentWithdrawalOperationId", undefined) + updateBankState("currentWithdrawalOperationId", undefined) onClose() }, } diff --git a/packages/demobank-ui/src/pages/OperationState/views.tsx b/packages/demobank-ui/src/pages/OperationState/views.tsx index 98eb7169f..5ebd66dac 100644 --- a/packages/demobank-ui/src/pages/OperationState/views.tsx +++ b/packages/demobank-ui/src/pages/OperationState/views.tsx @@ -24,6 +24,7 @@ import { undefinedIfEmpty } from "../../utils.js"; import { ShouldBeSameUser } from "../WithdrawalConfirmationQuestion.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { State } from "./index.js"; +import { useBankState } from "../../hooks/bank-state.js"; export function InvalidPaytoView({ payto, onClose }: State.InvalidPayto) { return ( @@ -45,6 +46,7 @@ export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doCon const { i18n } = useTranslationContext() const [settings] = usePreferences() const [notification, notify, errorHandler] = useLocalNotification() + const [, updateBankState] = useBankState() const captchaNumbers = useMemo(() => { return { @@ -135,7 +137,7 @@ export function NeedConfirmationView({ error, onAbort: doAbort, onConfirm: doCon debug: resp.detail, }); case HttpStatusCode.Accepted: { - resp.body.challenge_id; + updateBankState("currentChallengeId", resp.body.challenge_id) return notify({ type: "info", title: i18n.str`The operation needs a confirmation to complete.`, diff --git a/packages/demobank-ui/src/pages/PaymentOptions.tsx b/packages/demobank-ui/src/pages/PaymentOptions.tsx index bbe33eb57..1a431a939 100644 --- a/packages/demobank-ui/src/pages/PaymentOptions.tsx +++ b/packages/demobank-ui/src/pages/PaymentOptions.tsx @@ -21,6 +21,7 @@ import { useState } from "preact/hooks"; import { PaytoWireTransferForm, doAutoFocus } from "./PaytoWireTransferForm.js"; import { WalletWithdrawForm } from "./WalletWithdrawForm.js"; import { usePreferences } from "../hooks/preferences.js"; +import { useBankState } from "../hooks/bank-state.js"; /** * Let the user choose a payment option, @@ -28,7 +29,7 @@ import { usePreferences } from "../hooks/preferences.js"; */ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJson, goToConfirmOperation: (id: string) => void }): VNode { const { i18n } = useTranslationContext(); - const [settings] = usePreferences(); + const [bankState] = useBankState(); const [tab, setTab] = useState<"charge-wallet" | "wire-transfer" | undefined>(); @@ -59,7 +60,7 @@ export function PaymentOptions({ limit, goToConfirmOperation }: { limit: AmountJ
Withdraw digital money into your mobile wallet or browser extension
- {!!settings.currentWithdrawalOperationId && + {!!bankState.currentWithdrawalOperationId && - To complete or cancel the operation click here + To complete or cancel the operation click here } @@ -99,7 +101,7 @@ function OldWithdrawalForm({ goToConfirmOperation, limit, onCancel, focus }: { i18n.str`Server responded with an invalid withdraw URI`, i18n.str`Withdraw URI: ${resp.body.taler_withdraw_uri}`); } else { - updateSettings("currentWithdrawalOperationId", uri.withdrawalOperationId) + updateBankState("currentWithdrawalOperationId", uri.withdrawalOperationId) goToConfirmOperation(uri.withdrawalOperationId); } } else { diff --git a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx index 6f18e1283..206b51008 100644 --- a/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalConfirmationQuestion.tsx @@ -48,6 +48,7 @@ import { LoginForm } from "./LoginForm.js"; import { RenderAmount } from "./PaytoWireTransferForm.js"; import { assertUnreachable } from "./WithdrawalOperationPage.js"; import { OperationNotFound } from "./WithdrawalQRCode.js"; +import { useBankState } from "../hooks/bank-state.js"; const logger = new Logger("WithdrawalConfirmationQuestion"); @@ -75,6 +76,7 @@ export function WithdrawalConfirmationQuestion({ const { state: credentials } = useBackendState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials const withdrawalInfo = useWithdrawalDetails(withdrawUri.withdrawalOperationId) + const [, updateBankState] = useBankState() if (!withdrawalInfo) { return } @@ -154,7 +156,7 @@ export function WithdrawalConfirmationQuestion({ debug: resp.detail, }) case HttpStatusCode.Accepted: { - resp.body.challenge_id; + updateBankState("currentChallengeId", resp.body.challenge_id) return notify({ type: "info", title: i18n.str`The operation needs a confirmation to complete.`, diff --git a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx index 7060b7a98..4bb3b4d7b 100644 --- a/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx +++ b/packages/demobank-ui/src/pages/WithdrawalOperationPage.tsx @@ -20,12 +20,12 @@ import { stringifyWithdrawUri } from "@gnu-taler/taler-util"; import { + Attention, useTranslationContext } from "@gnu-taler/web-util/browser"; import { Fragment, VNode, h } from "preact"; -import { Attention } from "@gnu-taler/web-util/browser"; import { useBankCoreApiContext } from "../context/config.js"; -import { usePreferences } from "../hooks/preferences.js"; +import { useBankState } from "../hooks/bank-state.js"; import { WithdrawalQRCode } from "./WithdrawalQRCode.js"; const logger = new Logger("AccountPage"); @@ -44,7 +44,8 @@ export function WithdrawalOperationPage({ }); const parsedUri = parseWithdrawUri(uri); const { i18n } = useTranslationContext(); - const [settings, updateSettings] = usePreferences(); + const [, updateBankState] = useBankState(); + if (!parsedUri) { return @@ -56,7 +57,7 @@ export function WithdrawalOperationPage({ { - updateSettings("currentWithdrawalOperationId", undefined) + updateBankState("currentWithdrawalOperationId", undefined) onContinue() }} /> diff --git a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx index 1bf21f62e..28875bde6 100644 --- a/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx +++ b/packages/demobank-ui/src/pages/account/ShowAccountDetails.tsx @@ -10,6 +10,7 @@ import { LoginForm } from "../LoginForm.js"; import { ProfileNavigation } from "../ProfileNavigation.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { AccountForm } from "../admin/AccountForm.js"; +import { useBankState } from "../../hooks/bank-state.js"; export function ShowAccountDetails({ account, @@ -30,6 +31,7 @@ export function ShowAccountDetails({ const [update, setUpdate] = useState(false); const [submitAccount, setSubmitAccount] = useState(); const [notification, notify, handleError] = useLocalNotification() + const [, updateBankState] = useBankState() const result = useAccountDetails(account); if (!result) { @@ -97,7 +99,7 @@ export function ShowAccountDetails({ debug: resp.detail, }) case HttpStatusCode.Accepted: { - resp.body.challenge_id; + updateBankState("currentChallengeId", resp.body.challenge_id) return notify({ type: "info", title: i18n.str`Cashout created but confirmation is required.`, diff --git a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx index ed074b9c4..0ff1cf725 100644 --- a/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx +++ b/packages/demobank-ui/src/pages/account/UpdateAccountPassword.tsx @@ -10,6 +10,7 @@ import { ProfileNavigation } from "../ProfileNavigation.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; import { LocalNotificationBanner } from "@gnu-taler/web-util/browser"; import { HttpStatusCode, TalerErrorCode } from "@gnu-taler/taler-util"; +import { useBankState } from "../../hooks/bank-state.js"; export function UpdateAccountPassword({ account: accountName, @@ -30,6 +31,7 @@ export function UpdateAccountPassword({ const [current, setCurrent] = useState(); const [password, setPassword] = useState(); const [repeat, setRepeat] = useState(); + const [, updateBankState] = useBankState() const accountIsTheCurrentUser = credentials.status === "loggedIn" ? credentials.username === accountName : false @@ -75,7 +77,7 @@ export function UpdateAccountPassword({ title: i18n.str`Your current password doesn't match, can't change to a new password.` }) case HttpStatusCode.Accepted: { - resp.body.challenge_id; + updateBankState("currentChallengeId", resp.body.challenge_id) return notify({ type: "info", title: i18n.str`Cashout created but confirmation is required.`, diff --git a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx index 330ebf3a9..3f7d62935 100644 --- a/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx +++ b/packages/demobank-ui/src/pages/admin/RemoveAccount.tsx @@ -10,6 +10,7 @@ import { undefinedIfEmpty } from "../../utils.js"; import { LoginForm } from "../LoginForm.js"; import { doAutoFocus } from "../PaytoWireTransferForm.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; +import { useBankState } from "../../hooks/bank-state.js"; export function RemoveAccount({ account, @@ -30,6 +31,7 @@ export function RemoveAccount({ const token = state.status !== "loggedIn" ? undefined : state.token const { api } = useBankCoreApiContext() const [notification, notify, handleError] = useLocalNotification() + const [, updateBankState] = useBankState() if (!result) { return @@ -90,7 +92,7 @@ export function RemoveAccount({ debug: resp.detail, }) case HttpStatusCode.Accepted: { - resp.body.challenge_id; + updateBankState("currentChallengeId", resp.body.challenge_id) return notify({ type: "info", title: i18n.str`The operation needs a confirmation to complete.`, diff --git a/packages/demobank-ui/src/pages/business/CreateCashout.tsx b/packages/demobank-ui/src/pages/business/CreateCashout.tsx index 9ee5cbeaf..d97a00a2e 100644 --- a/packages/demobank-ui/src/pages/business/CreateCashout.tsx +++ b/packages/demobank-ui/src/pages/business/CreateCashout.tsx @@ -50,6 +50,7 @@ import { import { LoginForm } from "../LoginForm.js"; import { InputAmount, RenderAmount, doAutoFocus } from "../PaytoWireTransferForm.js"; import { assertUnreachable } from "../WithdrawalOperationPage.js"; +import { useBankState } from "../../hooks/bank-state.js"; interface Props { account: string; @@ -83,6 +84,7 @@ export function CreateCashout({ } = useEstimator(); const { state: credentials } = useBackendState(); const creds = credentials.status !== "loggedIn" ? undefined : credentials + const [, updateBankState] = useBankState() const { api, config } = useBankCoreApiContext() const [form, setForm] = useState>({ isDebit: true, }); @@ -199,7 +201,7 @@ export function CreateCashout({ } else { switch (resp.case) { case HttpStatusCode.Accepted: { - resp.body.challenge_id; + updateBankState("currentChallengeId", resp.body.challenge_id) return notify({ type: "info", title: i18n.str`Cashout created but confirmation is required.`, -- cgit v1.2.3