diff options
| author | Sebastian <sebasjm@gmail.com> | 2021-11-08 09:56:06 -0300 | 
|---|---|---|
| committer | Sebastian <sebasjm@gmail.com> | 2021-11-08 09:56:06 -0300 | 
| commit | 6ef5fd21fc365d780da42170ce85042f874ed1dc (patch) | |
| tree | e9b86dc8949e1ad38d13f3b13814f3770de45675 /packages | |
| parent | 292d647aa917ecef7fee3f1ebeee0411b4c9a0d6 (diff) | |
some solve challenge examples, WIP
Diffstat (limited to 'packages')
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, | 
