commit 1fd337f4fed08d7867359ec52104a6cadb76cdfc parent aa78c1105e7b6b74d6185cc33daa42f93ccbea58 Author: Sebastian <sebasjm@gmail.com> Date: Tue, 2 Nov 2021 12:31:37 -0300 refactoring challenge overview to look more like policy reviewing Diffstat:
23 files changed, 281 insertions(+), 264 deletions(-)
diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -3,7 +3,7 @@ import { AuthMethod } from "anastasis-core"; import { ComponentChildren, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { authMethods, KnownAuthMethods } from "./authMethodSetup"; +import { authMethods, KnownAuthMethods } from "./authMethod"; import { AnastasisClientFrame } from "./index"; diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -56,17 +56,17 @@ export const SomePoliciesOneSolved = createExample(TestedComponent, { policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]], challenges: [{ cost: 'USD:1', - instructions: 'just go for it', + instructions: 'this question cost 1 USD', type: 'question', uuid: '1', }, { - cost: 'USD:1', - instructions: 'just go for it', + cost: 'USD:0', + instructions: 'answering this question is free', type: 'question', uuid: '2', }, { cost: 'USD:1', - instructions: 'just go for it', + instructions: 'this question is already answered', type: 'question', uuid: 'uuid-3', }] @@ -84,8 +84,8 @@ export const OneBadConfiguredPolicy = createExample(TestedComponent, { policies: [[{ uuid: '1' }, { uuid: '2' }]], challenges: [{ cost: 'USD:1', - instructions: 'just go for it', - type: 'sasd', + instructions: 'this policy has a missing uuid (the other auth method)', + type: 'totp', uuid: '1', }], }, @@ -101,35 +101,48 @@ export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, { { uuid: '4' }, { uuid: '5' }, { uuid: '6' }, + { uuid: '7' }, + { uuid: '8' }, ]], challenges: [{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'Does P equals NP?', type: 'question', uuid: '1', },{ cost: 'USD:1', - instructions: 'enter a text received by a sms', + instructions: 'SMS to 555-555', type: 'sms', uuid: '2', },{ cost: 'USD:1', - instructions: 'enter a text received by a email', + instructions: 'Email to qwe@asd.com', type: 'email', uuid: '3', },{ cost: 'USD:1', - instructions: 'enter a code based on a time-based one-time password', + instructions: 'Enter 8 digits code for "Anastasis"', type: 'totp', uuid: '4', - },{ - cost: 'USD:1', - instructions: 'send a wire transfer to an account', + },{// + cost: 'USD:0', + instructions: 'Wire transfer from ASDXCVQWE123123 with holder Florian', type: 'iban', uuid: '5', },{ cost: 'USD:1', - instructions: 'just go for it', + 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', }], @@ -154,52 +167,52 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample(Teste ]], challenges: [{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "solved"', type: 'question', uuid: '1', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "hint"', type: 'question', uuid: '2', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "details"', type: 'question', uuid: '3', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "body"', type: 'question', uuid: '4', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "redirect"', type: 'question', uuid: '5', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "server-failure"', type: 'question', uuid: '6', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "truth-unknown"', type: 'question', uuid: '7', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "rate-limit-exceeded"', type: 'question', uuid: '8', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "authentication-timeout"', type: 'question', uuid: '9', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "external-instructions"', type: 'question', uuid: '10', }], diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -1,7 +1,9 @@ +/* eslint-disable @typescript-eslint/camelcase */ import { ChallengeFeedback } 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() @@ -50,59 +52,61 @@ export function ChallengeOverviewScreen(): VNode { const errors = !atLeastThereIsOnePolicySolved ? "Solve one policy before proceeding" : undefined; return ( <AnastasisClientFrame hideNext={errors} title="Recovery: Solve challenges"> - {!policies.length ? <p> + {!policies.length ? <p class="block"> No policies found, try with another version of the secret - </p> : (policies.length === 1 ? <p> + </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> + </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((row, i) => { - const tableBody = row.challenges.map(({ info, uuid }) => { + {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] return ( - <tr key={uuid}> - <td>{info.type}</td> - <td> - {info.instructions} - </td> - <td>{info.feedback?.state ?? "unknown"}</td> - <td>{info.cost}</td> - <td> - {info.feedback?.state !== "solved" ? ( - <a onClick={() => reducer.transition("select_challenge", { uuid })}> - Solve + <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> + <div> + {method && info.feedback?.state !== "solved" ? ( + <a class="button" onClick={() => reducer.transition("select_challenge", { uuid })}> + {isFree ? "Solve" : `Pay and Solve`} </a> ) : null} - </td> - </tr> + {info.feedback?.state === "solved" ? ( + <a class="button is-success"> Solved </a> + ) : null} + </div> + </div> ); }) + + const policyName = policy.challenges.map(x => x.info.type).join(" + "); + const opa = !atLeastThereIsOnePolicySolved ? undefined : ( policy.isPolicySolved ? undefined : '0.6') return ( - <div key={i}> - <b>Policy #{i + 1}</b> - {row.challenges.length === 0 && <p> - This policy doesn't have challenges + <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>} - {row.challenges.length === 1 && <p> - This policy just have one challenge to be solved + {policy.challenges.length === 1 && <p> + This policy just have one challenge. </p>} - {row.challenges.length > 1 && <p> - This policy have {row.challenges.length} challenges + {policy.challenges.length > 1 && <p> + This policy have {policy.challenges.length} challenges. </p>} - <table class="table"> - <thead> - <tr> - <td>Challenge type</td> - <td>Description</td> - <td>Status</td> - <td>Cost</td> - </tr> - </thead> - <tbody> - {tableBody} - </tbody> - </table> + {tableBody} </div> ); })} diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -2,7 +2,7 @@ import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; -import { authMethods, KnownAuthMethods } from "./authMethodSetup"; +import { authMethods, KnownAuthMethods } from "./authMethod"; export function ReviewPoliciesScreen(): VNode { const reducer = useAnastasisContext() diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; +import { EmailInput } from "../../../components/fields/EmailInput"; + +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,}))$/ + +export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { + const [email, setEmail] = useState(""); + const addEmailAuth = (): void => addAuthMethod({ + authentication_method: { + type: "email", + instructions: `Email to ${email}`, + challenge: encodeCrock(stringToBytes(email)), + }, + }); + const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined + const errors = !email ? 'Add your email' : emailError + + return ( + <AnastasisClientFrame hideNav title="Add email authentication"> + <p> + For email authentication, you need to provide an email address. When + recovering your secret, you will need to enter the code you receive by + email. + </p> + <div> + <EmailInput + label="Email address" + error={emailError} + placeholder="email@domain.com" + bind={[email, setEmail]} /> + </div> + {configured.length > 0 && <section class="section"> + <div class="block"> + Your emails: + </div><div class="block"> + {configured.map((c, i) => { + return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> + <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p> + <div><button class="button is-danger" onClick={c.remove} >Delete</button></div> + </div> + })} + </div></section>} + <div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={cancel}>Cancel</button> + <span data-tooltip={errors}> + <button class="button is-info" disabled={errors !== undefined} onClick={addEmailAuth}>Add</button> + </span> + </div> + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx @@ -0,0 +1,81 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useMemo, useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; +import { QR } from "../../../components/QR"; +import { base32enc, computeTOTPandCheck } from "./totp"; + +export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [name, setName] = useState("anastasis"); + const [test, setTest] = useState(""); + const digits = 8 + const secretKey = useMemo(() => { + const array = new Uint8Array(32) + return window.crypto.getRandomValues(array) + }, []) + const secret32 = base32enc(secretKey); + const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` + + const addTotpAuth = (): void => addAuthMethod({ + authentication_method: { + type: "totp", + instructions: `Enter ${digits} digits code for "${name}"`, + challenge: encodeCrock(stringToBytes(totpURL)), + }, + }); + + const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10)); + + const errors = !name ? 'The TOTP name is missing' : ( + !testCodeMatches ? 'The test code doesnt match' : undefined + ); + return ( + <AnastasisClientFrame hideNav title="Add TOTP authentication"> + <p> + For Time-based One-Time Password (TOTP) authentication, you need to set + a name for the TOTP secret. Then, you must scan the generated QR code + with your TOTP App to import the TOTP secret into your TOTP App. + </p> + <div class="block"> + <TextInput + label="TOTP Name" + grabFocus + bind={[name, setName]} /> + </div> + <div style={{ height: 300 }}> + <QR text={totpURL} /> + </div> + <p> + After scanning the code with your TOTP App, test it in the input below. + </p> + <TextInput + label="Test code" + bind={[test, setTest]} /> + {configured.length > 0 && <section class="section"> + <div class="block"> + Your TOTP numbers: + </div><div class="block"> + {configured.map((c, i) => { + return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> + <p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p> + <div><button class="button is-danger" onClick={c.remove}>Delete</button></div> + </div> + })} + </div></section>} + <div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={cancel}>Cancel</button> + <span data-tooltip={errors}> + <button class="button is-info" disabled={errors !== undefined} onClick={addTotpAuth}>Add</button> + </span> + </div> + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { ImageInput } from "../../../components/fields/ImageInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { + const [image, setImage] = useState(""); + const addVideoAuth = (): void => { + addAuthMethod({ + authentication_method: { + type: "video", + instructions: 'Join a video call', + challenge: encodeCrock(stringToBytes(image)), + }, + }) + }; + return ( + <AnastasisClientFrame hideNav title="Add video authentication"> + <p> + For video identification, you need to provide a passport-style + photograph. When recovering your secret, you will be asked to join a + video call. During that call, a human will use the photograph to + verify your identity. + </p> + <div style={{textAlign:'center'}}> + <ImageInput + label="Choose photograph" + grabFocus + bind={[image, setImage]} /> + </div> + {configured.length > 0 && <section class="section"> + <div class="block"> + Your photographs: + </div><div class="block"> + {configured.map((c, i) => { + return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> + <img style={{ marginTop: 'auto', marginBottom: 'auto', width: 100, height:100, border: 'solid 1px black' }} src={c.instructions} /> + <div style={{marginTop: 'auto', marginBottom: 'auto'}}><button class="button is-danger" onClick={c.remove}>Delete</button></div> + </div> + })} + </div></section>} + <div> + <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> + <button class="button" onClick={cancel}>Cancel</button> + <button class="button is-info" onClick={addVideoAuth}>Add</button> + </div> + </div> + </AnastasisClientFrame> + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx @@ -1,62 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; -import { TextInput } from "../../../components/fields/TextInput"; -import { EmailInput } from "../../../components/fields/EmailInput"; - -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,}))$/ - -export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { - const [email, setEmail] = useState(""); - const addEmailAuth = (): void => addAuthMethod({ - authentication_method: { - type: "email", - instructions: `Email to ${email}`, - challenge: encodeCrock(stringToBytes(email)), - }, - }); - const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined - const errors = !email ? 'Add your email' : emailError - - return ( - <AnastasisClientFrame hideNav title="Add email authentication"> - <p> - For email authentication, you need to provide an email address. When - recovering your secret, you will need to enter the code you receive by - email. - </p> - <div> - <EmailInput - label="Email address" - error={emailError} - placeholder="email@domain.com" - bind={[email, setEmail]} /> - </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your emails: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove} >Delete</button></div> - </div> - })} - </div></section>} - <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Canceul</button> - <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addEmailAuth}>Add</button> - </span> - </div> - </div> - </AnastasisClientFrame> - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx @@ -1,81 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useMemo, useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; -import { TextInput } from "../../../components/fields/TextInput"; -import { QR } from "../../../components/QR"; -import { base32enc, computeTOTPandCheck } from "./totp"; - -export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { - const [name, setName] = useState("anastasis"); - const [test, setTest] = useState(""); - const digits = 8 - const secretKey = useMemo(() => { - const array = new Uint8Array(32) - return window.crypto.getRandomValues(array) - }, []) - const secret32 = base32enc(secretKey); - const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` - - const addTotpAuth = (): void => addAuthMethod({ - authentication_method: { - type: "totp", - instructions: `Enter ${digits} digits code for ${name}`, - challenge: encodeCrock(stringToBytes(totpURL)), - }, - }); - - const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10)); - - const errors = !name ? 'The TOTP name is missing' : ( - !testCodeMatches ? 'The test code doesnt match' : undefined - ); - return ( - <AnastasisClientFrame hideNav title="Add TOTP authentication"> - <p> - For Time-based One-Time Password (TOTP) authentication, you need to set - a name for the TOTP secret. Then, you must scan the generated QR code - with your TOTP App to import the TOTP secret into your TOTP App. - </p> - <div class="block"> - <TextInput - label="TOTP Name" - grabFocus - bind={[name, setName]} /> - </div> - <div style={{ height: 300 }}> - <QR text={totpURL} /> - </div> - <p> - After scanning the code with your TOTP App, test it in the input below. - </p> - <TextInput - label="Test code" - bind={[test, setTest]} /> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your TOTP numbers: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p> - <div><button class="button is-danger" onClick={c.remove}>Delete</button></div> - </div> - })} - </div></section>} - <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> - <span data-tooltip={errors}> - <button class="button is-info" disabled={errors !== undefined} onClick={addTotpAuth}>Add</button> - </span> - </div> - </div> - </AnastasisClientFrame> - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx @@ -1,56 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { ImageInput } from "../../../components/fields/ImageInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; - -export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { - const [image, setImage] = useState(""); - const addVideoAuth = (): void => { - addAuthMethod({ - authentication_method: { - type: "video", - instructions: image, - challenge: encodeCrock(stringToBytes(image)), - }, - }) - }; - return ( - <AnastasisClientFrame hideNav title="Add video authentication"> - <p> - For video identification, you need to provide a passport-style - photograph. When recovering your secret, you will be asked to join a - video call. During that call, a human will use the photograph to - verify your identity. - </p> - <div style={{textAlign:'center'}}> - <ImageInput - label="Choose photograph" - grabFocus - bind={[image, setImage]} /> - </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your photographs: - </div><div class="block"> - {configured.map((c, i) => { - return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <img style={{ marginTop: 'auto', marginBottom: 'auto', width: 100, height:100, border: 'solid 1px black' }} src={c.instructions} /> - <div style={{marginTop: 'auto', marginBottom: 'auto'}}><button class="button is-danger" onClick={c.remove}>Delete</button></div> - </div> - })} - </div></section>} - <div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> - <button class="button is-info" onClick={addVideoAuth}>Add</button> - </div> - </div> - </AnastasisClientFrame> - ); -}