anastasis-webui: show feedback in solution screen

This commit is contained in:
Florian Dold 2021-11-03 18:56:19 +01:00
parent fefdb0d7ad
commit 9fb6536fbc
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
2 changed files with 135 additions and 46 deletions

View File

@ -5,7 +5,7 @@ import { useAnastasisContext } from "../../context/anastasis";
import { AnastasisClientFrame } from "./index";
import { authMethods, KnownAuthMethods } from "./authMethod";
function FeedbackDisplay(props: { feedback?: ChallengeFeedback }) {
function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) {
const { feedback } = props;
if (!feedback) {
return null;
@ -130,7 +130,7 @@ export function ChallengeOverviewScreen(): VNode {
<span class="icon">{method?.icon}</span>
<span>{info.instructions}</span>
</div>
<FeedbackDisplay feedback={info.feedback} />
<OverviewFeedbackDisplay feedback={info.feedback} />
</div>
<div>
{method && info.feedback?.state !== "solved" ? (

View File

@ -1,38 +1,89 @@
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { AnastasisClientFrame } from ".";
import { ChallengeFeedback, ChallengeInfo } from "../../../../anastasis-core/lib";
import {
ChallengeFeedback,
ChallengeFeedbackStatus,
ChallengeInfo,
} from "../../../../anastasis-core/lib";
import { TextInput } from "../../components/fields/TextInput";
import { useAnastasisContext } from "../../context/anastasis";
function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) {
const { feedback } = props;
if (!feedback) {
return null;
}
switch (feedback.state) {
case ChallengeFeedbackStatus.Message:
return (
<div>
<p>{feedback.message}</p>
</div>
);
case ChallengeFeedbackStatus.Pending:
case ChallengeFeedbackStatus.AuthIban:
return null;
case ChallengeFeedbackStatus.RateLimitExceeded:
return <div>Rate limit exceeded.</div>;
case ChallengeFeedbackStatus.Redirect:
return <div>Redirect (FIXME: not supported)</div>;
case ChallengeFeedbackStatus.Unsupported:
return <div>Challenge not supported by client.</div>;
case ChallengeFeedbackStatus.TruthUnknown:
return <div>Truth unknown</div>;
default:
return (
<div>
<pre>{JSON.stringify(feedback)}</pre>
</div>
);
}
}
export function SolveScreen(): VNode {
const reducer = useAnastasisContext()
const reducer = useAnastasisContext();
const [answer, setAnswer] = useState("");
if (!reducer) {
return <AnastasisClientFrame hideNav title="Recovery problem">
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">
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">
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">
return (
<AnastasisClientFrame hideNav title="Recovery problem">
<div>invalid state</div>
</AnastasisClientFrame>
);
}
const chArr = reducer.currentReducerState.recovery_information.challenges;
const challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {};
const challengeFeedback =
reducer.currentReducerState.challenge_feedback ?? {};
const selectedUuid = reducer.currentReducerState.selected_challenge_uuid;
const challenges: {
[uuid: string]: ChallengeInfo;
@ -47,31 +98,44 @@ export function SolveScreen(): VNode {
email: SolveEmailEntry,
post: SolvePostEntry,
};
const SolveDialog = selectedChallenge === undefined ? SolveUndefinedEntry : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
const SolveDialog =
selectedChallenge === undefined
? SolveUndefinedEntry
: dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry;
function onNext(): void {
reducer?.transition("solve_challenge", { answer })
reducer?.transition("solve_challenge", { answer });
}
function onCancel(): void {
reducer?.back()
reducer?.back();
}
return (
<AnastasisClientFrame
hideNav
title="Recovery: Solve challenge"
>
<AnastasisClientFrame hideNav title="Recovery: Solve challenge">
<SolveOverviewFeedbackDisplay
feedback={challengeFeedback[selectedUuid]}
/>
<SolveDialog
id={selectedUuid}
answer={answer}
setAnswer={setAnswer}
challenge={selectedChallenge}
feedback={challengeFeedback[selectedUuid]} />
feedback={challengeFeedback[selectedUuid]}
/>
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
<button class="button" onClick={onCancel}>Cancel</button>
<button class="button is-info" onClick={onNext} >Confirm</button>
<div
style={{
marginTop: "2em",
display: "flex",
justifyContent: "space-between",
}}
>
<button class="button" onClick={onCancel}>
Cancel
</button>
<button class="button is-info" onClick={onNext}>
Confirm
</button>
</div>
</AnastasisClientFrame>
);
@ -85,38 +149,61 @@ export interface SolveEntryProps {
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>
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 {
function SolveQuestionEntry({
challenge,
answer,
setAnswer,
}: SolveEntryProps): VNode {
return (
<Fragment>
<p>Type the answer to the following question:</p>
<pre>
{challenge.instructions}
</pre>
<pre>{challenge.instructions}</pre>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
</Fragment>
);
}
function SolvePostEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode {
function SolvePostEntry({
challenge,
answer,
setAnswer,
}: SolveEntryProps): VNode {
return (
<Fragment>
<p>instruction for post type challenge "<b>{challenge.instructions}</b>"</p>
<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 {
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>
<p>
An email has been sent to "<b>{challenge.instructions}</b>". Type the
code below
</p>
<TextInput label="Answer" grabFocus bind={[answer, setAnswer]} />
</Fragment>
);
@ -126,7 +213,8 @@ 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.
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}
@ -136,9 +224,10 @@ function SolveUnsupportedEntry(props: SolveEntryProps): VNode {
}
function SolveUndefinedEntry(props: SolveEntryProps): VNode {
return (
<Fragment >
<Fragment>
<p>
There is no challenge information for id <b>"{props.id}"</b>. Try resetting the recovery session.
There is no challenge information for id <b>"{props.id}"</b>. Try
resetting the recovery session.
</p>
</Fragment>
);