diff options
47 files changed, 1287 insertions, 283 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx index 43807fefe..d290a6602 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,7 +25,7 @@ import { AddingProviderScreen as TestedComponent } from './AddingProviderScreen' export default { - title: 'Pages/backup/AddingProviderScreen', + title: 'Pages/backup/AuthorizationMethod/AddingProvider', component: TestedComponent, args: { order: 4, diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx index 549686616..9cdd132ef 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,10 +25,10 @@ import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen' export default { - title: 'Pages/AttributeEntryScreen', + title: 'Pages/PersonalInformation', component: TestedComponent, args: { - order: 4, + order: 3, }, argTypes: { onUpdate: { action: 'onUpdate' }, diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx index 5077c3eb0..2712522ce 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,10 +25,10 @@ import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationE export default { - title: 'Pages/backup/AuthenticationEditorScreen', + title: 'Pages/backup/AuthorizationMethod', component: TestedComponent, args: { - order: 5, + order: 4, }, argTypes: { onUpdate: { action: 'onUpdate' }, @@ -37,7 +36,7 @@ export default { }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.authEditing); +export const InitialState = createExample(TestedComponent, reducerStatesExample.authEditing); export const OneAuthMethodConfigured = createExample(TestedComponent, { ...reducerStatesExample.authEditing, authentication_methods: [{ @@ -86,8 +85,3 @@ export const NoAuthMethodProvided = createExample(TestedComponent, { authentication_providers: {}, authentication_methods: [] } as ReducerState); - - // type: string; - // instructions: string; - // challenge: string; - // mime_type?: string; diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index 93ca81194..a71220c55 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -1,10 +1,8 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { AuthMethod } from "anastasis-core"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { TextInput } from "../../components/fields/TextInput"; import { useAnastasisContext } from "../../context/anastasis"; -import { authMethods, KnownAuthMethods } from "./authMethod"; +import { authMethods, AuthMethodSetupProps, AuthMethodWithRemove, KnownAuthMethods } from "./authMethod"; import { AnastasisClientFrame } from "./index"; @@ -14,7 +12,7 @@ const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T> export function AuthenticationEditorScreen(): VNode { const [noProvidersAck, setNoProvidersAck] = useState(false) const [selectedMethod, setSelectedMethod] = useState<KnownAuthMethods | undefined>(undefined); - const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined) + // const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined) const reducer = useAnastasisContext() if (!reducer) { @@ -63,7 +61,7 @@ export function AuthenticationEditorScreen(): VNode { setSelectedMethod(undefined); }; - const AuthSetup = authMethods[selectedMethod].screen ?? AuthMethodNotImplemented; + const AuthSetup = authMethods[selectedMethod].setup ?? AuthMethodNotImplemented; return (<Fragment> <AuthSetup cancel={cancel} @@ -88,10 +86,6 @@ export function AuthenticationEditorScreen(): VNode { ); } - if (addingProvider !== undefined) { - return <div /> - } - function MethodButton(props: { method: KnownAuthMethods }): VNode { if (authMethods[props.method].skip) return <div /> @@ -169,14 +163,6 @@ export function AuthenticationEditorScreen(): VNode { ); } -type AuthMethodWithRemove = AuthMethod & { remove: () => void } -export interface AuthMethodSetupProps { - method: string; - addAuthMethod: (x: any) => void; - configured: AuthMethodWithRemove[]; - cancel: () => void; -} - function AuthMethodNotImplemented(props: AuthMethodSetupProps): VNode { return ( <AnastasisClientFrame hideNav title={`Add ${props.method} authentication`}> diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index b71a79727..306adacbb 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,10 +25,10 @@ import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen' export default { - title: 'Pages/backup/FinishedScreen', + title: 'Pages/backup/Finished', component: TestedComponent, args: { - order: 9, + order: 8, }, argTypes: { onUpdate: { action: 'onUpdate' }, diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index e001ed157..46c574cf2 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -24,7 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils"; import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen"; export default { - title: "Pages/recovery/ChallengeOverviewScreen", + title: "Pages/recovery/SolveChallenge/Overview", component: TestedComponent, args: { order: 5, diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx index e5fe09e99..fbcaa0e95 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx @@ -24,7 +24,7 @@ import { ChallengePayingScreen as TestedComponent } from './ChallengePayingScree export default { - title: 'Pages/recovery/__ChallengePayingScreen', + title: 'Pages/recovery/__ChallengePaying', component: TestedComponent, args: { order: 10, diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx index fc339e48e..3d5fcce55 100644 --- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx @@ -26,7 +26,7 @@ import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen'; export default { - title: 'Pages/backup/ReviewPoliciesScreen/EditPoliciesScreen', + title: 'Pages/backup/ReviewPolicies/EditPolicies', args: { order: 6, }, diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx index e952ab28d..3ddf8011e 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,10 +25,10 @@ import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen' export default { - title: 'Pages/backup/PoliciesPayingScreen', + title: 'Pages/backup/__PoliciesPaying', component: TestedComponent, args: { - order: 8, + order: 9, }, argTypes: { onUpdate: { action: 'onUpdate' }, diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx index 0d2ebb778..e92a231a8 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx @@ -26,7 +26,7 @@ import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScr export default { - title: 'Pages/recovery/FinishedScreen', + title: 'Pages/recovery/Finished', args: { order: 7, }, diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx index 9f7e26c16..e348101ee 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,7 +25,7 @@ import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen' export default { - title: 'Pages/backup/ReviewPoliciesScreen', + title: 'Pages/backup/ReviewPolicies', args: { order: 6, }, diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx index 49dd8fca8..db061d936 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -26,7 +25,7 @@ import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen'; export default { - title: 'Pages/backup/SecretEditorScreen', + title: 'Pages/backup/SecretInput', component: TestedComponent, args: { order: 7, diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx index 6919eebad..8d02ebfbe 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx @@ -25,7 +25,7 @@ import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScree export default { - title: 'Pages/recovery/SecretSelectionScreen', + title: 'Pages/recovery/SecretSelection', component: TestedComponent, args: { order: 4, diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx index 7e3880f9c..82f06c34f 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx @@ -25,7 +25,7 @@ import { SolveScreen as TestedComponent } from './SolveScreen'; export default { - title: 'Pages/recovery/SolveScreen', + title: 'Pages/recovery/SolveChallenge/Solve', component: TestedComponent, args: { order: 6, diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 35db5ead0..ec6c7735b 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -1,17 +1,14 @@ import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; import { AnastasisClientFrame } from "."; import { ChallengeFeedback, - ChallengeFeedbackStatus, - ChallengeInfo, + ChallengeFeedbackStatus } from "../../../../anastasis-core/lib"; -import { AsyncButton } from "../../components/AsyncButton"; -import { TextInput } from "../../components/fields/TextInput"; import { Notifications } from "../../components/Notifications"; import { useAnastasisContext } from "../../context/anastasis"; +import { authMethods, AuthMethodSolveProps, KnownAuthMethods } from "./authMethod"; -function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }): VNode { +export function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }): VNode { const { feedback } = props; if (!feedback) { return <div />; @@ -80,7 +77,6 @@ function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }): export function SolveScreen(): VNode { const reducer = useAnastasisContext(); - const [answer, setAnswer] = useState(""); if (!reducer) { return ( @@ -120,162 +116,30 @@ export function SolveScreen(): VNode { </AnastasisClientFrame> ); } - - const chArr = reducer.currentReducerState.recovery_information.challenges; - const challengeFeedback = - reducer.currentReducerState.challenge_feedback ?? {}; - const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; - const challenges: { - [uuid: string]: ChallengeInfo; - } = {}; - for (const ch of chArr) { - challenges[ch.uuid] = ch; - } - const selectedChallenge = challenges[selectedUuid]; - const dialogMap: Record<string, (p: SolveEntryProps) => h.JSX.Element> = { - question: SolveQuestionEntry, - sms: SolveSmsEntry, - email: SolveEmailEntry, - post: SolvePostEntry, - }; - const SolveDialog = - selectedChallenge === undefined - ? SolveUndefinedEntry - : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry; - - async function onNext(): Promise<void> { - return reducer?.transition("solve_challenge", { answer }); - } - function onCancel(): void { - reducer?.back(); + function SolveNotImplemented(): VNode { + return ( + <AnastasisClientFrame hideNav title="Not implemented"> + <p> + The challenge selected is not supported for this UI. Please update this + version or try using another policy. + </p> + {reducer && + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + } + </AnastasisClientFrame> + ); } - const feedback = challengeFeedback[selectedUuid] - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown - - return ( - <AnastasisClientFrame hideNav title="Recovery: Solve challenge"> - <SolveOverviewFeedbackDisplay - feedback={feedback} - /> - <SolveDialog - id={selectedUuid} - answer={answer} - setAnswer={setAnswer} - challenge={selectedChallenge} - feedback={feedback} - /> - - <div - style={{ - marginTop: "2em", - display: "flex", - justifyContent: "space-between", - }} - > - <button class="button" onClick={onCancel}> - Cancel - </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} - </div> - </AnastasisClientFrame> - ); -} - -export interface SolveEntryProps { - id: string; - challenge: ChallengeInfo; - feedback?: ChallengeFeedback; - answer: string; - setAnswer: (s: string) => void; -} - -function SolveSmsEntry({ - challenge, - answer, - setAnswer, -}: SolveEntryProps): VNode { - return ( - <Fragment> - - <p> - An sms has been sent to "<b>{challenge.instructions}</b>". Type the code - below - </p> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> - </Fragment> - ); -} -function SolveQuestionEntry({ - challenge, - answer, - setAnswer, -}: SolveEntryProps): VNode { - return ( - <Fragment> - <p>Type the answer to the following question:</p> - <pre>{challenge.instructions}</pre> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> - </Fragment> - ); -} -function SolvePostEntry({ - challenge, - answer, - setAnswer, -}: SolveEntryProps): VNode { - return ( - <Fragment> - <p> - instruction for post type challenge "<b>{challenge.instructions}</b>" - </p> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> - </Fragment> - ); -} + const chArr = reducer.currentReducerState.recovery_information.challenges; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const selectedChallenge = chArr.find(ch => ch.uuid === selectedUuid) -function SolveEmailEntry({ - challenge, - answer, - setAnswer, -}: SolveEntryProps): VNode { - return ( - <Fragment> - <p> - An email has been sent to "<b>{challenge.instructions}</b>". Type the - code below - </p> - <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> - </Fragment> - ); -} + const SolveDialog = !selectedChallenge || !authMethods[selectedChallenge.type as KnownAuthMethods] ? + SolveNotImplemented : + authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? SolveNotImplemented -function SolveUnsupportedEntry(props: SolveEntryProps): VNode { - return ( - <Fragment> - <p> - The challenge selected is not supported for this UI. Please update this - version or try using another policy. - </p> - <p> - <b>Challenge type:</b> {props.challenge.type} - </p> - </Fragment> - ); -} -function SolveUndefinedEntry(props: SolveEntryProps): VNode { - return ( - <Fragment> - <p> - There is no challenge information for id <b>"{props.id}"</b>. Try - resetting the recovery session. - </p> - </Fragment> - ); + return <SolveDialog id={selectedUuid} /> } diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx index 657a2dd74..41082c128 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx @@ -24,7 +24,7 @@ import { StartScreen as TestedComponent } from './StartScreen'; export default { - title: 'Pages/StartScreen', + title: 'Pages/Start', component: TestedComponent, args: { order: 1, diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx index 7568ccd69..38b71bc36 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx @@ -25,7 +25,7 @@ import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen'; export default { - title: 'Pages/backup/__TruthsPayingScreen', + title: 'Pages/backup/__TruthsPaying', component: TestedComponent, args: { order: 10, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx index e178a4955..da87b7a8b 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -25,7 +24,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; export default { - title: 'Pages/backup/authMethods/email', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/email', component: TestedComponent, args: { order: 5, @@ -38,11 +37,11 @@ export default { const type: KnownAuthMethods = 'email' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -51,7 +50,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce }] }); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx index 1a6be1b61..27a0685b2 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx @@ -1,14 +1,12 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; +import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; -import { TextInput } from "../../../components/fields/TextInput"; import { EmailInput } from "../../../components/fields/EmailInput"; +import { AnastasisClientFrame } from "../index"; +import { AuthMethodSetupProps } from "./index"; const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx new file mode 100644 index 000000000..525cd2b07 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx @@ -0,0 +1,80 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/email', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'email' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + +export const PaymentFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', + challenge_feedback: { + 'uuid-1': { + state: ChallengeFeedbackStatus.Payment, + taler_pay_uri: "taler://pay/...", + provider: "https://localhost:8080/", + payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" + } + } +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx new file mode 100644 index 000000000..bd4f43740 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx @@ -0,0 +1,106 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + An email has been sent to "<b>{selectedChallenge.instructions}</b>". Type the + code below + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx index 71f618646..be0a04847 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ /* This file is part of GNU Taler (C) 2021 Taler Systems S.A. @@ -25,7 +24,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; export default { - title: 'Pages/backup/authMethods/IBAN', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/IBAN', component: TestedComponent, args: { order: 5, @@ -38,11 +37,11 @@ export default { const type: KnownAuthMethods = 'iban' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -50,7 +49,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce remove: () => null }] }); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx index c9edbfa07..87969ab27 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { canonicalJson, encodeCrock, @@ -6,8 +5,8 @@ import { } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "."; import { TextInput } from "../../../components/fields/TextInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; import { AnastasisClientFrame } from "../index"; export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx new file mode 100644 index 000000000..df73a9214 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/Iban', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'iban' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx new file mode 100644 index 000000000..1e4353da6 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx @@ -0,0 +1,105 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + Send a wire transfer to the address + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx index 0f1c17495..adc83d6fe 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx @@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; export default { - title: 'Pages/backup/authMethods/Post', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/Post', component: TestedComponent, args: { order: 5, @@ -38,11 +38,11 @@ export default { const type: KnownAuthMethods = 'post' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -51,7 +51,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce }] }); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx index bfeaaa832..692421d74 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx @@ -1,13 +1,12 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { canonicalJson, encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; +import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { TextInput } from "../../../components/fields/TextInput"; import { AnastasisClientFrame } from ".."; +import { TextInput } from "../../../components/fields/TextInput"; +import { AuthMethodSetupProps } from "./index"; export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { const [fullName, setFullName] = useState(""); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx new file mode 100644 index 000000000..99451090b --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/post', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'post' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx new file mode 100644 index 000000000..7e3c45abe --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx @@ -0,0 +1,105 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + Wait for the answer + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx index 3ba4a84ca..0c3ee2b77 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx @@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; export default { - title: 'Pages/backup/authMethods/Question', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/Question', component: TestedComponent, args: { order: 5, @@ -38,11 +38,11 @@ export default { const type: KnownAuthMethods = 'question' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -51,7 +51,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce }] }); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx index 04fa00d59..780bfcb82 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx @@ -1,11 +1,10 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AuthMethodSetupProps } from "./index"; import { AnastasisClientFrame } from "../index"; import { TextInput } from "../../../components/fields/TextInput"; diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx new file mode 100644 index 000000000..a325b3843 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/question', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'question' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx new file mode 100644 index 000000000..ee1c0028f --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx @@ -0,0 +1,105 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + Answer the question please + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx index ae8297ef7..da2087ce1 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx @@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; export default { - title: 'Pages/backup/authMethods/Sms', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/Sms', component: TestedComponent, args: { order: 5, @@ -38,11 +38,11 @@ export default { const type: KnownAuthMethods = 'sms' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -51,7 +51,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce }] }); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx index 9e85af2b2..cd8782b0c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx @@ -1,12 +1,11 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "."; import { NumberInput } from "../../../components/fields/NumberInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; import { AnastasisClientFrame } from "../index"; export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx new file mode 100644 index 000000000..76e769303 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/sms', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'sms' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx new file mode 100644 index 000000000..ce7159bd0 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx @@ -0,0 +1,106 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type the code + below + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx index 4e46b600e..c0a52924c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx @@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; export default { - title: 'Pages/backup/authMethods/TOTP', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/TOTP', component: TestedComponent, args: { order: 5, @@ -38,10 +38,10 @@ export default { const type: KnownAuthMethods = 'totp' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -49,7 +49,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce remove: () => null }] }); -export const WithMoreExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx index fd0bd0224..a8ac499b2 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx @@ -1,11 +1,10 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useMemo, useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AuthMethodSetupProps } from "./index"; import { AnastasisClientFrame } from "../index"; import { TextInput } from "../../../components/fields/TextInput"; import { QR } from "../../../components/QR"; diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx new file mode 100644 index 000000000..a301931b2 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/totp', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'totp' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx new file mode 100644 index 000000000..30fc44f0e --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx @@ -0,0 +1,105 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + enter the totp solution + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx index 3c4c7bf39..52e897c60 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx @@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index'; import logoImage from '../../../assets/logo.jpeg' export default { - title: 'Pages/backup/authMethods/Video', + title: 'Pages/backup/AuthorizationMethod/AuthMethods/Video', component: TestedComponent, args: { order: 5, @@ -38,11 +38,11 @@ export default { const type: KnownAuthMethods = 'video' -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [] }); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, @@ -51,7 +51,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce }] }); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { +export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { configured: [{ challenge: 'qwe', type, diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx index 8be999b3f..22abe4a49 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ import { encodeCrock, stringToBytes @@ -6,7 +5,7 @@ import { import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { ImageInput } from "../../../components/fields/ImageInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AuthMethodSetupProps } from "./index"; import { AnastasisClientFrame } from "../index"; export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx new file mode 100644 index 000000000..5c4976b87 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx @@ -0,0 +1,56 @@ +/* + This file is part of GNU Taler + (C) 2021 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 <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/recovery/SolveChallenge/AuthMethods/video', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'video' + +export const WithoutFeedback = createExample(TestedComponent[type].solve, { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [{ + cost: 'USD:1', + instructions: 'does P equals NP?', + type: 'question', + uuid: 'uuid-1' + }], + policies: [], + }, + selected_challenge_uuid: 'uuid-1', +} as ReducerState, { + id: 'uuid-1', +}); + diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx new file mode 100644 index 000000000..79401028a --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx @@ -0,0 +1,105 @@ +import { ChallengeFeedbackStatus, ChallengeInfo } from "anastasis-core"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AsyncButton } from "../../../components/AsyncButton"; +import { TextInput } from "../../../components/fields/TextInput"; +import { useAnastasisContext } from "../../../context/anastasis"; +import { AnastasisClientFrame } from "../index"; +import { SolveOverviewFeedbackDisplay } from "../SolveScreen"; +import { AuthMethodSolveProps } from "./index"; + +export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { + const [answer, setAnswer] = useState(""); + + const reducer = useAnastasisContext(); + if (!reducer) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>no reducer in context</div> + </AnastasisClientFrame> + ); + } + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + </AnastasisClientFrame> + ); + } + + if (!reducer.currentReducerState.recovery_information) { + return ( + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery problem" + > + <div>no recovery information found</div> + </AnastasisClientFrame> + ); + } + if (!reducer.currentReducerState.selected_challenge_uuid) { + return ( + <AnastasisClientFrame hideNav title="Recovery problem"> + <div>invalid state</div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={() => reducer.back()}>Back</button> + </div> + </AnastasisClientFrame> + ); + } + + const chArr = reducer.currentReducerState.recovery_information.challenges; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; + const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; + const challenges: { + [uuid: string]: ChallengeInfo; + } = {}; + for (const ch of chArr) { + challenges[ch.uuid] = ch; + } + const selectedChallenge = challenges[selectedUuid]; + const feedback = challengeFeedback[selectedUuid] + + + async function onNext(): Promise<void> { + return reducer?.transition("solve_challenge", { answer }); + } + function onCancel(): void { + reducer?.back(); + } + + + const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded + || feedback?.state === ChallengeFeedbackStatus.Redirect + || feedback?.state === ChallengeFeedbackStatus.Unsupported + || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <SolveOverviewFeedbackDisplay feedback={feedback} /> + <p> + You are gonna be called to check your identity + </p> + <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> + + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> + {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton>} + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx index 7b0cce883..07f6ec206 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -1,22 +1,44 @@ +import { AuthMethod } from "anastasis-core"; import { h, VNode } from "preact"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; - -import { AuthMethodEmailSetup as EmailScreen } from "./AuthMethodEmailSetup"; -import { AuthMethodIbanSetup as IbanScreen } from "./AuthMethodIbanSetup"; -import { AuthMethodPostSetup as PostalScreen } from "./AuthMethodPostSetup"; -import { AuthMethodQuestionSetup as QuestionScreen } from "./AuthMethodQuestionSetup"; -import { AuthMethodSmsSetup as SmsScreen } from "./AuthMethodSmsSetup"; -import { AuthMethodTotpSetup as TotpScreen } from "./AuthMethodTotpSetup"; -import { AuthMethodVideoSetup as VideScreen } from "./AuthMethodVideoSetup"; import postalIcon from '../../../assets/icons/auth_method/postal.svg'; import questionIcon from '../../../assets/icons/auth_method/question.svg'; import smsIcon from '../../../assets/icons/auth_method/sms.svg'; import videoIcon from '../../../assets/icons/auth_method/video.svg'; +import { AuthMethodEmailSetup as EmailSetup } from "./AuthMethodEmailSetup"; +import { AuthMethodEmailSolve as EmailSolve } from "./AuthMethodEmailSolve"; +import { AuthMethodIbanSetup as IbanSetup } from "./AuthMethodIbanSetup"; +import { AuthMethodPostSetup as PostalSetup } from "./AuthMethodPostSetup"; +import { AuthMethodQuestionSetup as QuestionSetup } from "./AuthMethodQuestionSetup"; +import { AuthMethodSmsSetup as SmsSetup } from "./AuthMethodSmsSetup"; +import { AuthMethodTotpSetup as TotpSetup } from "./AuthMethodTotpSetup"; +import { AuthMethodVideoSetup as VideoSetup } from "./AuthMethodVideoSetup"; + +import { AuthMethodIbanSolve as IbanSolve } from "./AuthMethodIbanSolve"; +import { AuthMethodPostSolve as PostalSolve } from "./AuthMethodPostSolve"; +import { AuthMethodQuestionSolve as QuestionSolve } from "./AuthMethodQuestionSolve"; +import { AuthMethodSmsSolve as SmsSolve } from "./AuthMethodSmsSolve"; +import { AuthMethodTotpSolve as TotpSolve } from "./AuthMethodTotpSolve"; +import { AuthMethodVideoSolve as VideoSolve } from "./AuthMethodVideoSolve"; + + +export type AuthMethodWithRemove = AuthMethod & { remove: () => void } + +export interface AuthMethodSetupProps { + method: string; + addAuthMethod: (x: any) => void; + configured: AuthMethodWithRemove[]; + cancel: () => void; +} + +export interface AuthMethodSolveProps { + id: string; +} interface AuthMethodConfiguration { icon: VNode; label: string; - screen: (props: AuthMethodSetupProps) => VNode; + setup: (props: AuthMethodSetupProps) => VNode; + solve: (props: AuthMethodSolveProps) => VNode; skip?: boolean; } export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; @@ -29,41 +51,44 @@ export const authMethods: KnowMethodConfig = { question: { icon: <img src={questionIcon} />, label: "Question", - screen: QuestionScreen + setup: QuestionSetup, + solve: QuestionSolve, }, sms: { icon: <img src={smsIcon} />, label: "SMS", - screen: SmsScreen + setup: SmsSetup, + solve: SmsSolve, }, email: { icon: <i class="mdi mdi-email" />, label: "Email", - screen: EmailScreen - + setup: EmailSetup, + solve: EmailSolve, }, iban: { icon: <i class="mdi mdi-bank" />, label: "IBAN", - screen: IbanScreen - + setup: IbanSetup, + solve: IbanSolve, }, post: { icon: <img src={postalIcon} />, label: "Physical mail", - screen: PostalScreen - + setup: PostalSetup, + solve: PostalSolve, }, totp: { icon: <i class="mdi mdi-devices" />, label: "TOTP", - screen: TotpScreen - + setup: TotpSetup, + solve: TotpSolve, }, video: { icon: <img src={videoIcon} />, label: "Video", - screen: VideScreen, - skip: true, + setup: VideoSetup, + solve: VideoSolve, + skip: true, } }
\ No newline at end of file diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx index 07bc7c604..cd8d6c842 100644 --- a/packages/anastasis-webui/src/pages/home/index.tsx +++ b/packages/anastasis-webui/src/pages/home/index.tsx @@ -1,8 +1,6 @@ import { BackupStates, - RecoveryStates, - ReducerStateBackup, - ReducerStateRecovery + RecoveryStates } from "anastasis-core"; import { ComponentChildren, Fragment, |