diff options
Diffstat (limited to 'packages/anastasis-webui/src/pages/home/authMethod')
29 files changed, 1239 insertions, 71 deletions
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 |