some solve challenge examples, WIP
This commit is contained in:
parent
292d647aa9
commit
6ef5fd21fc
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,7 +25,7 @@ import { AddingProviderScreen as TestedComponent } from './AddingProviderScreen'
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AddingProviderScreen',
|
title: 'Pages/backup/AuthorizationMethod/AddingProvider',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 4,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,10 +25,10 @@ import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen'
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/AttributeEntryScreen',
|
title: 'Pages/PersonalInformation',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 3,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: 'onUpdate' },
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,10 +25,10 @@ import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationE
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/AuthenticationEditorScreen',
|
title: 'Pages/backup/AuthorizationMethod',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 4,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
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, {
|
export const OneAuthMethodConfigured = createExample(TestedComponent, {
|
||||||
...reducerStatesExample.authEditing,
|
...reducerStatesExample.authEditing,
|
||||||
authentication_methods: [{
|
authentication_methods: [{
|
||||||
@ -86,8 +85,3 @@ export const NoAuthMethodProvided = createExample(TestedComponent, {
|
|||||||
authentication_providers: {},
|
authentication_providers: {},
|
||||||
authentication_methods: []
|
authentication_methods: []
|
||||||
} as ReducerState);
|
} as ReducerState);
|
||||||
|
|
||||||
// type: string;
|
|
||||||
// instructions: string;
|
|
||||||
// challenge: string;
|
|
||||||
// mime_type?: string;
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import { AuthMethod } from "anastasis-core";
|
import { AuthMethod } from "anastasis-core";
|
||||||
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
import { ComponentChildren, Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { TextInput } from "../../components/fields/TextInput";
|
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
import { useAnastasisContext } from "../../context/anastasis";
|
||||||
import { authMethods, KnownAuthMethods } from "./authMethod";
|
import { authMethods, AuthMethodSetupProps, AuthMethodWithRemove, KnownAuthMethods } from "./authMethod";
|
||||||
import { AnastasisClientFrame } from "./index";
|
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 {
|
export function AuthenticationEditorScreen(): VNode {
|
||||||
const [noProvidersAck, setNoProvidersAck] = useState(false)
|
const [noProvidersAck, setNoProvidersAck] = useState(false)
|
||||||
const [selectedMethod, setSelectedMethod] = useState<KnownAuthMethods | undefined>(undefined);
|
const [selectedMethod, setSelectedMethod] = useState<KnownAuthMethods | undefined>(undefined);
|
||||||
const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined)
|
// const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined)
|
||||||
|
|
||||||
const reducer = useAnastasisContext()
|
const reducer = useAnastasisContext()
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
@ -63,7 +61,7 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
setSelectedMethod(undefined);
|
setSelectedMethod(undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const AuthSetup = authMethods[selectedMethod].screen ?? AuthMethodNotImplemented;
|
const AuthSetup = authMethods[selectedMethod].setup ?? AuthMethodNotImplemented;
|
||||||
return (<Fragment>
|
return (<Fragment>
|
||||||
<AuthSetup
|
<AuthSetup
|
||||||
cancel={cancel}
|
cancel={cancel}
|
||||||
@ -88,10 +86,6 @@ export function AuthenticationEditorScreen(): VNode {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addingProvider !== undefined) {
|
|
||||||
return <div />
|
|
||||||
}
|
|
||||||
|
|
||||||
function MethodButton(props: { method: KnownAuthMethods }): VNode {
|
function MethodButton(props: { method: KnownAuthMethods }): VNode {
|
||||||
if (authMethods[props.method].skip) return <div />
|
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 {
|
function AuthMethodNotImplemented(props: AuthMethodSetupProps): VNode {
|
||||||
return (
|
return (
|
||||||
<AnastasisClientFrame hideNav title={`Add ${props.method} authentication`}>
|
<AnastasisClientFrame hideNav title={`Add ${props.method} authentication`}>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,10 +25,10 @@ import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen'
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/FinishedScreen',
|
title: 'Pages/backup/Finished',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 9,
|
order: 8,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: 'onUpdate' },
|
||||||
|
@ -24,7 +24,7 @@ import { createExample, reducerStatesExample } from "../../utils";
|
|||||||
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen";
|
import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: "Pages/recovery/ChallengeOverviewScreen",
|
title: "Pages/recovery/SolveChallenge/Overview",
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
|
@ -24,7 +24,7 @@ import { ChallengePayingScreen as TestedComponent } from './ChallengePayingScree
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/__ChallengePayingScreen',
|
title: 'Pages/recovery/__ChallengePaying',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 10,
|
order: 10,
|
||||||
|
@ -26,7 +26,7 @@ import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/ReviewPoliciesScreen/EditPoliciesScreen',
|
title: 'Pages/backup/ReviewPolicies/EditPolicies',
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,10 +25,10 @@ import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen'
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/PoliciesPayingScreen',
|
title: 'Pages/backup/__PoliciesPaying',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 8,
|
order: 9,
|
||||||
},
|
},
|
||||||
argTypes: {
|
argTypes: {
|
||||||
onUpdate: { action: 'onUpdate' },
|
onUpdate: { action: 'onUpdate' },
|
||||||
|
@ -26,7 +26,7 @@ import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScr
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/FinishedScreen',
|
title: 'Pages/recovery/Finished',
|
||||||
args: {
|
args: {
|
||||||
order: 7,
|
order: 7,
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,7 +25,7 @@ import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen'
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/ReviewPoliciesScreen',
|
title: 'Pages/backup/ReviewPolicies',
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -26,7 +25,7 @@ import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/SecretEditorScreen',
|
title: 'Pages/backup/SecretInput',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 7,
|
order: 7,
|
||||||
|
@ -25,7 +25,7 @@ import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScree
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SecretSelectionScreen',
|
title: 'Pages/recovery/SecretSelection',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 4,
|
order: 4,
|
||||||
|
@ -25,7 +25,7 @@ import { SolveScreen as TestedComponent } from './SolveScreen';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/recovery/SolveScreen',
|
title: 'Pages/recovery/SolveChallenge/Solve',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 6,
|
order: 6,
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import { AnastasisClientFrame } from ".";
|
import { AnastasisClientFrame } from ".";
|
||||||
import {
|
import {
|
||||||
ChallengeFeedback,
|
ChallengeFeedback,
|
||||||
ChallengeFeedbackStatus,
|
ChallengeFeedbackStatus
|
||||||
ChallengeInfo,
|
|
||||||
} from "../../../../anastasis-core/lib";
|
} from "../../../../anastasis-core/lib";
|
||||||
import { AsyncButton } from "../../components/AsyncButton";
|
|
||||||
import { TextInput } from "../../components/fields/TextInput";
|
|
||||||
import { Notifications } from "../../components/Notifications";
|
import { Notifications } from "../../components/Notifications";
|
||||||
import { useAnastasisContext } from "../../context/anastasis";
|
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;
|
const { feedback } = props;
|
||||||
if (!feedback) {
|
if (!feedback) {
|
||||||
return <div />;
|
return <div />;
|
||||||
@ -80,7 +77,6 @@ function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }):
|
|||||||
|
|
||||||
export function SolveScreen(): VNode {
|
export function SolveScreen(): VNode {
|
||||||
const reducer = useAnastasisContext();
|
const reducer = useAnastasisContext();
|
||||||
const [answer, setAnswer] = useState("");
|
|
||||||
|
|
||||||
if (!reducer) {
|
if (!reducer) {
|
||||||
return (
|
return (
|
||||||
@ -120,162 +116,30 @@ export function SolveScreen(): VNode {
|
|||||||
</AnastasisClientFrame>
|
</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 chArr = reducer.currentReducerState.recovery_information.challenges;
|
||||||
const challengeFeedback =
|
|
||||||
reducer.currentReducerState.challenge_feedback ?? {};
|
|
||||||
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
|
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
|
||||||
const challenges: {
|
const selectedChallenge = chArr.find(ch => ch.uuid === selectedUuid)
|
||||||
[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> {
|
const SolveDialog = !selectedChallenge || !authMethods[selectedChallenge.type as KnownAuthMethods] ?
|
||||||
return reducer?.transition("solve_challenge", { answer });
|
SolveNotImplemented :
|
||||||
}
|
authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? SolveNotImplemented
|
||||||
function onCancel(): void {
|
|
||||||
reducer?.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedback = challengeFeedback[selectedUuid]
|
return <SolveDialog id={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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import { StartScreen as TestedComponent } from './StartScreen';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/StartScreen',
|
title: 'Pages/Start',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 1,
|
order: 1,
|
||||||
|
@ -25,7 +25,7 @@ import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/__TruthsPayingScreen',
|
title: 'Pages/backup/__TruthsPaying',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 10,
|
order: 10,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -25,7 +24,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/email',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/email',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,11 +37,11 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'email'
|
const type: KnownAuthMethods = 'email'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
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: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
stringToBytes
|
stringToBytes
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
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 { 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,}))$/
|
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,}))$/
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
/*
|
/*
|
||||||
This file is part of GNU Taler
|
This file is part of GNU Taler
|
||||||
(C) 2021 Taler Systems S.A.
|
(C) 2021 Taler Systems S.A.
|
||||||
@ -25,7 +24,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/IBAN',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/IBAN',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,11 +37,11 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'iban'
|
const type: KnownAuthMethods = 'iban'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
@ -50,7 +49,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce
|
|||||||
remove: () => null
|
remove: () => null
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
canonicalJson,
|
canonicalJson,
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
@ -6,8 +5,8 @@ import {
|
|||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
|
import { AuthMethodSetupProps } from ".";
|
||||||
import { TextInput } from "../../../components/fields/TextInput";
|
import { TextInput } from "../../../components/fields/TextInput";
|
||||||
import { AuthMethodSetupProps } from "../AuthenticationEditorScreen";
|
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
|
|
||||||
export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode {
|
export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode {
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/Post',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Post',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,11 +38,11 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'post'
|
const type: KnownAuthMethods = 'post'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
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: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
canonicalJson, encodeCrock,
|
canonicalJson, encodeCrock,
|
||||||
stringToBytes
|
stringToBytes
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "../AuthenticationEditorScreen";
|
|
||||||
import { TextInput } from "../../../components/fields/TextInput";
|
|
||||||
import { AnastasisClientFrame } from "..";
|
import { AnastasisClientFrame } from "..";
|
||||||
|
import { TextInput } from "../../../components/fields/TextInput";
|
||||||
|
import { AuthMethodSetupProps } from "./index";
|
||||||
|
|
||||||
export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode {
|
export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode {
|
||||||
const [fullName, setFullName] = useState("");
|
const [fullName, setFullName] = useState("");
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/Question',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Question',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,11 +38,11 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'question'
|
const type: KnownAuthMethods = 'question'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
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: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
stringToBytes
|
stringToBytes
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "../AuthenticationEditorScreen";
|
import { AuthMethodSetupProps } from "./index";
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
import { TextInput } from "../../../components/fields/TextInput";
|
import { TextInput } from "../../../components/fields/TextInput";
|
||||||
|
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/Sms',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Sms',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,11 +38,11 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'sms'
|
const type: KnownAuthMethods = 'sms'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
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: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
stringToBytes
|
stringToBytes
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { Fragment, h, VNode } from "preact";
|
import { Fragment, h, VNode } from "preact";
|
||||||
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
import { useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||||
|
import { AuthMethodSetupProps } from ".";
|
||||||
import { NumberInput } from "../../../components/fields/NumberInput";
|
import { NumberInput } from "../../../components/fields/NumberInput";
|
||||||
import { AuthMethodSetupProps } from "../AuthenticationEditorScreen";
|
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
|
|
||||||
export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode {
|
export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode {
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/TOTP',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/TOTP',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,10 +38,10 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'totp'
|
const type: KnownAuthMethods = 'totp'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
@ -49,7 +49,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce
|
|||||||
remove: () => null
|
remove: () => null
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
export const WithMoreExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithMoreExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
stringToBytes
|
stringToBytes
|
||||||
} from "@gnu-taler/taler-util";
|
} from "@gnu-taler/taler-util";
|
||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useMemo, useState } from "preact/hooks";
|
import { useMemo, useState } from "preact/hooks";
|
||||||
import { AuthMethodSetupProps } from "../AuthenticationEditorScreen";
|
import { AuthMethodSetupProps } from "./index";
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
import { TextInput } from "../../../components/fields/TextInput";
|
import { TextInput } from "../../../components/fields/TextInput";
|
||||||
import { QR } from "../../../components/QR";
|
import { QR } from "../../../components/QR";
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -25,7 +25,7 @@ import { authMethods as TestedComponent, KnownAuthMethods } from './index';
|
|||||||
import logoImage from '../../../assets/logo.jpeg'
|
import logoImage from '../../../assets/logo.jpeg'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Pages/backup/authMethods/Video',
|
title: 'Pages/backup/AuthorizationMethod/AuthMethods/Video',
|
||||||
component: TestedComponent,
|
component: TestedComponent,
|
||||||
args: {
|
args: {
|
||||||
order: 5,
|
order: 5,
|
||||||
@ -38,11 +38,11 @@ export default {
|
|||||||
|
|
||||||
const type: KnownAuthMethods = 'video'
|
const type: KnownAuthMethods = 'video'
|
||||||
|
|
||||||
export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: []
|
configured: []
|
||||||
});
|
});
|
||||||
|
|
||||||
export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, {
|
export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, {
|
||||||
configured: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
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: [{
|
configured: [{
|
||||||
challenge: 'qwe',
|
challenge: 'qwe',
|
||||||
type,
|
type,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/camelcase */
|
|
||||||
import {
|
import {
|
||||||
encodeCrock,
|
encodeCrock,
|
||||||
stringToBytes
|
stringToBytes
|
||||||
@ -6,7 +5,7 @@ import {
|
|||||||
import { h, VNode } from "preact";
|
import { h, VNode } from "preact";
|
||||||
import { useState } from "preact/hooks";
|
import { useState } from "preact/hooks";
|
||||||
import { ImageInput } from "../../../components/fields/ImageInput";
|
import { ImageInput } from "../../../components/fields/ImageInput";
|
||||||
import { AuthMethodSetupProps } from "../AuthenticationEditorScreen";
|
import { AuthMethodSetupProps } from "./index";
|
||||||
import { AnastasisClientFrame } from "../index";
|
import { AnastasisClientFrame } from "../index";
|
||||||
|
|
||||||
export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode {
|
export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode {
|
||||||
|
@ -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',
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
@ -1,22 +1,44 @@
|
|||||||
|
import { AuthMethod } from "anastasis-core";
|
||||||
import { h, VNode } from "preact";
|
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 postalIcon from '../../../assets/icons/auth_method/postal.svg';
|
||||||
import questionIcon from '../../../assets/icons/auth_method/question.svg';
|
import questionIcon from '../../../assets/icons/auth_method/question.svg';
|
||||||
import smsIcon from '../../../assets/icons/auth_method/sms.svg';
|
import smsIcon from '../../../assets/icons/auth_method/sms.svg';
|
||||||
import videoIcon from '../../../assets/icons/auth_method/video.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 {
|
interface AuthMethodConfiguration {
|
||||||
icon: VNode;
|
icon: VNode;
|
||||||
label: string;
|
label: string;
|
||||||
screen: (props: AuthMethodSetupProps) => VNode;
|
setup: (props: AuthMethodSetupProps) => VNode;
|
||||||
|
solve: (props: AuthMethodSolveProps) => VNode;
|
||||||
skip?: boolean;
|
skip?: boolean;
|
||||||
}
|
}
|
||||||
export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban";
|
export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban";
|
||||||
@ -29,41 +51,44 @@ export const authMethods: KnowMethodConfig = {
|
|||||||
question: {
|
question: {
|
||||||
icon: <img src={questionIcon} />,
|
icon: <img src={questionIcon} />,
|
||||||
label: "Question",
|
label: "Question",
|
||||||
screen: QuestionScreen
|
setup: QuestionSetup,
|
||||||
|
solve: QuestionSolve,
|
||||||
},
|
},
|
||||||
sms: {
|
sms: {
|
||||||
icon: <img src={smsIcon} />,
|
icon: <img src={smsIcon} />,
|
||||||
label: "SMS",
|
label: "SMS",
|
||||||
screen: SmsScreen
|
setup: SmsSetup,
|
||||||
|
solve: SmsSolve,
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
icon: <i class="mdi mdi-email" />,
|
icon: <i class="mdi mdi-email" />,
|
||||||
label: "Email",
|
label: "Email",
|
||||||
screen: EmailScreen
|
setup: EmailSetup,
|
||||||
|
solve: EmailSolve,
|
||||||
},
|
},
|
||||||
iban: {
|
iban: {
|
||||||
icon: <i class="mdi mdi-bank" />,
|
icon: <i class="mdi mdi-bank" />,
|
||||||
label: "IBAN",
|
label: "IBAN",
|
||||||
screen: IbanScreen
|
setup: IbanSetup,
|
||||||
|
solve: IbanSolve,
|
||||||
},
|
},
|
||||||
post: {
|
post: {
|
||||||
icon: <img src={postalIcon} />,
|
icon: <img src={postalIcon} />,
|
||||||
label: "Physical mail",
|
label: "Physical mail",
|
||||||
screen: PostalScreen
|
setup: PostalSetup,
|
||||||
|
solve: PostalSolve,
|
||||||
},
|
},
|
||||||
totp: {
|
totp: {
|
||||||
icon: <i class="mdi mdi-devices" />,
|
icon: <i class="mdi mdi-devices" />,
|
||||||
label: "TOTP",
|
label: "TOTP",
|
||||||
screen: TotpScreen
|
setup: TotpSetup,
|
||||||
|
solve: TotpSolve,
|
||||||
},
|
},
|
||||||
video: {
|
video: {
|
||||||
icon: <img src={videoIcon} />,
|
icon: <img src={videoIcon} />,
|
||||||
label: "Video",
|
label: "Video",
|
||||||
screen: VideScreen,
|
setup: VideoSetup,
|
||||||
skip: true,
|
solve: VideoSolve,
|
||||||
|
skip: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,8 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BackupStates,
|
BackupStates,
|
||||||
RecoveryStates,
|
RecoveryStates
|
||||||
ReducerStateBackup,
|
|
||||||
ReducerStateRecovery
|
|
||||||
} from "anastasis-core";
|
} from "anastasis-core";
|
||||||
import {
|
import {
|
||||||
ComponentChildren, Fragment,
|
ComponentChildren, Fragment,
|
||||||
|
Loading…
Reference in New Issue
Block a user