some solve challenge examples, WIP

This commit is contained in:
Sebastian 2021-11-08 09:56:06 -03:00
parent 292d647aa9
commit 6ef5fd21fc
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
47 changed files with 1287 additions and 283 deletions

View File

@ -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,

View File

@ -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' },

View File

@ -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;

View File

@ -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`}>

View File

@ -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' },

View File

@ -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,

View File

@ -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,

View File

@ -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,
},

View File

@ -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' },

View File

@ -26,7 +26,7 @@ import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScr
export default {
title: 'Pages/recovery/FinishedScreen',
title: 'Pages/recovery/Finished',
args: {
order: 7,
},

View File

@ -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,
},

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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>
);
}
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 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;
const selectedChallenge = chArr.find(ch => ch.uuid === selectedUuid)
async function onNext(): Promise<void> {
return reducer?.transition("solve_challenge", { answer });
}
function onCancel(): void {
reducer?.back();
}
const SolveDialog = !selectedChallenge || !authMethods[selectedChallenge.type as KnownAuthMethods] ?
SolveNotImplemented :
authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? SolveNotImplemented
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>
);
}
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>
);
}
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} />
}

View File

@ -24,7 +24,7 @@ import { StartScreen as TestedComponent } from './StartScreen';
export default {
title: 'Pages/StartScreen',
title: 'Pages/Start',
component: TestedComponent,
args: {
order: 1,

View File

@ -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,

View File

@ -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,

View File

@ -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,}))$/

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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 {

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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("");

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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";

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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 {

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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";

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,

View File

@ -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 {

View File

@ -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',
});

View File

@ -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>
);
}

View File

@ -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,
}
}

View File

@ -1,8 +1,6 @@
import {
BackupStates,
RecoveryStates,
ReducerStateBackup,
ReducerStateRecovery
RecoveryStates
} from "anastasis-core";
import {
ComponentChildren, Fragment,