diff options
5 files changed, 265 insertions, 236 deletions
diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 859dd083b..3b73aa002 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -96,6 +96,7 @@ const { fetch } = fetchPonyfill({}); export * from "./reducer-types.js"; export * as validators from "./validators.js"; +export * from "./challenge-feedback-types.js"; const logger = new Logger("anastasis-core:index.ts"); diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index a89b5640c..48115c798 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -16,218 +16,201 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { RecoveryStates, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { ChallengeOverviewScreen as TestedComponent } from './ChallengeOverviewScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { RecoveryStates, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen"; export default { - title: 'Pages/recovery/ChallengeOverviewScreen', + title: "Pages/recovery/ChallengeOverviewScreen", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const OneUnsolvedPolicy = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '1' }]], - challenges: [{ - cost: 'USD:1', - instructions: 'just go for it', - type: 'question', - uuid: '1', - }] + policies: [[{ uuid: "1" }]], + challenges: [ + { + cost: "USD:1", + instructions: "just go for it", + type: "question", + uuid: "1", + }, + ], }, } as ReducerState); export const SomePoliciesOneSolved = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]], - challenges: [{ - cost: 'USD:1', - instructions: 'this question cost 1 USD', - type: 'question', - uuid: '1', - }, { - cost: 'USD:0', - instructions: 'answering this question is free', - type: 'question', - uuid: '2', - }, { - cost: 'USD:1', - instructions: 'this question is already answered', - type: 'question', - uuid: 'uuid-3', - }] + policies: [[{ uuid: "1" }, { uuid: "2" }], [{ uuid: "uuid-3" }]], + challenges: [ + { + cost: "USD:1", + instructions: "this question cost 1 USD", + type: "question", + uuid: "1", + }, + { + cost: "USD:0", + instructions: "answering this question is free", + type: "question", + uuid: "2", + }, + { + cost: "USD:1", + instructions: "this question is already answered", + type: "question", + uuid: "uuid-3", + }, + ], }, challenge_feedback: { - 'uuid-3': { - state: 'solved' - } + "uuid-3": { + state: "solved", + }, }, } as ReducerState); export const OneBadConfiguredPolicy = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '1' }, { uuid: '2' }]], - challenges: [{ - cost: 'USD:1', - instructions: 'this policy has a missing uuid (the other auth method)', - type: 'totp', - uuid: '1', - }], + policies: [[{ uuid: "1" }, { uuid: "2" }]], + challenges: [ + { + cost: "USD:1", + instructions: "this policy has a missing uuid (the other auth method)", + type: "totp", + uuid: "1", + }, + ], }, } as ReducerState); export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[ - { uuid: '1' }, - { uuid: '2' }, - { uuid: '3' }, - { uuid: '4' }, - { uuid: '5' }, - { uuid: '6' }, - { uuid: '7' }, - { uuid: '8' }, - ]], - challenges: [{ - cost: 'USD:1', - instructions: 'Does P equals NP?', - type: 'question', - uuid: '1', - },{ - cost: 'USD:1', - instructions: 'SMS to 555-555', - type: 'sms', - uuid: '2', - },{ - cost: 'USD:1', - instructions: 'Email to qwe@asd.com', - type: 'email', - uuid: '3', - },{ - cost: 'USD:1', - instructions: 'Enter 8 digits code for "Anastasis"', - type: 'totp', - uuid: '4', - },{// - cost: 'USD:0', - instructions: 'Wire transfer from ASDXCVQWE123123 with holder Florian', - type: 'iban', - uuid: '5', - },{ - cost: 'USD:1', - instructions: 'Join a video call', - type: 'video',//Enter 8 digits code for "Anastasis" - uuid: '7', - },{ - },{ - cost: 'USD:1', - instructions: 'Letter to address in postal code DE123123', - type: 'post',//Enter 8 digits code for "Anastasis" - uuid: '8', - },{ - cost: 'USD:1', - instructions: 'instruction for an unknown type of challenge', - type: 'new-type-of-challenge', - uuid: '6', - }], + policies: [ + [ + { uuid: "1" }, + { uuid: "2" }, + { uuid: "3" }, + { uuid: "4" }, + { uuid: "5" }, + { uuid: "6" }, + { uuid: "7" }, + { uuid: "8" }, + ], + ], + challenges: [ + { + cost: "USD:1", + instructions: "Does P equals NP?", + type: "question", + uuid: "1", + }, + { + cost: "USD:1", + instructions: "SMS to 555-555", + type: "sms", + uuid: "2", + }, + { + cost: "USD:1", + instructions: "Email to qwe@asd.com", + type: "email", + uuid: "3", + }, + { + cost: "USD:1", + instructions: 'Enter 8 digits code for "Anastasis"', + type: "totp", + uuid: "4", + }, + { + // + cost: "USD:0", + instructions: "Wire transfer from ASDXCVQWE123123 with holder Florian", + type: "iban", + uuid: "5", + }, + { + cost: "USD:1", + instructions: "Join a video call", + type: "video", //Enter 8 digits code for "Anastasis" + uuid: "7", + }, + {}, + { + cost: "USD:1", + instructions: "Letter to address in postal code DE123123", + type: "post", //Enter 8 digits code for "Anastasis" + uuid: "8", + }, + { + cost: "USD:1", + instructions: "instruction for an unknown type of challenge", + type: "new-type-of-challenge", + uuid: "6", + }, + ], }, } as ReducerState); - -export const OnePolicyWithAllTheChallengesInDifferentState = createExample(TestedComponent, { - ...reducerStatesExample.challengeSelecting, - recovery_information: { - policies: [[ - { uuid: '1' }, - { uuid: '2' }, - { uuid: '3' }, - { uuid: '4' }, - { uuid: '5' }, - { uuid: '6' }, - { uuid: '7' }, - { uuid: '8' }, - { uuid: '9' }, - { uuid: '10' }, - ]], - challenges: [{ - cost: 'USD:1', - instructions: 'in state "solved"', - type: 'question', - uuid: '1', - },{ - cost: 'USD:1', - instructions: 'in state "hint"', - type: 'question', - uuid: '2', - },{ - cost: 'USD:1', - instructions: 'in state "details"', - type: 'question', - uuid: '3', - },{ - cost: 'USD:1', - instructions: 'in state "body"', - type: 'question', - uuid: '4', - },{ - cost: 'USD:1', - instructions: 'in state "redirect"', - type: 'question', - uuid: '5', - },{ - cost: 'USD:1', - instructions: 'in state "server-failure"', - type: 'question', - uuid: '6', - },{ - cost: 'USD:1', - instructions: 'in state "truth-unknown"', - type: 'question', - uuid: '7', - },{ - cost: 'USD:1', - instructions: 'in state "rate-limit-exceeded"', - type: 'question', - uuid: '8', - },{ - cost: 'USD:1', - instructions: 'in state "authentication-timeout"', - type: 'question', - uuid: '9', - },{ - cost: 'USD:1', - instructions: 'in state "external-instructions"', - type: 'question', - uuid: '10', - }], - }, - challenge_feedback: { - 1: { state: 'solved' }, - 2: { state: 'hint' }, - 3: { state: 'details' }, - 4: { state: 'body' }, - 5: { state: 'redirect' }, - 6: { state: 'server-failure' }, - 7: { state: 'truth-unknown' }, - 8: { state: 'rate-limit-exceeded' }, - 9: { state: 'authentication-timeout' }, - 10: { state: 'external-instructions' }, - } -} as ReducerState); -export const NoPolicies = createExample(TestedComponent, reducerStatesExample.challengeSelecting); +export const OnePolicyWithAllTheChallengesInDifferentState = createExample( + TestedComponent, + { + ...reducerStatesExample.challengeSelecting, + recovery_state: RecoveryStates.ChallengeSelecting, + recovery_information: { + policies: [ + [ + { uuid: "1" }, + { uuid: "2" }, + { uuid: "3" }, + { uuid: "4" }, + { uuid: "5" }, + { uuid: "6" }, + { uuid: "7" }, + { uuid: "8" }, + { uuid: "9" }, + { uuid: "10" }, + ], + ], + challenges: [ + { + cost: "USD:1", + instructions: 'in state "solved"', + type: "question", + uuid: "1", + }, + { + cost: "USD:1", + instructions: 'in state "message"', + type: "question", + uuid: "2", + }, + ], + }, + challenge_feedback: { + 1: { state: "solved" }, + 2: { state: "message", message: "Security question was not solved correctly" }, + // FIXME: add missing feedback states here! + }, + } as ReducerState, +); +export const NoPolicies = createExample( + TestedComponent, + reducerStatesExample.challengeSelecting, +); diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index 7b9b060ce..c63f19eb6 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -1,23 +1,29 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { ChallengeFeedback } from "anastasis-core"; +import { ChallengeFeedback, ChallengeFeedbackStatus } from "anastasis-core"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; import { authMethods, KnownAuthMethods } from "./authMethod"; export function ChallengeOverviewScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return <div>invalid state</div>; } - const policies = reducer.currentReducerState.recovery_information?.policies ?? []; - const knownChallengesArray = reducer.currentReducerState.recovery_information?.challenges ?? []; - const challengeFeedback = reducer.currentReducerState?.challenge_feedback ?? {}; + const policies = + reducer.currentReducerState.recovery_information?.policies ?? []; + const knownChallengesArray = + reducer.currentReducerState.recovery_information?.challenges ?? []; + const challengeFeedback = + reducer.currentReducerState?.challenge_feedback ?? {}; const knownChallengesMap: { [uuid: string]: { @@ -32,51 +38,80 @@ export function ChallengeOverviewScreen(): VNode { type: ch.type, cost: ch.cost, instructions: ch.instructions, - feedback: challengeFeedback[ch.uuid] + feedback: challengeFeedback[ch.uuid], }; } - const policiesWithInfo = policies.map(row => { - let isPolicySolved = true - const challenges = row.map(({ uuid }) => { - const info = knownChallengesMap[uuid]; - const isChallengeSolved = info?.feedback?.state === 'solved' - isPolicySolved = isPolicySolved && isChallengeSolved - return { info, uuid, isChallengeSolved } - }).filter(ch => ch.info !== undefined) + const policiesWithInfo = policies.map((row) => { + let isPolicySolved = true; + const challenges = row + .map(({ uuid }) => { + const info = knownChallengesMap[uuid]; + const isChallengeSolved = info?.feedback?.state === "solved"; + isPolicySolved = isPolicySolved && isChallengeSolved; + return { info, uuid, isChallengeSolved }; + }) + .filter((ch) => ch.info !== undefined); - return { isPolicySolved, challenges } - }) + return { isPolicySolved, challenges }; + }); - const atLeastThereIsOnePolicySolved = policiesWithInfo.find(p => p.isPolicySolved) !== undefined + const atLeastThereIsOnePolicySolved = + policiesWithInfo.find((p) => p.isPolicySolved) !== undefined; - const errors = !atLeastThereIsOnePolicySolved ? "Solve one policy before proceeding" : undefined; + const errors = !atLeastThereIsOnePolicySolved + ? "Solve one policy before proceeding" + : undefined; return ( <AnastasisClientFrame hideNext={errors} title="Recovery: Solve challenges"> - {!policies.length ? <p class="block"> - No policies found, try with another version of the secret - </p> : (policies.length === 1 ? <p class="block"> - One policy found for this secret. You need to solve all the challenges in order to recover your secret. - </p> : <p class="block"> - We have found {policies.length} polices. You need to solve all the challenges from one policy in order - to recover your secret. - </p>)} + {!policies.length ? ( + <p class="block"> + No policies found, try with another version of the secret + </p> + ) : policies.length === 1 ? ( + <p class="block"> + One policy found for this secret. You need to solve all the challenges + in order to recover your secret. + </p> + ) : ( + <p class="block"> + We have found {policies.length} polices. You need to solve all the + challenges from one policy in order to recover your secret. + </p> + )} {policiesWithInfo.map((policy, policy_index) => { const tableBody = policy.challenges.map(({ info, uuid }) => { - const isFree = !info.cost || info.cost.endsWith(':0') - const method = authMethods[info.type as KnownAuthMethods] + const isFree = !info.cost || info.cost.endsWith(":0"); + const method = authMethods[info.type as KnownAuthMethods]; return ( - <div key={uuid} class="block" style={{ display: 'flex', justifyContent: 'space-between' }}> - <div style={{display:'flex', alignItems:'center'}}> - <span class="icon"> - {method?.icon} - </span> - <span> - {info.instructions} - </span> + <div + key={uuid} + class="block" + style={{ display: "flex", justifyContent: "space-between" }} + > + <div + style={{ + display: "flex", + flexDirection: "column", + }} + > + <div style={{ display: "flex", alignItems: "center" }}> + <span class="icon">{method?.icon}</span> + <span>{info.instructions}</span> + </div> + {info.feedback?.state === ChallengeFeedbackStatus.Message ? ( + <div> + <p>{info.feedback.message}</p> + </div> + ) : null} </div> <div> {method && info.feedback?.state !== "solved" ? ( - <a class="button" onClick={() => reducer.transition("select_challenge", { uuid })}> + <a + class="button" + onClick={() => + reducer.transition("select_challenge", { uuid }) + } + > {isFree ? "Solve" : `Pay and Solve`} </a> ) : null} @@ -86,26 +121,36 @@ export function ChallengeOverviewScreen(): VNode { </div> </div> ); - }) - - const policyName = policy.challenges.map(x => x.info.type).join(" + "); - const opa = !atLeastThereIsOnePolicySolved ? undefined : ( policy.isPolicySolved ? undefined : '0.6') + }); + + const policyName = policy.challenges + .map((x) => x.info.type) + .join(" + "); + const opa = !atLeastThereIsOnePolicySolved + ? undefined + : policy.isPolicySolved + ? undefined + : "0.6"; return ( - <div key={policy_index} class="box" style={{ - opacity: opa - }}> + <div + key={policy_index} + class="box" + style={{ + opacity: opa, + }} + > <h3 class="subtitle"> Policy #{policy_index + 1}: {policyName} </h3> - {policy.challenges.length === 0 && <p> - This policy doesn't have challenges. - </p>} - {policy.challenges.length === 1 && <p> - This policy just have one challenge. - </p>} - {policy.challenges.length > 1 && <p> - This policy have {policy.challenges.length} challenges. - </p>} + {policy.challenges.length === 0 && ( + <p>This policy doesn't have challenges.</p> + )} + {policy.challenges.length === 1 && ( + <p>This policy just have one challenge.</p> + )} + {policy.challenges.length > 1 && ( + <p>This policy have {policy.challenges.length} challenges.</p> + )} {tableBody} </div> ); diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx index 77329f4fa..b64e1a096 100644 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx @@ -16,7 +16,7 @@ export function CountrySelectionScreen(): VNode { currencies: [x.currency], }); return ( - <AnastasisClientFrame hideNext title={withProcessLabel(reducer, "Select Country")} > + <AnastasisClientFrame hideNext={"FIXME"} title={withProcessLabel(reducer, "Select Country")} > <div style={{ display: 'flex', flexDirection: 'column' }}> {reducer.currentReducerState.countries!.map((x: any) => ( <div key={x.name}> diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx index 098a8ba8d..0b32e0db5 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx @@ -13,7 +13,7 @@ export function TruthsPayingScreen(): VNode { const payments = reducer.currentReducerState.payments ?? []; return ( <AnastasisClientFrame - hideNext + hideNext={"FIXME"} title="Backup: Truths Paying" > <p> |