diff options
Diffstat (limited to 'packages/anastasis-webui/src/pages/home')
60 files changed, 2910 insertions, 1941 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx index 08e2b4371..9b067127d 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx @@ -15,24 +15,23 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { AddingProviderScreen as TestedComponent } from './AddingProviderScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { AddingProviderScreen as TestedComponent } from "./AddingProviderScreen"; export default { - title: 'Pages/ManageProvider', + title: "Pages/ManageProvider", component: TestedComponent, args: { order: 1, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; @@ -40,20 +39,31 @@ export const NewProvider = createExample(TestedComponent, { ...reducerStatesExample.authEditing, } as ReducerState); - export const NewProviderWithoutProviderList = createExample(TestedComponent, { ...reducerStatesExample.authEditing, - authentication_providers: {} + authentication_providers: {}, } as ReducerState); -export const NewVideoProvider = createExample(TestedComponent, { - ...reducerStatesExample.authEditing, -} as ReducerState, { providerType: 'video'}); +export const NewVideoProvider = createExample( + TestedComponent, + { + ...reducerStatesExample.authEditing, + } as ReducerState, + { providerType: "video" }, +); -export const NewSmsProvider = createExample(TestedComponent, { - ...reducerStatesExample.authEditing, -} as ReducerState, { providerType: 'sms'}); +export const NewSmsProvider = createExample( + TestedComponent, + { + ...reducerStatesExample.authEditing, + } as ReducerState, + { providerType: "sms" }, +); -export const NewIBANProvider = createExample(TestedComponent, { - ...reducerStatesExample.authEditing, -} as ReducerState, { providerType: 'iban' }); +export const NewIBANProvider = createExample( + TestedComponent, + { + ...reducerStatesExample.authEditing, + } as ReducerState, + { providerType: "iban" }, +); diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx index 7504f4d2b..96b38e92d 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx @@ -11,185 +11,250 @@ interface Props { onCancel: () => void; } - -async function testProvider(url: string, expectedMethodType?: string): Promise<void> { +async function testProvider( + url: string, + expectedMethodType?: string, +): Promise<void> { try { - const response = await fetch(new URL("config", url).href) - const json = await (response.json().catch(d => ({}))) + const response = await fetch(new URL("config", url).href); + const json = await response.json().catch((d) => ({})); if (!("methods" in json) || !Array.isArray(json.methods)) { - throw Error("This provider doesn't have authentication method. Check the provider URL") + throw Error( + "This provider doesn't have authentication method. Check the provider URL", + ); } - console.log("expected", expectedMethodType) + console.log("expected", expectedMethodType); if (!expectedMethodType) { - return + return; } - let found = false + let found = false; for (let i = 0; i < json.methods.length && !found; i++) { - found = json.methods[i].type === expectedMethodType + found = json.methods[i].type === expectedMethodType; } if (!found) { - throw Error(`This provider does not support authentication method ${expectedMethodType}`) + throw Error( + `This provider does not support authentication method ${expectedMethodType}`, + ); } - return + return; } catch (e) { - console.log("error", e) - const error = e instanceof Error ? - Error(`There was an error testing this provider, try another one. ${e.message}`) : - Error(`There was an error testing this provider, try another one.`) - throw error + console.log("error", e); + const error = + e instanceof Error + ? Error( + `There was an error testing this provider, try another one. ${e.message}`, + ) + : Error(`There was an error testing this provider, try another one.`); + throw error; } - } export function AddingProviderScreen({ providerType, onCancel }: Props): VNode { const reducer = useAnastasisContext(); const [providerURL, setProviderURL] = useState(""); - const [error, setError] = useState<string | undefined>() - const [testing, setTesting] = useState(false) - const providerLabel = providerType ? authMethods[providerType].label : undefined + const [error, setError] = useState<string | undefined>(); + const [testing, setTesting] = useState(false); + const providerLabel = providerType + ? authMethods[providerType].label + : undefined; //FIXME: move this timeout logic into a hook const timeout = useRef<number | undefined>(undefined); useEffect(() => { - if (timeout) window.clearTimeout(timeout.current) + if (timeout) window.clearTimeout(timeout.current); timeout.current = window.setTimeout(async () => { - const url = providerURL.endsWith('/') ? providerURL : (providerURL + '/') + const url = providerURL.endsWith("/") ? providerURL : providerURL + "/"; if (!providerURL || authProviders.includes(url)) return; try { - setTesting(true) - await testProvider(url, providerType) + setTesting(true); + await testProvider(url, providerType); // this is use as tested but everything when ok // undefined will mean that the field is not dirty - setError("") + setError(""); } catch (e) { - console.log("tuvieja", e) - if (e instanceof Error) setError(e.message) + console.log("tuvieja", e); + if (e instanceof Error) setError(e.message); } - setTesting(false) + setTesting(false); }, 200); - }, [providerURL, reducer]) - + }, [providerURL, reducer]); if (!reducer) { return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || !("authentication_providers" in reducer.currentReducerState)) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + !("authentication_providers" in reducer.currentReducerState) + ) { + return <div>invalid state</div>; } async function addProvider(provider_url: string): Promise<void> { - await reducer?.transition("add_provider", { provider_url }) - onCancel() + await reducer?.transition("add_provider", { provider_url }); + onCancel(); } function deleteProvider(provider_url: string): void { - reducer?.transition("delete_provider", { provider_url }) + reducer?.transition("delete_provider", { provider_url }); } - const allAuthProviders = reducer.currentReducerState.authentication_providers || {} - const authProviders = Object.keys(allAuthProviders).filter(provUrl => { + const allAuthProviders = + reducer.currentReducerState.authentication_providers || {}; + const authProviders = Object.keys(allAuthProviders).filter((provUrl) => { const p = allAuthProviders[provUrl]; if (!providerLabel) { - return p && ("currency" in p) + return p && "currency" in p; } else { - return p && ("currency" in p) && p.methods.findIndex(m => m.type === providerType) !== -1 + return ( + p && + "currency" in p && + p.methods.findIndex((m) => m.type === providerType) !== -1 + ); } - }) + }); - let errors = !providerURL ? 'Add provider URL' : undefined + let errors = !providerURL ? "Add provider URL" : undefined; let url: string | undefined; try { - url = new URL("",providerURL).href + url = new URL("", providerURL).href; } catch { - errors = 'Check the URL' + errors = "Check the URL"; } if (!!error && !errors) { - errors = error + errors = error; } if (!errors && authProviders.includes(url!)) { - errors = 'That provider is already known' + errors = "That provider is already known"; } return ( - <AnastasisClientFrame hideNav + <AnastasisClientFrame + hideNav title="Backup: Manage providers" - hideNext={errors}> + hideNext={errors} + > <div> - {!providerLabel ? - <p> - Add a provider url - </p> : - <p> - Add a provider url for a {providerLabel} service - </p> - } + {!providerLabel ? ( + <p>Add a provider url</p> + ) : ( + <p>Add a provider url for a {providerLabel} service</p> + )} <div class="container"> <TextInput label="Provider URL" placeholder="https://provider.com" grabFocus error={errors} - bind={[providerURL, setProviderURL]} /> + bind={[providerURL, setProviderURL]} + /> </div> - <p class="block"> - Example: https://kudos.demo.anastasis.lu - </p> + <p class="block">Example: https://kudos.demo.anastasis.lu</p> {testing && <p class="has-text-info">Testing</p>} - - <div class="block" style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={onCancel}>Cancel</button> + + <div + class="block" + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> <span data-tooltip={errors}> - <button class="button is-info" disabled={error !== "" || testing} onClick={() => addProvider(url!)}>Add</button> + <button + class="button is-info" + disabled={error !== "" || testing} + onClick={() => addProvider(url!)} + > + Add + </button> </span> </div> {authProviders.length > 0 ? ( - !providerLabel ? + !providerLabel ? ( + <p class="subtitle">Current providers</p> + ) : ( <p class="subtitle"> - Current providers - </p> : <p class="subtitle"> Current providers for {providerLabel} service </p> + ) + ) : !providerLabel ? ( + <p class="subtitle">No known providers, add one.</p> ) : ( - !providerLabel ? <p class="subtitle"> - No known providers, add one. - </p> : <p class="subtitle"> - No known providers for {providerLabel} service - </p> + <p class="subtitle">No known providers for {providerLabel} service</p> )} - {authProviders.map(k => { - const p = allAuthProviders[k] as AuthenticationProviderStatusOk - return <TableRow url={k} info={p} onDelete={deleteProvider} /> + {authProviders.map((k) => { + const p = allAuthProviders[k] as AuthenticationProviderStatusOk; + return <TableRow url={k} info={p} onDelete={deleteProvider} />; })} </div> </AnastasisClientFrame> ); } -function TableRow({ url, info, onDelete }: { onDelete: (s: string) => void, url: string, info: AuthenticationProviderStatusOk }) { - const [status, setStatus] = useState("checking") +function TableRow({ + url, + info, + onDelete, +}: { + onDelete: (s: string) => void; + url: string; + info: AuthenticationProviderStatusOk; +}) { + const [status, setStatus] = useState("checking"); useEffect(function () { - testProvider(url.endsWith('/') ? url.substring(0, url.length - 1) : url) - .then(function () { setStatus('responding') }) - .catch(function () { setStatus('failed to contact') }) - }) - return <div class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> - <div> - <div class="subtitle">{url}</div> - <dl> - <dt><b>Business Name</b></dt> - <dd>{info.business_name}</dd> - <dt><b>Supported methods</b></dt> - <dd>{info.methods.map(m => m.type).join(',')}</dd> - <dt><b>Maximum storage</b></dt> - <dd>{info.storage_limit_in_megabytes} Mb</dd> - <dt><b>Status</b></dt> - <dd>{status}</dd> - </dl> - </div> - <div class="block" style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}> - <button class="button is-danger" onClick={() => onDelete(url)}>Remove</button> + testProvider(url.endsWith("/") ? url.substring(0, url.length - 1) : url) + .then(function () { + setStatus("responding"); + }) + .catch(function () { + setStatus("failed to contact"); + }); + }); + return ( + <div + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > + <div> + <div class="subtitle">{url}</div> + <dl> + <dt> + <b>Business Name</b> + </dt> + <dd>{info.business_name}</dd> + <dt> + <b>Supported methods</b> + </dt> + <dd>{info.methods.map((m) => m.type).join(",")}</dd> + <dt> + <b>Maximum storage</b> + </dt> + <dd>{info.storage_limit_in_megabytes} Mb</dd> + <dt> + <b>Status</b> + </dt> + <dd>{status}</dd> + </dl> + </div> + <div + class="block" + style={{ + marginTop: "auto", + marginBottom: "auto", + display: "flex", + justifyContent: "space-between", + flexDirection: "column", + }} + > + <button class="button is-danger" onClick={() => onDelete(url)}> + Remove + </button> + </div> </div> - </div> -}
\ No newline at end of file + ); +} diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx index 9cdd132ef..d48e94403 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx @@ -15,76 +15,83 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { AttributeEntryScreen as TestedComponent } from "./AttributeEntryScreen"; export default { - title: 'Pages/PersonalInformation', + title: "Pages/PersonalInformation", component: TestedComponent, args: { order: 3, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const Backup = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, - required_attributes: [{ - name: 'first name', - label: 'first', - type: 'string', - uuid: 'asdasdsa1', - widget: 'wid', - }, { - name: 'last name', - label: 'second', - type: 'string', - uuid: 'asdasdsa2', - widget: 'wid', - }, { - name: 'birthdate', - label: 'birthdate', - type: 'date', - uuid: 'asdasdsa3', - widget: 'calendar', - }] + required_attributes: [ + { + name: "first name", + label: "first", + type: "string", + uuid: "asdasdsa1", + widget: "wid", + }, + { + name: "last name", + label: "second", + type: "string", + uuid: "asdasdsa2", + widget: "wid", + }, + { + name: "birthdate", + label: "birthdate", + type: "date", + uuid: "asdasdsa3", + widget: "calendar", + }, + ], } as ReducerState); export const Recovery = createExample(TestedComponent, { ...reducerStatesExample.recoveryAttributeEditing, - required_attributes: [{ - name: 'first', - label: 'first', - type: 'string', - uuid: 'asdasdsa1', - widget: 'wid', - }, { - name: 'pepe', - label: 'second', - type: 'string', - uuid: 'asdasdsa2', - widget: 'wid', - }, { - name: 'pepe2', - label: 'third', - type: 'date', - uuid: 'asdasdsa3', - widget: 'calendar', - }] + required_attributes: [ + { + name: "first", + label: "first", + type: "string", + uuid: "asdasdsa1", + widget: "wid", + }, + { + name: "pepe", + label: "second", + type: "string", + uuid: "asdasdsa2", + widget: "wid", + }, + { + name: "pepe2", + label: "third", + type: "date", + uuid: "asdasdsa3", + widget: "calendar", + }, + ], } as ReducerState); export const WithNoRequiredAttribute = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, - required_attributes: undefined + required_attributes: undefined, } as ReducerState); const allWidgets = [ @@ -107,23 +114,22 @@ const allWidgets = [ "anastasis_gtk_ia_tax_de", "anastasis_gtk_xx_prime", "anastasis_gtk_xx_square", -] +]; function typeForWidget(name: string): string { - if (["anastasis_gtk_xx_prime", - "anastasis_gtk_xx_square", - ].includes(name)) return "number"; - if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date" + if (["anastasis_gtk_xx_prime", "anastasis_gtk_xx_square"].includes(name)) + return "number"; + if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date"; return "string"; } export const WithAllPosibleWidget = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, - required_attributes: allWidgets.map(w => ({ + required_attributes: allWidgets.map((w) => ({ name: w, label: `widget: ${w}`, type: typeForWidget(w), uuid: `uuid-${w}`, - widget: w - })) + widget: w, + })), } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 557718458..0918c2db5 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -9,24 +9,32 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame, withProcessLabel } from "./index"; export function AttributeEntryScreen(): VNode { - const reducer = useAnastasisContext() - const state = reducer?.currentReducerState - const currentIdentityAttributes = state && "identity_attributes" in state ? (state.identity_attributes || {}) : {} - const [attrs, setAttrs] = useState<Record<string, string>>(currentIdentityAttributes); + const reducer = useAnastasisContext(); + const state = reducer?.currentReducerState; + const currentIdentityAttributes = + state && "identity_attributes" in state + ? state.identity_attributes || {} + : {}; + const [attrs, setAttrs] = useState<Record<string, string>>( + currentIdentityAttributes, + ); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + !("required_attributes" in reducer.currentReducerState) + ) { + return <div>invalid state</div>; } - const reqAttr = reducer.currentReducerState.required_attributes || [] + const reqAttr = reducer.currentReducerState.required_attributes || []; let hasErrors = false; const fieldList: VNode[] = reqAttr.map((spec, i: number) => { - const value = attrs[spec.name] - const error = checkIfValid(value, spec) - hasErrors = hasErrors || error !== undefined + const value = attrs[spec.name]; + const error = checkIfValid(value, spec); + hasErrors = hasErrors || error !== undefined; return ( <AttributeEntryField key={i} @@ -34,23 +42,24 @@ export function AttributeEntryScreen(): VNode { setValue={(v: string) => setAttrs({ ...attrs, [spec.name]: v })} spec={spec} errorMessage={error} - value={value} /> + value={value} + /> ); - }) + }); return ( <AnastasisClientFrame title={withProcessLabel(reducer, "Who are you?")} hideNext={hasErrors ? "Complete the form." : undefined} - onNext={() => reducer.transition("enter_user_attributes", { - identity_attributes: attrs, - })} + onNext={() => + reducer.transition("enter_user_attributes", { + identity_attributes: attrs, + }) + } > - <div class="columns" style={{ maxWidth: 'unset' }}> + <div class="columns" style={{ maxWidth: "unset" }}> + <div class="column">{fieldList}</div> <div class="column"> - {fieldList} - </div> - <div class="column" > <p>This personal information will help to locate your secret.</p> <h1 class="title">This stays private</h1> <p>The information you have entered here:</p> @@ -61,9 +70,12 @@ export function AttributeEntryScreen(): VNode { </span> Will be hashed, and therefore unreadable </li> - <li><span class="icon is-right"> - <i class="mdi mdi-circle-small" /> - </span>The non-hashed version is not shared</li> + <li> + <span class="icon is-right"> + <i class="mdi mdi-circle-small" /> + </span> + The non-hashed version is not shared + </li> </ul> </div> </div> @@ -78,22 +90,22 @@ interface AttributeEntryFieldProps { spec: UserAttributeSpec; errorMessage: string | undefined; } -const possibleBirthdayYear: Array<number> = [] +const possibleBirthdayYear: Array<number> = []; for (let i = 0; i < 100; i++) { - possibleBirthdayYear.push(2020 - i) + possibleBirthdayYear.push(2020 - i); } function AttributeEntryField(props: AttributeEntryFieldProps): VNode { - return ( <div> - {props.spec.type === 'date' && + {props.spec.type === "date" && <DateInput grabFocus={props.isFirst} label={props.spec.label} years={possibleBirthdayYear} error={props.errorMessage} bind={[props.value, props.setValue]} - />} + /> + } {props.spec.type === 'number' && <PhoneNumberInput grabFocus={props.isFirst} @@ -102,14 +114,14 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { bind={[props.value, props.setValue]} /> } - {props.spec.type === 'string' && + {props.spec.type === "string" && ( <TextInput grabFocus={props.isFirst} label={props.spec.label} error={props.errorMessage} bind={[props.value, props.setValue]} /> - } + )} <div class="block"> This stays private <span class="icon is-right"> @@ -119,40 +131,43 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { </div> ); } -const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/ - +const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/; -function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined { - const pattern = spec['validation-regex'] +function checkIfValid( + value: string, + spec: UserAttributeSpec, +): string | undefined { + const pattern = spec["validation-regex"]; if (pattern) { - const re = new RegExp(pattern) - if (!re.test(value)) return 'The value is invalid' + const re = new RegExp(pattern); + if (!re.test(value)) return "The value is invalid"; } - const logic = spec['validation-logic'] + const logic = spec["validation-logic"]; if (logic) { const func = (validators as any)[logic]; - if (func && typeof func === 'function' && !func(value)) return 'Please check the value' + if (func && typeof func === "function" && !func(value)) + return "Please check the value"; } - const optional = spec.optional + const optional = spec.optional; if (!optional && !value) { - return 'This value is required' + return "This value is required"; } if ("date" === spec.type) { if (!YEAR_REGEX.test(value)) { - return "The date doesn't follow the format" + return "The date doesn't follow the format"; } try { - const v = parse(value, 'yyyy-MM-dd', new Date()); + const v = parse(value, "yyyy-MM-dd", new Date()); if (Number.isNaN(v.getTime())) { - return "Some numeric values seems out of range for a date" + return "Some numeric values seems out of range for a date"; } if ("birthdate" === spec.name && isAfter(v, new Date())) { - return "A birthdate cannot be in the future" + return "A birthdate cannot be in the future"; } } catch (e) { - return "Could not parse the date" + return "Could not parse the date"; } } - return undefined + return undefined; } diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx index 2712522ce..8acf1c8c8 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx @@ -15,73 +15,84 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationEditorScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { AuthenticationEditorScreen as TestedComponent } from "./AuthenticationEditorScreen"; export default { - title: 'Pages/backup/AuthorizationMethod', + title: "Pages/backup/AuthorizationMethod", component: TestedComponent, args: { order: 4, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const InitialState = createExample(TestedComponent, reducerStatesExample.authEditing); +export const InitialState = createExample( + TestedComponent, + reducerStatesExample.authEditing, +); export const OneAuthMethodConfigured = createExample(TestedComponent, { ...reducerStatesExample.authEditing, - authentication_methods: [{ - type: 'question', - instructions: 'what time is it?', - challenge: 'asd', - }] + authentication_methods: [ + { + type: "question", + instructions: "what time is it?", + challenge: "asd", + }, + ], } as ReducerState); - export const SomeMoreAuthMethodConfigured = createExample(TestedComponent, { ...reducerStatesExample.authEditing, - authentication_methods: [{ - type: 'question', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'question', - instructions: 'what time is it?', - challenge: 'qwe', - },{ - type: 'sms', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - },{ - type: 'email', - instructions: 'what time is it?', - challenge: 'asd', - }] + authentication_methods: [ + { + type: "question", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "question", + instructions: "what time is it?", + challenge: "qwe", + }, + { + type: "sms", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + { + type: "email", + instructions: "what time is it?", + challenge: "asd", + }, + ], } as ReducerState); export const NoAuthMethodProvided = createExample(TestedComponent, { ...reducerStatesExample.authEditing, authentication_providers: {}, - authentication_methods: [] + authentication_methods: [], } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index 00eb54d4d..1ef326773 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -20,7 +20,9 @@ export function AuthenticationEditorScreen(): VNode { KnownAuthMethods | undefined >(undefined); const [tooFewAuths, setTooFewAuths] = useState(false); - const [manageProvider, setManageProvider] = useState<string | undefined>(undefined) + const [manageProvider, setManageProvider] = useState<string | undefined>( + undefined, + ); // const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined) const reducer = useAnastasisContext(); @@ -68,11 +70,14 @@ export function AuthenticationEditorScreen(): VNode { } if (manageProvider !== undefined) { - - return <AddingProviderScreen - onCancel={() => setManageProvider(undefined)} - providerType={isKnownAuthMethods(manageProvider) ? manageProvider : undefined} - /> + return ( + <AddingProviderScreen + onCancel={() => setManageProvider(undefined)} + providerType={ + isKnownAuthMethods(manageProvider) ? manageProvider : undefined + } + /> + ); } if (selectedMethod) { @@ -100,7 +105,7 @@ export function AuthenticationEditorScreen(): VNode { description="No providers founds" label="Add a provider manually" onConfirm={() => { - setManageProvider(selectedMethod) + setManageProvider(selectedMethod); }} > <p> @@ -193,7 +198,7 @@ export function AuthenticationEditorScreen(): VNode { description="No providers founds" label="Add a provider manually" onConfirm={() => { - setManageProvider("") + setManageProvider(""); }} > <p> @@ -214,7 +219,10 @@ export function AuthenticationEditorScreen(): VNode { authentication method is defined by the backup provider list. </p> <p class="block"> - <button class="button is-info" onClick={() => setManageProvider("")}> + <button + class="button is-info" + onClick={() => setManageProvider("")} + > Manage backup providers </button> </p> diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index 306adacbb..0789ee6ad 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -15,48 +15,51 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { BackupFinishedScreen as TestedComponent } from "./BackupFinishedScreen"; export default { - title: 'Pages/backup/Finished', + title: "Pages/backup/Finished", component: TestedComponent, args: { order: 8, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const WithoutName = createExample(TestedComponent, reducerStatesExample.backupFinished); +export const WithoutName = createExample( + TestedComponent, + reducerStatesExample.backupFinished, +); -export const WithName = createExample(TestedComponent, {...reducerStatesExample.backupFinished, - secret_name: 'super_secret', +export const WithName = createExample(TestedComponent, { + ...reducerStatesExample.backupFinished, + secret_name: "super_secret", } as ReducerState); export const WithDetails = createExample(TestedComponent, { ...reducerStatesExample.backupFinished, - secret_name: 'super_secret', + secret_name: "super_secret", success_details: { - 'http://anastasis.net': { + "http://anastasis.net": { policy_expiration: { - t_ms: 'never' + t_ms: "never", }, - policy_version: 0 + policy_version: 0, }, - 'http://taler.net': { + "http://taler.net": { policy_expiration: { - t_ms: new Date().getTime() + 60*60*24*1000 + t_ms: new Date().getTime() + 60 * 60 * 24 * 1000, }, - policy_version: 1 + policy_version: 1, }, - } + }, } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index 7938baca4..825ec5dc0 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -4,41 +4,62 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function BackupFinishedScreen(): 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.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } - const details = reducer.currentReducerState.success_details + const details = reducer.currentReducerState.success_details; - return (<AnastasisClientFrame hideNav title="Backup finished"> - {reducer.currentReducerState.secret_name ? <p> - Your backup of secret <b>"{reducer.currentReducerState.secret_name}"</b> was - successful. - </p> : - <p> - Your secret was successfully backed up. - </p>} + return ( + <AnastasisClientFrame hideNav title="Backup finished"> + {reducer.currentReducerState.secret_name ? ( + <p> + Your backup of secret{" "} + <b>"{reducer.currentReducerState.secret_name}"</b> was successful. + </p> + ) : ( + <p>Your secret was successfully backed up.</p> + )} - {details && <div class="block"> - <p>The backup is stored by the following providers:</p> - {Object.keys(details).map((x, i) => { - const sd = details[x]; - return ( - <div key={i} class="box"> - {x} - <p> - version {sd.policy_version} - {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd-MM-yyyy')}` : ' without expiration date'} - </p> - </div> - ); - })} - </div>} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> - </div> - </AnastasisClientFrame>); + {details && ( + <div class="block"> + <p>The backup is stored by the following providers:</p> + {Object.keys(details).map((x, i) => { + const sd = details[x]; + return ( + <div key={i} class="box"> + {x} + <p> + version {sd.policy_version} + {sd.policy_expiration.t_ms !== "never" + ? ` expires at: ${format( + sd.policy_expiration.t_ms, + "dd-MM-yyyy", + )}` + : " without expiration date"} + </p> + </div> + ); + })} + </div> + )} + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> + </div> + </AnastasisClientFrame> + ); } diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index 46c574cf2..56aee8763 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -19,7 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ChallengeFeedbackStatus, RecoveryStates, ReducerState } from "anastasis-core"; +import { + ChallengeFeedbackStatus, + RecoveryStates, + ReducerState, +} from "anastasis-core"; import { createExample, reducerStatesExample } from "../../utils"; import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen"; @@ -247,20 +251,20 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( "uuid-1": { state: ChallengeFeedbackStatus.Solved.toString() }, "uuid-2": { state: ChallengeFeedbackStatus.Message.toString(), - message: 'Challenge should be solved' + message: "Challenge should be solved", }, "uuid-3": { state: ChallengeFeedbackStatus.AuthIban.toString(), challenge_amount: "EUR:1", credit_iban: "DE12345789000", business_name: "Data Loss Incorporated", - wire_transfer_subject: "Anastasis 987654321" + wire_transfer_subject: "Anastasis 987654321", }, "uuid-4": { state: ChallengeFeedbackStatus.Payment.toString(), taler_pay_uri: "taler://pay/...", provider: "https://localhost:8080/", - payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" + payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", }, "uuid-5": { state: ChallengeFeedbackStatus.RateLimitExceeded.toString(), @@ -269,7 +273,7 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample( "uuid-6": { state: ChallengeFeedbackStatus.Redirect.toString(), redirect_url: "https://videoconf.example.com/", - http_status: 303 + http_status: 303, }, "uuid-7": { state: ChallengeFeedbackStatus.ServerFailure.toString(), diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index ae03dd4d4..c6de00e98 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -11,23 +11,34 @@ function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { } switch (feedback.state) { case ChallengeFeedbackStatus.Message: - return ( - <div class="block has-text-danger">{feedback.message}</div> - ); + return <div class="block has-text-danger">{feedback.message}</div>; case ChallengeFeedbackStatus.Solved: - return <div /> + return <div />; case ChallengeFeedbackStatus.Pending: - case ChallengeFeedbackStatus.Solved: case ChallengeFeedbackStatus.AuthIban: return null; case ChallengeFeedbackStatus.ServerFailure: return <div class="block has-text-danger">Server error.</div>; case ChallengeFeedbackStatus.RateLimitExceeded: - return <div class="block has-text-danger">There were to many failed attempts.</div>; + return ( + <div class="block has-text-danger"> + There were to many failed attempts. + </div> + ); case ChallengeFeedbackStatus.Unsupported: - return <div class="block has-text-danger">This client doesn't support solving this type of challenge. Use another version or contact the provider.</div>; + return ( + <div class="block has-text-danger"> + This client doesn't support solving this type of challenge. Use + another version or contact the provider. + </div> + ); case ChallengeFeedbackStatus.TruthUnknown: - return <div class="block has-text-danger">Provider doesn't recognize the challenge of the policy. Contact the provider for further information.</div>; + return ( + <div class="block has-text-danger"> + Provider doesn't recognize the challenge of the policy. Contact the + provider for further information. + </div> + ); case ChallengeFeedbackStatus.Redirect: default: return <div />; @@ -70,19 +81,25 @@ export function ChallengeOverviewScreen(): VNode { 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, + corrupted: row.length > challenges.length, + }; + }) + .filter((p) => !p.corrupted); const atLeastThereIsOnePolicySolved = policiesWithInfo.find((p) => p.isPolicySolved) !== undefined; @@ -92,19 +109,19 @@ export function ChallengeOverviewScreen(): VNode { : undefined; return ( <AnastasisClientFrame hideNext={errors} title="Recovery: Solve challenges"> - {!policies.length ? ( + {!policiesWithInfo.length ? ( <p class="block"> No policies found, try with another version of the secret </p> - ) : policies.length === 1 ? ( + ) : policiesWithInfo.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. + We have found {policiesWithInfo.length} polices. You need to solve all + the challenges from one policy in order to recover your secret. </p> )} {policiesWithInfo.map((policy, policy_index) => { @@ -113,74 +130,100 @@ export function ChallengeOverviewScreen(): VNode { const method = authMethods[info.type as KnownAuthMethods]; if (!method) { - return <div - key={uuid} - class="block" - style={{ display: "flex", justifyContent: "space-between" }} - > - <div style={{ display: "flex", alignItems: "center" }}> - <span>unknown challenge</span> + return ( + <div + key={uuid} + class="block" + style={{ display: "flex", justifyContent: "space-between" }} + > + <div style={{ display: "flex", alignItems: "center" }}> + <span>unknown challenge</span> + </div> </div> - - </div> + ); } - function ChallengeButton({ id, feedback }: { id: string; feedback?: ChallengeFeedback }): VNode { + function ChallengeButton({ + id, + feedback, + }: { + id: string; + feedback?: ChallengeFeedback; + }): VNode { function selectChallenge(): void { - if (reducer) reducer.transition("select_challenge", { uuid: id }) + if (reducer) reducer.transition("select_challenge", { uuid: id }); } if (!feedback) { - return <div> - <button class="button" onClick={selectChallenge}> - Solve - </button> - </div> + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Solve + </button> + </div> + ); } switch (feedback.state) { case ChallengeFeedbackStatus.ServerFailure: case ChallengeFeedbackStatus.Unsupported: case ChallengeFeedbackStatus.TruthUnknown: - case ChallengeFeedbackStatus.RateLimitExceeded: return <div /> + case ChallengeFeedbackStatus.RateLimitExceeded: + return <div />; case ChallengeFeedbackStatus.AuthIban: - case ChallengeFeedbackStatus.Payment: return <div> - <button class="button" onClick={selectChallenge}> - Pay - </button> - </div> - case ChallengeFeedbackStatus.Redirect: return <div> - <button class="button" onClick={selectChallenge}> - Go to {feedback.redirect_url} - </button> - </div> - case ChallengeFeedbackStatus.Solved: return <div> - <div class="tag is-success is-large"> - Solved - </div> - </div> - default: return <div> - <button class="button" onClick={selectChallenge}> - Solve - </button> - </div> - + case ChallengeFeedbackStatus.Payment: + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Pay + </button> + </div> + ); + case ChallengeFeedbackStatus.Redirect: + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Go to {feedback.redirect_url} + </button> + </div> + ); + case ChallengeFeedbackStatus.Solved: + return ( + <div> + <div class="tag is-success is-large">Solved</div> + </div> + ); + default: + return ( + <div> + <button + class="button" + disabled={ + atLeastThereIsOnePolicySolved && !policy.isPolicySolved + } + onClick={selectChallenge} + > + Solve + </button> + </div> + ); } - // return <div> - // {feedback.state !== "solved" ? ( - // <a - // class="button" - // onClick={() => - - // } - // > - // {isFree ? "Solve" : `Pay and Solve`} - // </a> - // ) : null} - // {feedback.state === "solved" ? ( - // // <div class="block is-success" > Solved </div> - // <div class="tag is-success is-large">Solved</div> - - // ) : null} - // </div> } return ( <div @@ -202,7 +245,6 @@ export function ChallengeOverviewScreen(): VNode { </div> <ChallengeButton id={uuid} feedback={info.feedback} /> - </div> ); }); @@ -210,11 +252,13 @@ export function ChallengeOverviewScreen(): VNode { const policyName = policy.challenges .map((x) => x.info.type) .join(" + "); + const opa = !atLeastThereIsOnePolicySolved ? undefined : policy.isPolicySolved - ? undefined - : "0.6"; + ? undefined + : "0.6"; + return ( <div key={policy_index} diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx index fbcaa0e95..8c788e556 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx @@ -15,24 +15,26 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../utils'; -import { ChallengePayingScreen as TestedComponent } from './ChallengePayingScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../utils"; +import { ChallengePayingScreen as TestedComponent } from "./ChallengePayingScreen"; export default { - title: 'Pages/recovery/__ChallengePaying', + title: "Pages/recovery/__ChallengePaying", component: TestedComponent, args: { order: 10, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.challengePaying); +export const Example = createExample( + TestedComponent, + reducerStatesExample.challengePaying, +); diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx index 84896a2ec..ffcc8fafc 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx @@ -3,19 +3,19 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function ChallengePayingScreen(): 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 payments = ['']; //reducer.currentReducerState.payments ?? + const payments = [""]; //reducer.currentReducerState.payments ?? return ( - <AnastasisClientFrame - hideNav - title="Recovery: Challenge Paying" - > + <AnastasisClientFrame hideNav title="Recovery: Challenge Paying"> <p> Some of the providers require a payment to store the encrypted authentication information. diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx index 6bdb3515d..0948d603e 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx @@ -16,37 +16,42 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { ContinentSelectionScreen as TestedComponent } from "./ContinentSelectionScreen"; export default { - title: 'Pages/Location', + title: "Pages/Location", component: TestedComponent, args: { order: 2, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const BackupSelectContinent = createExample(TestedComponent, reducerStatesExample.backupSelectContinent); +export const BackupSelectContinent = createExample( + TestedComponent, + reducerStatesExample.backupSelectContinent, +); export const BackupSelectCountry = createExample(TestedComponent, { ...reducerStatesExample.backupSelectContinent, - selected_continent: 'Testcontinent', + selected_continent: "Testcontinent", } as ReducerState); -export const RecoverySelectContinent = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent); +export const RecoverySelectContinent = createExample( + TestedComponent, + reducerStatesExample.recoverySelectContinent, +); export const RecoverySelectCountry = createExample(TestedComponent, { ...reducerStatesExample.recoverySelectContinent, - selected_continent: 'Testcontinent', + selected_continent: "Testcontinent", } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx index 3d5fcce55..4cbeb8308 100644 --- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx @@ -16,94 +16,126 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { EditPoliciesScreen as TestedComponent } from "./EditPoliciesScreen"; export default { - title: 'Pages/backup/ReviewPolicies/EditPolicies', + title: "Pages/backup/ReviewPolicies/EditPolicies", args: { order: 6, }, component: TestedComponent, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const EditingAPolicy = createExample(TestedComponent, { - ...reducerStatesExample.policyReview, - policies: [{ - methods: [{ - authentication_method: 1, - provider: 'https://anastasis.demo.taler.net/' - }, { - authentication_method: 2, - provider: 'http://localhost:8086/' - }] - }, { - methods: [{ - authentication_method: 1, - provider: 'http://localhost:8086/' - }] - }], - authentication_methods: [{ - type: "email", - instructions: "Email to qwe@asd.com", - challenge: "E5VPA" - }, { - type: "totp", - instructions: "Response code for 'Anastasis'", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 6666-6666", - challenge: "" - }, { - type: "question", - instructions: "How did the chicken cross the road?", - challenge: "C5SP8" - }] -} as ReducerState, { index : 0}); - -export const CreatingAPolicy = createExample(TestedComponent, { - ...reducerStatesExample.policyReview, - policies: [{ - methods: [{ - authentication_method: 1, - provider: 'https://anastasis.demo.taler.net/' - }, { - authentication_method: 2, - provider: 'http://localhost:8086/' - }] - }, { - methods: [{ - authentication_method: 1, - provider: 'http://localhost:8086/' - }] - }], - authentication_methods: [{ - type: "email", - instructions: "Email to qwe@asd.com", - challenge: "E5VPA" - }, { - type: "totp", - instructions: "Response code for 'Anastasis'", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 6666-6666", - challenge: "" - }, { - type: "question", - instructions: "How did the chicken cross the road?", - challenge: "C5SP8" - }] -} as ReducerState, { index : 3}); +export const EditingAPolicy = createExample( + TestedComponent, + { + ...reducerStatesExample.policyReview, + policies: [ + { + methods: [ + { + authentication_method: 1, + provider: "https://anastasis.demo.taler.net/", + }, + { + authentication_method: 2, + provider: "http://localhost:8086/", + }, + ], + }, + { + methods: [ + { + authentication_method: 1, + provider: "http://localhost:8086/", + }, + ], + }, + ], + authentication_methods: [ + { + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA", + }, + { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "", + }, + { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8", + }, + ], + } as ReducerState, + { index: 0 }, +); +export const CreatingAPolicy = createExample( + TestedComponent, + { + ...reducerStatesExample.policyReview, + policies: [ + { + methods: [ + { + authentication_method: 1, + provider: "https://anastasis.demo.taler.net/", + }, + { + authentication_method: 2, + provider: "http://localhost:8086/", + }, + ], + }, + { + methods: [ + { + authentication_method: 1, + provider: "http://localhost:8086/", + }, + ], + }, + ], + authentication_methods: [ + { + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA", + }, + { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "", + }, + { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8", + }, + ], + } as ReducerState, + { index: 3 }, +); diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx index 85cc96c46..198209399 100644 --- a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx @@ -20,7 +20,6 @@ interface Props { index: number; cancel: () => void; confirm: (changes: MethodProvider[]) => void; - } export interface MethodProvider { @@ -28,106 +27,151 @@ export interface MethodProvider { provider: string; } -export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Props): VNode { - const [changedProvider, setChangedProvider] = useState<Array<string>>([]) +export function EditPoliciesScreen({ + index: policy_index, + cancel, + confirm, +}: Props): VNode { + const [changedProvider, setChangedProvider] = useState<Array<string>>([]); - 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.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } - const selectableProviders: ProviderInfoByType = {} - const allProviders = Object.entries(reducer.currentReducerState.authentication_providers || {}) + const selectableProviders: ProviderInfoByType = {}; + const allProviders = Object.entries( + reducer.currentReducerState.authentication_providers || {}, + ); for (let index = 0; index < allProviders.length; index++) { - const [url, status] = allProviders[index] + const [url, status] = allProviders[index]; if ("methods" in status) { - status.methods.map(m => { - const type: KnownAuthMethods = m.type as KnownAuthMethods - const values = selectableProviders[type] || [] - const isFree = !m.usage_fee || m.usage_fee.endsWith(":0") - values.push({ url, cost: m.usage_fee, isFree }) - selectableProviders[type] = values - }) + status.methods.map((m) => { + const type: KnownAuthMethods = m.type as KnownAuthMethods; + const values = selectableProviders[type] || []; + const isFree = !m.usage_fee || m.usage_fee.endsWith(":0"); + values.push({ url, cost: m.usage_fee, isFree }); + selectableProviders[type] = values; + }); } } - const allAuthMethods = reducer.currentReducerState.authentication_methods ?? []; + const allAuthMethods = + reducer.currentReducerState.authentication_methods ?? []; const policies = reducer.currentReducerState.policies ?? []; - const policy = policies[policy_index] - - for(let method_index = 0; method_index < allAuthMethods.length; method_index++ ) { - policy?.methods.find(m => m.authentication_method === method_index)?.provider + const policy = policies[policy_index]; + + for ( + let method_index = 0; + method_index < allAuthMethods.length; + method_index++ + ) { + policy?.methods.find((m) => m.authentication_method === method_index) + ?.provider; } function sendChanges(): void { - const newMethods: MethodProvider[] = [] + const newMethods: MethodProvider[] = []; allAuthMethods.forEach((method, index) => { - const oldValue = policy?.methods.find(m => m.authentication_method === index) + const oldValue = policy?.methods.find( + (m) => m.authentication_method === index, + ); if (changedProvider[index] === undefined && oldValue !== undefined) { - newMethods.push(oldValue) + newMethods.push(oldValue); } - if (changedProvider[index] !== undefined && changedProvider[index] !== "") { + if ( + changedProvider[index] !== undefined && + changedProvider[index] !== "" + ) { newMethods.push({ authentication_method: index, - provider: changedProvider[index] - }) + provider: changedProvider[index], + }); } - }) - confirm(newMethods) + }); + confirm(newMethods); } - return <AnastasisClientFrame hideNav title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}> - <section class="section"> - {!policy ? <p> - Creating a new policy #{policy_index} - </p> : <p> - Editing policy #{policy_index} - </p>} - {allAuthMethods.map((method, index) => { - //take the url from the updated change or from the policy - const providerURL = changedProvider[index] === undefined ? - policy?.methods.find(m => m.authentication_method === index)?.provider : - changedProvider[index]; + return ( + <AnastasisClientFrame + hideNav + title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"} + > + <section class="section"> + {!policy ? ( + <p>Creating a new policy #{policy_index}</p> + ) : ( + <p>Editing policy #{policy_index}</p> + )} + {allAuthMethods.map((method, index) => { + //take the url from the updated change or from the policy + const providerURL = + changedProvider[index] === undefined + ? policy?.methods.find((m) => m.authentication_method === index) + ?.provider + : changedProvider[index]; - const type: KnownAuthMethods = method.type as KnownAuthMethods - function changeProviderTo(url: string): void { - const copy = [...changedProvider] - copy[index] = url - setChangedProvider(copy) - } - return ( - <div key={index} class="block" style={{ display: 'flex', alignItems: 'center' }}> - <span class="icon"> - {authMethods[type]?.icon} - </span> - <span> - {method.instructions} - </span> - <span> - <span class="select " > - <select onChange={(e) => changeProviderTo(e.currentTarget.value)} value={providerURL ?? ""}> - <option key="none" value=""> << off >> </option> - {selectableProviders[type]?.map(prov => ( - <option key={prov.url} value={prov.url}> - {prov.url} + const type: KnownAuthMethods = method.type as KnownAuthMethods; + function changeProviderTo(url: string): void { + const copy = [...changedProvider]; + copy[index] = url; + setChangedProvider(copy); + } + return ( + <div + key={index} + class="block" + style={{ display: "flex", alignItems: "center" }} + > + <span class="icon">{authMethods[type]?.icon}</span> + <span>{method.instructions}</span> + <span> + <span class="select "> + <select + onChange={(e) => changeProviderTo(e.currentTarget.value)} + value={providerURL ?? ""} + > + <option key="none" value=""> + {" "} + << off >>{" "} </option> - ))} - </select> + {selectableProviders[type]?.map((prov) => ( + <option key={prov.url} value={prov.url}> + {prov.url} + </option> + ))} + </select> + </span> </span> - </span> - </div> - ); - })} - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> - <span class="buttons"> - <button class="button" onClick={() => setChangedProvider([])}>Reset</button> - <button class="button is-info" onClick={sendChanges}>Confirm</button> - </span> - </div> - </section> - </AnastasisClientFrame> + </div> + ); + })} + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={cancel}> + Cancel + </button> + <span class="buttons"> + <button class="button" onClick={() => setChangedProvider([])}> + Reset + </button> + <button class="button is-info" onClick={sendChanges}> + Confirm + </button> + </span> + </div> + </section> + </AnastasisClientFrame> + ); } diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx index 3ddf8011e..9bebcfbc9 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx @@ -15,35 +15,40 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { PoliciesPayingScreen as TestedComponent } from "./PoliciesPayingScreen"; export default { - title: 'Pages/backup/__PoliciesPaying', + title: "Pages/backup/__PoliciesPaying", component: TestedComponent, args: { order: 9, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.policyPay); +export const Example = createExample( + TestedComponent, + reducerStatesExample.policyPay, +); export const WithSomePaymentRequest = createExample(TestedComponent, { ...reducerStatesExample.policyPay, - policy_payment_requests: [{ - payto: 'payto://x-taler-bank/bank.taler/account-a', - provider: 'provider1' - }, { - payto: 'payto://x-taler-bank/bank.taler/account-b', - provider: 'provider2' - }] + policy_payment_requests: [ + { + payto: "payto://x-taler-bank/bank.taler/account-a", + provider: "provider1", + }, + { + payto: "payto://x-taler-bank/bank.taler/account-b", + provider: "provider2", + }, + ], } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx index a470f5155..c3568b32d 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.tsx @@ -3,20 +3,23 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function PoliciesPayingScreen(): 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.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } const payments = reducer.currentReducerState.policy_payment_requests ?? []; - + return ( <AnastasisClientFrame hideNav title="Backup: Recovery Document Payments"> <p> - Some of the providers require a payment to store the encrypted - recovery document. + Some of the providers require a payment to store the encrypted recovery + document. </p> <ul> {payments.map((x, i) => { diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx index e92a231a8..47860db29 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx @@ -16,30 +16,32 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { RecoveryFinishedScreen as TestedComponent } from "./RecoveryFinishedScreen"; export default { - title: 'Pages/recovery/Finished', + title: "Pages/recovery/Finished", args: { order: 7, }, component: TestedComponent, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const GoodEnding = createExample(TestedComponent, { ...reducerStatesExample.recoveryFinished, - core_secret: { mime: 'text/plain', value: 'hello' } + core_secret: { mime: "text/plain", value: "hello" }, } as ReducerState); -export const BadEnding = createExample(TestedComponent, reducerStatesExample.recoveryFinished); +export const BadEnding = createExample( + TestedComponent, + reducerStatesExample.recoveryFinished, +); diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx index a61ef9efa..11ae09d4f 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.tsx @@ -1,39 +1,53 @@ -import { - bytesToString, - decodeCrock -} from "@gnu-taler/taler-util"; +import { bytesToString, decodeCrock } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function RecoveryFinishedScreen(): 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 encodedSecret = reducer.currentReducerState.core_secret + const encodedSecret = reducer.currentReducerState.core_secret; if (!encodedSecret) { - return <AnastasisClientFrame title="Recovery Problem" hideNav> - <p> - Secret not found - </p> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> - </div> - </AnastasisClientFrame> + return ( + <AnastasisClientFrame title="Recovery Problem" hideNav> + <p>Secret not found</p> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> + </div> + </AnastasisClientFrame> + ); } - const secret = bytesToString(decodeCrock(encodedSecret.value)) + const secret = bytesToString(decodeCrock(encodedSecret.value)); return ( <AnastasisClientFrame title="Recovery Finished" hideNav> - <p> - Secret: {secret} - </p> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={() => reducer.back()}>Back</button> + <p>Your secret: {secret}</p> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx index e348101ee..4a1cba6a8 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx @@ -15,44 +15,51 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { ReviewPoliciesScreen as TestedComponent } from "./ReviewPoliciesScreen"; export default { - title: 'Pages/backup/ReviewPolicies', + title: "Pages/backup/ReviewPolicies", args: { order: 6, }, component: TestedComponent, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const HasPoliciesButMethodListIsEmpty = createExample(TestedComponent, { ...reducerStatesExample.policyReview, - policies: [{ - methods: [{ - authentication_method: 0, - provider: 'asd' - }, { - authentication_method: 1, - provider: 'asd' - }] - }, { - methods: [{ - authentication_method: 1, - provider: 'asd' - }] - }], - authentication_methods: [] + policies: [ + { + methods: [ + { + authentication_method: 0, + provider: "asd", + }, + { + authentication_method: 1, + provider: "asd", + }, + ], + }, + { + methods: [ + { + authentication_method: 1, + provider: "asd", + }, + ], + }, + ], + authentication_methods: [], } as ReducerState); export const SomePoliciesWithMethods = createExample(TestedComponent, { @@ -62,186 +69,193 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" - } - ] + provider: "https://kudos.demo.anastasis.lu/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 0, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" + provider: "https://anastasis.demo.taler.net/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 1, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" + provider: "https://anastasis.demo.taler.net/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] + provider: "https://anastasis.demo.taler.net/", + }, + ], }, { methods: [ { authentication_method: 2, - provider: "https://kudos.demo.anastasis.lu/" + provider: "https://kudos.demo.anastasis.lu/", }, { authentication_method: 3, - provider: "https://anastasis.demo.taler.net/" + provider: "https://anastasis.demo.taler.net/", }, { authentication_method: 4, - provider: "https://anastasis.demo.taler.net/" - } - ] - } + provider: "https://anastasis.demo.taler.net/", + }, + ], + }, + ], + authentication_methods: [ + { + type: "email", + instructions: "Email to qwe@asd.com", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 555-555", + challenge: "", + }, + { + type: "question", + instructions: "Does P equal NP?", + challenge: "C5SP8", + }, + { + type: "totp", + instructions: "Response code for 'Anastasis'", + challenge: "E5VPA", + }, + { + type: "sms", + instructions: "SMS to 6666-6666", + challenge: "", + }, + { + type: "question", + instructions: "How did the chicken cross the road?", + challenge: "C5SP8", + }, ], - authentication_methods: [{ - type: "email", - instructions: "Email to qwe@asd.com", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 555-555", - challenge: "" - }, { - type: "question", - instructions: "Does P equal NP?", - challenge: "C5SP8" - },{ - type: "totp", - instructions: "Response code for 'Anastasis'", - challenge: "E5VPA" - }, { - type: "sms", - instructions: "SMS to 6666-6666", - challenge: "" - }, { - type: "question", - instructions: "How did the chicken cross the road?", - challenge: "C5SP8" -}] } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx index aa98b5dd9..c43f0cdea 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -6,16 +6,20 @@ import { EditPoliciesScreen } from "./EditPoliciesScreen"; import { AnastasisClientFrame } from "./index"; export function ReviewPoliciesScreen(): VNode { - const [editingPolicy, setEditingPolicy] = useState<number | undefined>() - const reducer = useAnastasisContext() + const [editingPolicy, setEditingPolicy] = useState<number | undefined>(); + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } - if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } - const configuredAuthMethods = reducer.currentReducerState.authentication_methods ?? []; + const configuredAuthMethods = + reducer.currentReducerState.authentication_methods ?? []; const policies = reducer.currentReducerState.policies ?? []; if (editingPolicy !== undefined) { @@ -28,58 +32,109 @@ export function ReviewPoliciesScreen(): VNode { policy_index: editingPolicy, policy: newMethods, }); - setEditingPolicy(undefined) + setEditingPolicy(undefined); }} /> - ) + ); } - const errors = policies.length < 1 ? 'Need more policies' : undefined + const errors = policies.length < 1 ? "Need more policies" : undefined; return ( - <AnastasisClientFrame hideNext={errors} title="Backup: Review Recovery Policies"> - {policies.length > 0 && <p class="block"> - Based on your configured authentication method you have created, some policies - have been configured. In order to recover your secret you have to solve all the - challenges of at least one policy. - </p>} - {policies.length < 1 && <p class="block"> - No policies had been created. Go back and add more authentication methods. - </p>} - <div class="block" style={{ justifyContent: 'flex-end' }} > - <button class="button is-success" onClick={() => setEditingPolicy(policies.length + 1)}>Add new policy</button> + <AnastasisClientFrame + hideNext={errors} + title="Backup: Review Recovery Policies" + > + {policies.length > 0 && ( + <p class="block"> + Based on your configured authentication method you have created, some + policies have been configured. In order to recover your secret you + have to solve all the challenges of at least one policy. + </p> + )} + {policies.length < 1 && ( + <p class="block"> + No policies had been created. Go back and add more authentication + methods. + </p> + )} + <div class="block" style={{ justifyContent: "flex-end" }}> + <button + class="button is-success" + onClick={() => setEditingPolicy(policies.length + 1)} + > + Add new policy + </button> </div> {policies.map((p, policy_index) => { const methods = p.methods - .map(x => configuredAuthMethods[x.authentication_method] && ({ ...configuredAuthMethods[x.authentication_method], provider: x.provider })) - .filter(x => !!x) + .map( + (x) => + configuredAuthMethods[x.authentication_method] && { + ...configuredAuthMethods[x.authentication_method], + provider: x.provider, + }, + ) + .filter((x) => !!x); - const policyName = methods.map(x => x.type).join(" + "); + const policyName = methods.map((x) => x.type).join(" + "); + + if (p.methods.length > methods.length) { + //there is at least one authentication method that is corrupted + return null; + } return ( - <div key={policy_index} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}> + <div + key={policy_index} + class="box" + style={{ display: "flex", justifyContent: "space-between" }} + > <div> <h3 class="subtitle"> Policy #{policy_index + 1}: {policyName} </h3> - {!methods.length && <p> - No auth method found - </p>} + {!methods.length && <p>No auth method found</p>} {methods.map((m, i) => { return ( - <p key={i} class="block" style={{ display: 'flex', alignItems: 'center' }}> + <p + key={i} + class="block" + style={{ display: "flex", alignItems: "center" }} + > <span class="icon"> {authMethods[m.type as KnownAuthMethods]?.icon} </span> <span> - {m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a> + {m.instructions} recovery provided by{" "} + <a href={m.provider}>{m.provider}</a> </span> </p> ); })} </div> - <div style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}> - <button class="button is-info block" onClick={() => setEditingPolicy(policy_index)}>Edit</button> - <button class="button is-danger block" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button> + <div + style={{ + marginTop: "auto", + marginBottom: "auto", + display: "flex", + justifyContent: "space-between", + flexDirection: "column", + }} + > + <button + class="button is-info block" + onClick={() => setEditingPolicy(policy_index)} + > + Edit + </button> + <button + class="button is-danger block" + onClick={() => + reducer.transition("delete_policy", { policy_index }) + } + > + Delete + </button> </div> </div> ); diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx index db061d936..3f2c6a245 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx @@ -15,30 +15,29 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { SecretEditorScreen as TestedComponent } from "./SecretEditorScreen"; export default { - title: 'Pages/backup/SecretInput', + title: "Pages/backup/SecretInput", component: TestedComponent, args: { order: 7, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const WithSecretNamePreselected = createExample(TestedComponent, { ...reducerStatesExample.secretEdition, - secret_name: 'someSecretName', + secret_name: "someSecretName", } as ReducerState); export const WithoutName = createExample(TestedComponent, { diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx index 8d02ebfbe..01ce3f0a7 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx @@ -15,37 +15,35 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { SecretSelectionScreen as TestedComponent } from "./SecretSelectionScreen"; export default { - title: 'Pages/recovery/SecretSelection', + title: "Pages/recovery/SecretSelection", component: TestedComponent, args: { order: 4, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const Example = createExample(TestedComponent, { ...reducerStatesExample.secretSelection, recovery_document: { - provider_url: 'https://kudos.demo.anastasis.lu/', - secret_name: 'secretName', + provider_url: "https://kudos.demo.anastasis.lu/", + secret_name: "secretName", version: 1, }, } as ReducerState); - export const NoRecoveryDocumentFound = createExample(TestedComponent, { ...reducerStatesExample.secretSelection, recovery_document: undefined, diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 398393619..4000f9bfe 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -8,18 +8,23 @@ import { AnastasisClientFrame } from "./index"; export function SecretSelectionScreen(): VNode { const [selectingVersion, setSelectingVersion] = useState<boolean>(false); - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); - const [manageProvider, setManageProvider] = useState(false) - const currentVersion = (reducer?.currentReducerState - && ("recovery_document" in reducer.currentReducerState) - && reducer.currentReducerState.recovery_document?.version) || 0; + const [manageProvider, setManageProvider] = useState(false); + const currentVersion = + (reducer?.currentReducerState && + "recovery_document" in reducer.currentReducerState && + reducer.currentReducerState.recovery_document?.version) || + 0; 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>; } async function doSelectVersion(p: string, n: number): Promise<void> { @@ -33,72 +38,101 @@ export function SecretSelectionScreen(): VNode { }); } - const providerList = Object.keys(reducer.currentReducerState.authentication_providers ?? {}) - const recoveryDocument = reducer.currentReducerState.recovery_document + const providerList = Object.keys( + reducer.currentReducerState.authentication_providers ?? {}, + ); + const recoveryDocument = reducer.currentReducerState.recovery_document; if (!recoveryDocument) { - return <ChooseAnotherProviderScreen - providers={providerList} selected="" - onChange={(newProv) => doSelectVersion(newProv, 0)} - /> + return ( + <ChooseAnotherProviderScreen + providers={providerList} + selected="" + onChange={(newProv) => doSelectVersion(newProv, 0)} + /> + ); } if (selectingVersion) { - return <SelectOtherVersionProviderScreen providers={providerList} - provider={recoveryDocument.provider_url} version={recoveryDocument.version} - onCancel={() => setSelectingVersion(false)} - onConfirm={doSelectVersion} - /> + return ( + <SelectOtherVersionProviderScreen + providers={providerList} + provider={recoveryDocument.provider_url} + version={recoveryDocument.version} + onCancel={() => setSelectingVersion(false)} + onConfirm={doSelectVersion} + /> + ); } if (manageProvider) { - return <AddingProviderScreen onCancel={() => setManageProvider(false)} /> + return <AddingProviderScreen onCancel={() => setManageProvider(false)} />; } return ( <AnastasisClientFrame title="Recovery: Select secret"> <div class="columns"> <div class="column"> - <div class="box" style={{ border: '2px solid green' }}> + <div class="box" style={{ border: "2px solid green" }}> <h1 class="subtitle">{recoveryDocument.provider_url}</h1> <div class="block"> - {currentVersion === 0 ? <p> - Set to recover the latest version - </p> : <p> - Set to recover the version number {currentVersion} - </p>} + {currentVersion === 0 ? ( + <p>Set to recover the latest version</p> + ) : ( + <p>Set to recover the version number {currentVersion}</p> + )} </div> <div class="buttons is-right"> - <button class="button" onClick={(e) => setSelectingVersion(true)}>Change secret's version</button> + <button class="button" onClick={(e) => setSelectingVersion(true)}> + Change secret's version + </button> </div> </div> </div> <div class="column"> - <p>Secret found, you can select another version or continue to the challenges solving</p> + <p> + Secret found, you can select another version or continue to the + challenges solving + </p> <p class="block"> - <button class="button is-info" onClick={() => setManageProvider(true)}> + <a onClick={() => setManageProvider(true)}> Manage recovery providers - </button> + </a> </p> - </div> </div> </AnastasisClientFrame> ); } - -function ChooseAnotherProviderScreen({ providers, selected, onChange }: { selected: string; providers: string[]; onChange: (prov: string) => void }): VNode { +function ChooseAnotherProviderScreen({ + providers, + selected, + onChange, +}: { + selected: string; + providers: string[]; + onChange: (prov: string) => void; +}): VNode { return ( - <AnastasisClientFrame hideNext="Recovery document not found" title="Recovery: Problem"> + <AnastasisClientFrame + hideNext="Recovery document not found" + title="Recovery: Problem" + > <p>No recovery document found, try with another provider</p> <div class="field"> <label class="label">Provider</label> <div class="control is-expanded has-icons-left"> <div class="select is-fullwidth"> - <select onChange={(e) => onChange(e.currentTarget.value)} value={selected}> - <option key="none" disabled selected value=""> Choose a provider </option> - {providers.map(prov => ( + <select + onChange={(e) => onChange(e.currentTarget.value)} + value={selected} + > + <option key="none" disabled selected value=""> + {" "} + Choose a provider{" "} + </option> + {providers.map((prov) => ( <option key={prov} value={prov}> {prov} </option> @@ -114,9 +148,23 @@ function ChooseAnotherProviderScreen({ providers, selected, onChange }: { select ); } -function SelectOtherVersionProviderScreen({ providers, provider, version, onConfirm, onCancel }: { onCancel: () => void; provider: string; version: number; providers: string[]; onConfirm: (prov: string, v: number) => Promise<void>; }): VNode { +function SelectOtherVersionProviderScreen({ + providers, + provider, + version, + onConfirm, + onCancel, +}: { + onCancel: () => void; + provider: string; + version: number; + providers: string[]; + onConfirm: (prov: string, v: number) => Promise<void>; +}): VNode { const [otherProvider, setOtherProvider] = useState<string>(provider); - const [otherVersion, setOtherVersion] = useState(version > 0 ? String(version) : ""); + const [otherVersion, setOtherVersion] = useState( + version > 0 ? String(version) : "", + ); return ( <AnastasisClientFrame hideNav title="Recovery: Select secret"> @@ -125,11 +173,11 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf <div class="box"> <h1 class="subtitle">Provider {otherProvider}</h1> <div class="block"> - {version === 0 ? <p> - Set to recover the latest version - </p> : <p> - Set to recover the version number {version} - </p>} + {version === 0 ? ( + <p>Set to recover the latest version</p> + ) : ( + <p>Set to recover the version number {version}</p> + )} <p>Specify other version below or use the latest</p> </div> @@ -137,9 +185,15 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf <label class="label">Provider</label> <div class="control is-expanded has-icons-left"> <div class="select is-fullwidth"> - <select onChange={(e) => setOtherProvider(e.currentTarget.value)} value={otherProvider}> - <option key="none" disabled selected value=""> Choose a provider </option> - {providers.map(prov => ( + <select + onChange={(e) => setOtherProvider(e.currentTarget.value)} + value={otherProvider} + > + <option key="none" disabled selected value=""> + {" "} + Choose a provider{" "} + </option> + {providers.map((prov) => ( <option key={prov} value={prov}> {prov} </option> @@ -156,23 +210,40 @@ function SelectOtherVersionProviderScreen({ providers, provider, version, onConf label="Version" placeholder="version number to recover" grabFocus - bind={[otherVersion, setOtherVersion]} /> + bind={[otherVersion, setOtherVersion]} + /> </div> </div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={onCancel}>Cancel</button> + <div + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={onCancel}> + Cancel + </button> <div class="buttons"> - <AsyncButton class="button" onClick={() => onConfirm(otherProvider, 0)}>Use latest</AsyncButton> - <AsyncButton class="button is-info" onClick={() => onConfirm(otherProvider, parseInt(otherVersion, 10))}>Confirm</AsyncButton> + <AsyncButton + class="button" + onClick={() => onConfirm(otherProvider, 0)} + > + Use latest + </AsyncButton> + <AsyncButton + class="button is-info" + onClick={() => + onConfirm(otherProvider, parseInt(otherVersion, 10)) + } + > + Confirm + </AsyncButton> </div> </div> </div> - <div class="column"> - . - </div> + <div class="column">.</div> </div> - </AnastasisClientFrame> ); - } diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx index f1f2802ae..76d0700db 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx @@ -15,55 +15,63 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, RecoveryStates, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { SolveScreen as TestedComponent } from './SolveScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { + ChallengeFeedbackStatus, + RecoveryStates, + ReducerState, +} from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { SolveScreen as TestedComponent } from "./SolveScreen"; export default { - title: 'Pages/recovery/SolveChallenge/Solve', + title: "Pages/recovery/SolveChallenge/Solve", component: TestedComponent, args: { order: 6, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const NoInformation = createExample(TestedComponent, reducerStatesExample.challengeSolving); +export const NoInformation = createExample( + TestedComponent, + reducerStatesExample.challengeSolving, +); export const NotSupportedChallenge = createExample(TestedComponent, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'chall-type', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "chall-type", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', - + selected_challenge_uuid: "ASDASDSAD!1", } as ReducerState); export const MismatchedChallengeId = createExample(TestedComponent, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'chall-type', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "chall-type", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'no-no-no' + selected_challenge_uuid: "no-no-no", } as ReducerState); - diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 5e17c9aa1..b87dad2ce 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -2,76 +2,126 @@ import { h, VNode } from "preact"; import { AnastasisClientFrame } from "."; import { ChallengeFeedback, - ChallengeFeedbackStatus + ChallengeFeedbackStatus, } from "../../../../anastasis-core/lib"; import { Notifications } from "../../components/Notifications"; import { useAnastasisContext } from "../../context/anastasis"; import { authMethods, KnownAuthMethods } from "./authMethod"; -export function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }): VNode { +export function SolveOverviewFeedbackDisplay(props: { + feedback?: ChallengeFeedback; +}): VNode { const { feedback } = props; if (!feedback) { return <div />; } switch (feedback.state) { case ChallengeFeedbackStatus.Message: - return (<Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: feedback.message - }]} />); + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: feedback.message, + }, + ]} + /> + ); case ChallengeFeedbackStatus.Payment: - return <Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: <span> - To pay you can <a href={feedback.taler_pay_uri}>click here</a> - </span> - }]} /> + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: ( + <span> + To pay you can <a href={feedback.taler_pay_uri}>click here</a> + </span> + ), + }, + ]} + /> + ); case ChallengeFeedbackStatus.AuthIban: - return <Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: `Need to send a wire transfer to "${feedback.business_name}"` - }]} />; + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: `Need to send a wire transfer to "${feedback.business_name}"`, + }, + ]} + /> + ); case ChallengeFeedbackStatus.ServerFailure: - return (<Notifications notifications={[{ - type: "ERROR", - message: `Server error: Code ${feedback.http_status}`, - description: feedback.error_response - }]} />); + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `Server error: Code ${feedback.http_status}`, + description: feedback.error_response, + }, + ]} + /> + ); case ChallengeFeedbackStatus.RateLimitExceeded: - return (<Notifications notifications={[{ - type: "ERROR", - message: `Message from provider`, - description: "There were to many failed attempts." - }]} />); + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `Message from provider`, + description: "There were to many failed attempts.", + }, + ]} + /> + ); case ChallengeFeedbackStatus.Redirect: - return (<Notifications notifications={[{ - type: "INFO", - message: `Message from provider`, - description: <span> - Please visit this link: <a>{feedback.redirect_url}</a> - </span> - }]} />); + return ( + <Notifications + notifications={[ + { + type: "INFO", + message: `Message from provider`, + description: ( + <span> + Please visit this link: <a>{feedback.redirect_url}</a> + </span> + ), + }, + ]} + /> + ); case ChallengeFeedbackStatus.Unsupported: - return (<Notifications notifications={[{ - type: "ERROR", - message: `This client doesn't support solving this type of challenge`, - description: `Use another version or contact the provider. Type of challenge "${feedback.unsupported_method}"` - }]} />); + return ( + <Notifications + notifications={[ + { + type: "ERROR", + message: `This client doesn't support solving this type of challenge`, + description: `Use another version or contact the provider. Type of challenge "${feedback.unsupported_method}"`, + }, + ]} + /> + ); case ChallengeFeedbackStatus.TruthUnknown: - return (<Notifications notifications={[{ - type: "ERROR", - message: `Provider doesn't recognize the type of challenge`, - description: "Contact the provider for further information" - }]} />); - default: return ( - <div> - <pre>{JSON.stringify(feedback)}</pre> - </div> + <Notifications + notifications={[ + { + type: "ERROR", + message: `Provider doesn't recognize the type of challenge`, + description: "Contact the provider for further information", + }, + ]} + /> ); + default: + return <div />; } } @@ -110,8 +160,16 @@ export function SolveScreen(): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -120,26 +178,36 @@ export function SolveScreen(): 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. + 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> + {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 selectedUuid = reducer.currentReducerState.selected_challenge_uuid; - const selectedChallenge = chArr.find(ch => ch.uuid === selectedUuid) + const selectedChallenge = chArr.find((ch) => ch.uuid === selectedUuid); - const SolveDialog = !selectedChallenge || !authMethods[selectedChallenge.type as KnownAuthMethods] ? - SolveNotImplemented : - authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? SolveNotImplemented + const SolveDialog = + !selectedChallenge || + !authMethods[selectedChallenge.type as KnownAuthMethods] + ? SolveNotImplemented + : authMethods[selectedChallenge.type as KnownAuthMethods].solve ?? + SolveNotImplemented; - return <SolveDialog id={selectedUuid} /> + return <SolveDialog id={selectedUuid} />; } diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx index 41082c128..fcddaf87a 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx @@ -15,24 +15,26 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../utils'; -import { StartScreen as TestedComponent } from './StartScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../utils"; +import { StartScreen as TestedComponent } from "./StartScreen"; export default { - title: 'Pages/Start', + title: "Pages/Start", component: TestedComponent, args: { order: 1, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const InitialState = createExample(TestedComponent, reducerStatesExample.initial);
\ No newline at end of file +export const InitialState = createExample( + TestedComponent, + reducerStatesExample.initial, +); diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx index d53df4cae..8b24ef684 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx @@ -1,27 +1,36 @@ - import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function StartScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return <div>no reducer in context</div> + return <div>no reducer in context</div>; } return ( <AnastasisClientFrame hideNav title="Home"> <div class="columns"> <div class="column" /> <div class="column is-four-fifths"> - <div class="buttons"> - <button class="button is-success" autoFocus onClick={() => reducer.startBackup()}> - <div class="icon"><i class="mdi mdi-arrow-up" /></div> + <button + class="button is-success" + autoFocus + onClick={() => reducer.startBackup()} + > + <div class="icon"> + <i class="mdi mdi-arrow-up" /> + </div> <span>Backup a secret</span> </button> - <button class="button is-info" onClick={() => reducer.startRecover()}> - <div class="icon"><i class="mdi mdi-arrow-down" /></div> + <button + class="button is-info" + onClick={() => reducer.startRecover()} + > + <div class="icon"> + <i class="mdi mdi-arrow-down" /> + </div> <span>Recover a secret</span> </button> @@ -30,7 +39,6 @@ export function StartScreen(): VNode { <span>Restore a session</span> </button> */} </div> - </div> <div class="column" /> </div> diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx index 38b71bc36..245ad8889 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx @@ -15,29 +15,31 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { TruthsPayingScreen as TestedComponent } from "./TruthsPayingScreen"; export default { - title: 'Pages/backup/__TruthsPaying', + title: "Pages/backup/__TruthsPaying", component: TestedComponent, args: { order: 10, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -export const Example = createExample(TestedComponent, reducerStatesExample.truthsPaying); +export const Example = createExample( + TestedComponent, + reducerStatesExample.truthsPaying, +); export const WithPaytoList = createExample(TestedComponent, { ...reducerStatesExample.truthsPaying, - payments: ['payto://x-taler-bank/bank/account'] + payments: ["payto://x-taler-bank/bank/account"], } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx index 0b32e0db5..6f95fa93b 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx @@ -3,19 +3,19 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; export function TruthsPayingScreen(): 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.backup_state === undefined) { - return <div>invalid state</div> + if ( + !reducer.currentReducerState || + reducer.currentReducerState.backup_state === undefined + ) { + return <div>invalid state</div>; } const payments = reducer.currentReducerState.payments ?? []; return ( - <AnastasisClientFrame - hideNext={"FIXME"} - title="Backup: Truths Paying" - > + <AnastasisClientFrame hideNext={"FIXME"} title="Backup: Truths Paying"> <p> Some of the providers require a payment to store the encrypted authentication information. diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx index da87b7a8b..080a7ab31 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx @@ -15,51 +15,67 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/email', + title: "Pages/backup/AuthorizationMethod/AuthMethods/email", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'email' +const type: KnownAuthMethods = "email"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Email to sebasjm@email.com ', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Email to sebasjm@email.com ", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Email to sebasjm@email.com', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Email to someone@sebasjm.com', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Email to sebasjm@email.com", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "Email to someone@sebasjm.com", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx index 27a0685b2..61c66c8c8 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx @@ -1,57 +1,90 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; 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,}))$/; -export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { +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 + 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. + email. Add the uuid from the challenge </p> <div> <EmailInput label="Email address" error={emailError} placeholder="email@domain.com" - bind={[email, setEmail]} /> + 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>} + {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> + <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> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addEmailAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx index 525cd2b07..6a8a2a347 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.stories.tsx @@ -15,66 +15,76 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/email", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'email' +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: [], +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", }, - 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: [], +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", }, - 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', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx index bd4f43740..ff6c51d1c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,19 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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 + An email has been sent to "<b>{selectedChallenge.instructions}</b>". + Type the code below. + <b>Here we need to add the code "{selectedUuid}"</b> </p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> @@ -97,9 +105,11 @@ export function AuthMethodEmailSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx index be0a04847..c521e18fd 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx @@ -15,50 +15,66 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/IBAN', + title: "Pages/backup/AuthorizationMethod/AuthMethods/IBAN", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'iban' +const type: KnownAuthMethods = "iban"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', - remove: () => null - }] -}); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Javier', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', - remove: () => null - }] -},); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Wire transfer from QWEASD123123 with holder Sebastian", + remove: () => null, + }, + ], + }, +); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Wire transfer from QWEASD123123 with holder Javier", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "Wire transfer from QWEASD123123 with holder Sebastian", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx index 87969ab27..dee550e5b 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx @@ -1,7 +1,7 @@ import { canonicalJson, encodeCrock, - stringToBytes + stringToBytes, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -9,56 +9,98 @@ import { AuthMethodSetupProps } from "."; import { TextInput } from "../../../components/fields/TextInput"; import { AnastasisClientFrame } from "../index"; -export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodIbanSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [name, setName] = useState(""); const [account, setAccount] = useState(""); - const addIbanAuth = (): void => addAuthMethod({ - authentication_method: { - type: "iban", - instructions: `Wire transfer from ${account} with holder ${name}`, - challenge: encodeCrock(stringToBytes(canonicalJson({ - name, account - }))), - }, - }); - const errors = !name ? 'Add an account name' : ( - !account ? 'Add an account IBAN number' : undefined - ) + const addIbanAuth = (): void => + addAuthMethod({ + authentication_method: { + type: "iban", + instructions: `Wire transfer from ${account} with holder ${name}`, + challenge: encodeCrock( + stringToBytes( + canonicalJson({ + name, + account, + }), + ), + ), + }, + }); + const errors = !name + ? "Add an account name" + : !account + ? "Add an account IBAN number" + : undefined; return ( <AnastasisClientFrame hideNav title="Add bank transfer authentication"> <p> - For bank transfer authentication, you need to provide a bank - account (account holder name and IBAN). When recovering your - secret, you will be asked to pay the recovery fee via bank - transfer from the account you provided here. + For bank transfer authentication, you need to provide a bank account + (account holder name and IBAN). When recovering your secret, you will be + asked to pay the recovery fee via bank transfer from the account you + provided here. </p> <div> <TextInput label="Bank account holder name" grabFocus placeholder="John Smith" - bind={[name, setName]} /> + bind={[name, setName]} + /> <TextInput label="IBAN" placeholder="DE91100000000123456789" - bind={[account, setAccount]} /> + bind={[account, setAccount]} + /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your bank accounts: - </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>} + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your bank accounts:</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> + <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={addIbanAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addIbanAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx index df73a9214..cbbc253e9 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/Iban", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + 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: [], +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", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx index 1e4353da6..46cf0502c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,19 +79,17 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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]} /> + <p>Send a wire transfer to the address,</p> + <button class="button">Check</button> <div style={{ @@ -96,9 +101,11 @@ export function AuthMethodIbanSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx index adc83d6fe..2977586ac 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx @@ -16,51 +16,67 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Post', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Post", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'post' +const type: KnownAuthMethods = "post"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code QWE456', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Letter to address in postal code QWE456", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code QWE456', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code ABC123', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Letter to address in postal code QWE456", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "Letter to address in postal code ABC123", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx index 692421d74..6c8d36bcf 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx @@ -1,6 +1,7 @@ import { - canonicalJson, encodeCrock, - stringToBytes + canonicalJson, + encodeCrock, + stringToBytes, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -8,7 +9,11 @@ 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 [street, setStreet] = useState(""); const [city, setCity] = useState(""); @@ -32,68 +37,83 @@ export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthM }); }; - const errors = !fullName ? 'The full name is missing' : ( - !street ? 'The street is missing' : ( - !city ? 'The city is missing' : ( - !postcode ? 'The postcode is missing' : ( - !country ? 'The country is missing' : undefined - ) - ) - ) - ) + const errors = !fullName + ? "The full name is missing" + : !street + ? "The street is missing" + : !city + ? "The city is missing" + : !postcode + ? "The postcode is missing" + : !country + ? "The country is missing" + : undefined; return ( <AnastasisClientFrame hideNav title="Add postal authentication"> <p> - For postal letter authentication, you need to provide a postal - address. When recovering your secret, you will be asked to enter a - code that you will receive in a letter to that address. + For postal letter authentication, you need to provide a postal address. + When recovering your secret, you will be asked to enter a code that you + will receive in a letter to that address. </p> <div> - <TextInput - grabFocus - label="Full Name" - bind={[fullName, setFullName]} - /> + <TextInput grabFocus label="Full Name" bind={[fullName, setFullName]} /> </div> <div> - <TextInput - label="Street" - bind={[street, setStreet]} - /> + <TextInput label="Street" bind={[street, setStreet]} /> </div> <div> - <TextInput - label="City" bind={[city, setCity]} - /> + <TextInput label="City" bind={[city, setCity]} /> </div> <div> - <TextInput - label="Postal Code" bind={[postcode, setPostcode]} - /> + <TextInput label="Postal Code" bind={[postcode, setPostcode]} /> </div> <div> - <TextInput - label="Country" - bind={[country, setCountry]} - /> + <TextInput label="Country" bind={[country, setCountry]} /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your postal code: - </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 style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your postal code:</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 + 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={addPostAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addPostAuth} + > + Add + </button> </span> </div> </AnastasisClientFrame> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx index 99451090b..3b67ee884 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/post", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + 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: [], +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", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx index 7e3c45abe..ee001ebe9 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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> + <p>Wait for the answer</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodPostSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx index 0c3ee2b77..991301cbf 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx @@ -16,51 +16,69 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Question', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Question", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'question' +const type: KnownAuthMethods = "question"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Is integer factorization polynomial? (non-quantum computer)', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: + "Is integer factorization polynomial? (non-quantum computer)", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Does P equal NP?', - remove: () => null - },{ - challenge: 'asd', - type, - instructions: 'Are continuous groups automatically differential groups?', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "Does P equal NP?", + remove: () => null, + }, + { + challenge: "asd", + type, + instructions: + "Are continuous groups automatically differential groups?", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx index 780bfcb82..0a14021dd 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx @@ -1,27 +1,31 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./index"; import { AnastasisClientFrame } from "../index"; import { TextInput } from "../../../components/fields/TextInput"; -export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodQuestionSetup({ + cancel, + addAuthMethod, + configured, +}: AuthMethodSetupProps): VNode { const [questionText, setQuestionText] = useState(""); const [answerText, setAnswerText] = useState(""); - const addQuestionAuth = (): void => addAuthMethod({ - authentication_method: { - type: "question", - instructions: questionText, - challenge: encodeCrock(stringToBytes(answerText)), - }, - }); + const addQuestionAuth = (): void => + addAuthMethod({ + authentication_method: { + type: "question", + instructions: questionText, + challenge: encodeCrock(stringToBytes(answerText)), + }, + }); - const errors = !questionText ? "Add your security question" : ( - !answerText ? 'Add the answer to your question' : undefined - ) + const errors = !questionText + ? "Add your security question" + : !answerText + ? "Add the answer to your question" + : undefined; return ( <AnastasisClientFrame hideNav title="Add Security Question"> <div> @@ -36,7 +40,8 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A label="Security question" grabFocus placeholder="Your question" - bind={[questionText, setQuestionText]} /> + bind={[questionText, setQuestionText]} + /> </div> <div> <TextInput @@ -46,25 +51,53 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A /> </div> - <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + <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={addQuestionAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addQuestionAuth} + > + Add + </button> </span> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your security questions: - </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>} + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your security questions:</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> - </AnastasisClientFrame > + </AnastasisClientFrame> ); } diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx index f0ec92d4d..1fa9fd6ec 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.stories.tsx @@ -15,186 +15,205 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/question", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'question' +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: [], +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", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); +); export const MessageFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Message, - message: 'Challenge should be solved' - } - } - -} as ReducerState); - -export const ServerFailureFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], - policies: [], + message: "Challenge should be solved", + }, }, - selected_challenge_uuid: 'ASDASDSAD!1', - challenge_feedback: { - 'ASDASDSAD!1': { - state: ChallengeFeedbackStatus.ServerFailure, - http_status: 500, - error_response: "Couldn't connect to mysql" - } - } - } as ReducerState); +export const ServerFailureFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.ServerFailure, + http_status: 500, + error_response: "Couldn't connect to mysql", + }, + }, + } as ReducerState, +); + export const RedirectFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Redirect, http_status: 302, - redirect_url: 'http://video.taler.net' - } - } - -} as ReducerState); - -export const MessageRateLimitExceededFeedback = createExample(TestedComponent[type].solve, { - ...reducerStatesExample.challengeSolving, - recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], - policies: [], + redirect_url: "http://video.taler.net", + }, }, - selected_challenge_uuid: 'ASDASDSAD!1', - challenge_feedback: { - 'ASDASDSAD!1': { - state: ChallengeFeedbackStatus.RateLimitExceeded, - } - } - } as ReducerState); +export const MessageRateLimitExceededFeedback = createExample( + TestedComponent[type].solve, + { + ...reducerStatesExample.challengeSolving, + recovery_information: { + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], + policies: [], + }, + selected_challenge_uuid: "ASDASDSAD!1", + challenge_feedback: { + "ASDASDSAD!1": { + state: ChallengeFeedbackStatus.RateLimitExceeded, + }, + }, + } as ReducerState, +); + export const UnsupportedFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Unsupported, http_status: 500, - unsupported_method: 'Question' - } - } - + unsupported_method: "Question", + }, + }, } as ReducerState); export const TruthUnknownFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.TruthUnknown, - } - } - + }, + }, } as ReducerState); export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.AuthIban, challenge_amount: "EUR:1", credit_iban: "DE12345789000", @@ -210,30 +229,30 @@ export const AuthIbanFeedback = createExample(TestedComponent[type].solve, { wire_transfer_subject: "foo", }, method: "iban", - } - } - + }, + }, } as ReducerState); export const PaymentFeedback = createExample(TestedComponent[type].solve, { ...reducerStatesExample.challengeSolving, recovery_information: { - challenges: [{ - cost: 'USD:1', - instructions: 'does P equals NP?', - type: 'question', - uuid: 'ASDASDSAD!1' - }], + challenges: [ + { + cost: "USD:1", + instructions: "does P equals NP?", + type: "question", + uuid: "ASDASDSAD!1", + }, + ], policies: [], }, - selected_challenge_uuid: 'ASDASDSAD!1', + selected_challenge_uuid: "ASDASDSAD!1", challenge_feedback: { - 'ASDASDSAD!1': { + "ASDASDSAD!1": { state: ChallengeFeedbackStatus.Payment, - taler_pay_uri : "taler://pay/...", - provider : "https://localhost:8080/", - payment_secret : "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG" - } - } + taler_pay_uri: "taler://pay/...", + provider: "https://localhost:8080/", + payment_secret: "3P4561HAMHRRYEYD6CM6J7TS5VTD5SR2K2EXJDZEFSX92XKHR4KG", + }, + }, } as ReducerState); - diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx index ee1c0028f..222789507 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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> + <p>Answer the question please</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodQuestionSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx index da2087ce1..3a44c7ad0 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx @@ -16,51 +16,67 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Sms', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Sms", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'sms' +const type: KnownAuthMethods = "sms"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-1234-2345', - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "SMS to +11-1234-2345", + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-1234-2345', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-5555-2345', - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: "SMS to +11-1234-2345", + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: "SMS to +11-5555-2345", + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx index 9a0459d78..056b1b175 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx @@ -1,14 +1,15 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { AuthMethodSetupProps } from "."; import { PhoneNumberInput } from "../../../components/fields/NumberInput"; import { AnastasisClientFrame } from "../index"; -export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodSmsSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [mobileNumber, setMobileNumber] = useState(""); const addSmsAuth = (): void => { addAuthMethod({ @@ -23,7 +24,7 @@ export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMe useLayoutEffect(() => { inputRef.current?.focus(); }, []); - const errors = !mobileNumber ? 'Add a mobile number' : undefined + const errors = !mobileNumber ? "Add a mobile number" : undefined; return ( <AnastasisClientFrame hideNav title="Add SMS authentication"> <div> @@ -37,23 +38,52 @@ export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMe label="Mobile number" placeholder="Your mobile number" grabFocus - bind={[mobileNumber, setMobileNumber]} /> + bind={[mobileNumber, setMobileNumber]} + /> </div> - {configured.length > 0 && <section class="section"> - <div class="block"> - Your mobile 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 style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> - <button class="button" onClick={cancel}>Cancel</button> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your mobile 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 + 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={addSmsAuth}>Add</button> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addSmsAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx index 76e769303..3dc3adb2b 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/sms", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + 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: [], +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", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx index ce7159bd0..8ee4d600a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,18 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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 + An sms has been sent to "<b>{selectedChallenge.instructions}</b>". Type + the code below </p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> @@ -97,9 +104,11 @@ export function AuthMethodSmsSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx index c0a52924c..bc4628828 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx @@ -16,49 +16,65 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/TOTP', + title: "Pages/backup/AuthorizationMethod/AuthMethods/TOTP", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'totp' +const type: KnownAuthMethods = "totp"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis"', - remove: () => null - }] -}); -export const WithMoreExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis1"', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis2"', - remove: () => null - }] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: 'Enter 8 digits code for "Anastasis"', + remove: () => null, + }, + ], + }, +); +export const WithMoreExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: 'Enter 8 digits code for "Anastasis1"', + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: 'Enter 8 digits code for "Anastasis2"', + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx index a8ac499b2..1451aadc8 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx @@ -1,7 +1,4 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useMemo, useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./index"; @@ -10,30 +7,37 @@ import { TextInput } from "../../../components/fields/TextInput"; import { QR } from "../../../components/QR"; import { base32enc, computeTOTPandCheck } from "./totp"; -export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { +export function AuthMethodTotpSetup({ + addAuthMethod, + cancel, + configured, +}: AuthMethodSetupProps): VNode { const [name, setName] = useState("anastasis"); const [test, setTest] = useState(""); - const digits = 8 + const digits = 8; const secretKey = useMemo(() => { - const array = new Uint8Array(32) - return window.crypto.getRandomValues(array) - }, []) + const array = new Uint8Array(32); + return window.crypto.getRandomValues(array); + }, []); const secret32 = base32enc(secretKey); - const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` + 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 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 - ); + const errors = !name + ? "The TOTP name is missing" + : !testCodeMatches + ? "The test code doesnt match" + : undefined; return ( <AnastasisClientFrame hideNav title="Add TOTP authentication"> <p> @@ -42,10 +46,7 @@ export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthM 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]} /> + <TextInput label="TOTP Name" grabFocus bind={[name, setName]} /> </div> <div style={{ height: 300 }}> <QR text={totpURL} /> @@ -53,25 +54,51 @@ export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthM <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>} + <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> + <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> + <button + class="button is-info" + disabled={errors !== undefined} + onClick={addTotpAuth} + > + Add + </button> </span> </div> </div> diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx index a301931b2..8743c5a73 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/totp", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + 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: [], +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", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx index 30fc44f0e..98c2e51df 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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> + <p>enter the totp solution</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodTotpSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx index 52e897c60..4aad0a097 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx @@ -16,51 +16,68 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ + * + * @author Sebastian Javier Marchano (sebasjm) + */ -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; -import logoImage from '../../../assets/logo.jpeg' +import { createExample, reducerStatesExample } from "../../../utils"; +import { authMethods as TestedComponent, KnownAuthMethods } from "./index"; +import logoImage from "../../../assets/logo.jpeg"; export default { - title: 'Pages/backup/AuthorizationMethod/AuthMethods/Video', + title: "Pages/backup/AuthorizationMethod/AuthMethods/Video", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; -const type: KnownAuthMethods = 'video' +const type: KnownAuthMethods = "video"; -export const Empty = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [] -}); +export const Empty = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [], + }, +); -export const WithOneExample = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - }] -}); +export const WithOneExample = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: logoImage, + remove: () => null, + }, + ], + }, +); -export const WithMoreExamples = createExample(TestedComponent[type].setup, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - }] -}); +export const WithMoreExamples = createExample( + TestedComponent[type].setup, + reducerStatesExample.authEditing, + { + configured: [ + { + challenge: "qwe", + type, + instructions: logoImage, + remove: () => null, + }, + { + challenge: "qwe", + type, + instructions: logoImage, + remove: () => null, + }, + ], + }, +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx index 22abe4a49..672b23500 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx @@ -1,53 +1,86 @@ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; +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 "./index"; import { AnastasisClientFrame } from "../index"; -export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { +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', + 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. + 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'}}> + <div style={{ textAlign: "center" }}> <ImageInput label="Choose photograph" grabFocus - bind={[image, setImage]} /> + bind={[image, setImage]} + /> </div> - {configured.length > 0 && <section class="section"> + {configured.length > 0 && ( + <section class="section"> + <div class="block">Your photographs:</div> <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> + 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> + </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 + 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/authMethod/AuthMethodVideoSolve.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx index 5c4976b87..7c5511c5a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.stories.tsx @@ -15,42 +15,46 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { ChallengeFeedbackStatus, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + * + * @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', + title: "Pages/recovery/SolveChallenge/AuthMethods/video", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + 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: [], +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", }, - selected_challenge_uuid: 'uuid-1', -} as ReducerState, { - id: 'uuid-1', -}); - +); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx index 79401028a..efadb9a9a 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSolve.tsx @@ -44,8 +44,16 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { 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 + style={{ + marginTop: "2em", + display: "flex", + justifyContent: "space-between", + }} + > + <button class="button" onClick={() => reducer.back()}> + Back + </button> </div> </AnastasisClientFrame> ); @@ -62,8 +70,7 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { challenges[ch.uuid] = ch; } const selectedChallenge = challenges[selectedUuid]; - const feedback = challengeFeedback[selectedUuid] - + const feedback = challengeFeedback[selectedUuid]; async function onNext(): Promise<void> { return reducer?.transition("solve_challenge", { answer }); @@ -72,18 +79,16 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { reducer?.back(); } - - const shouldHideConfirm = feedback?.state === ChallengeFeedbackStatus.RateLimitExceeded - || feedback?.state === ChallengeFeedbackStatus.Redirect - || feedback?.state === ChallengeFeedbackStatus.Unsupported - || feedback?.state === ChallengeFeedbackStatus.TruthUnknown + 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> + <p>You are gonna be called to check your identity</p> <TextInput label="Answer" grabFocus bind={[answer, setAnswer]} /> <div @@ -96,9 +101,11 @@ export function AuthMethodVideoSolve({ id }: AuthMethodSolveProps): VNode { <button class="button" onClick={onCancel}> Cancel </button> - {!shouldHideConfirm && <AsyncButton class="button is-info" onClick={onNext}> - Confirm - </AsyncButton>} + {!shouldHideConfirm && ( + <AsyncButton class="button is-info" onClick={onNext}> + Confirm + </AsyncButton> + )} </div> </AnastasisClientFrame> ); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx index 8b0126ce7..b4f649488 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -1,9 +1,9 @@ import { AuthMethod } from "anastasis-core"; import { h, VNode } from "preact"; -import postalIcon from '../../../assets/icons/auth_method/postal.svg'; -import questionIcon from '../../../assets/icons/auth_method/question.svg'; -import smsIcon from '../../../assets/icons/auth_method/sms.svg'; -import videoIcon from '../../../assets/icons/auth_method/video.svg'; +import postalIcon from "../../../assets/icons/auth_method/postal.svg"; +import questionIcon from "../../../assets/icons/auth_method/question.svg"; +import smsIcon from "../../../assets/icons/auth_method/sms.svg"; +import videoIcon from "../../../assets/icons/auth_method/video.svg"; import { AuthMethodEmailSetup as EmailSetup } from "./AuthMethodEmailSetup"; import { AuthMethodEmailSolve as EmailSolve } from "./AuthMethodEmailSolve"; import { AuthMethodIbanSetup as IbanSetup } from "./AuthMethodIbanSetup"; @@ -20,8 +20,7 @@ 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 type AuthMethodWithRemove = AuthMethod & { remove: () => void }; export interface AuthMethodSetupProps { method: string; @@ -43,10 +42,18 @@ interface AuthMethodConfiguration { } // export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; -const ALL_METHODS = ['sms', 'email', 'post', 'question', 'video' , 'totp', 'iban'] as const; -export type KnownAuthMethods = (typeof ALL_METHODS)[number]; +const ALL_METHODS = [ + "sms", + "email", + "post", + "question", + "video", + "totp", + "iban", +] as const; +export type KnownAuthMethods = typeof ALL_METHODS[number]; export function isKnownAuthMethods(value: string): value is KnownAuthMethods { - return ALL_METHODS.includes(value as KnownAuthMethods) + return ALL_METHODS.includes(value as KnownAuthMethods); } type KnowMethodConfig = { @@ -96,5 +103,5 @@ export const authMethods: KnowMethodConfig = { setup: VideoSetup, solve: VideoSolve, skip: true, - } -}
\ No newline at end of file + }, +}; diff --git a/packages/anastasis-webui/src/pages/home/authMethod/totp.ts b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts index 0bc3feaf8..c2288671c 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/totp.ts +++ b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts @@ -1,54 +1,61 @@ /* eslint-disable @typescript-eslint/camelcase */ -import jssha from 'jssha' +import jssha from "jssha"; -const SEARCH_RANGE = 16 -const timeStep = 30 +const SEARCH_RANGE = 16; +const timeStep = 30; -export function computeTOTPandCheck(secretKey: Uint8Array, digits: number, code: number): boolean { - const now = new Date().getTime() +export function computeTOTPandCheck( + secretKey: Uint8Array, + digits: number, + code: number, +): boolean { + const now = new Date().getTime(); const epoch = Math.floor(Math.round(now / 1000.0) / timeStep); for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) { const movingFactor = (epoch + ms).toString(16).padStart(16, "0"); - const hmacSha = new jssha('SHA-1', 'HEX', { hmacKey: { value: secretKey, format: 'UINT8ARRAY' } }); + const hmacSha = new jssha("SHA-1", "HEX", { + hmacKey: { value: secretKey, format: "UINT8ARRAY" }, + }); hmacSha.update(movingFactor); - const hmac_text = hmacSha.getHMAC('UINT8ARRAY'); + const hmac_text = hmacSha.getHMAC("UINT8ARRAY"); - const offset = (hmac_text[hmac_text.length - 1] & 0xf) + const offset = hmac_text[hmac_text.length - 1] & 0xf; - const otp = (( - (hmac_text[offset + 0] << 24) + - (hmac_text[offset + 1] << 16) + - (hmac_text[offset + 2] << 8) + - (hmac_text[offset + 3]) - ) & 0x7fffffff) % Math.pow(10, digits) + const otp = + (((hmac_text[offset + 0] << 24) + + (hmac_text[offset + 1] << 16) + + (hmac_text[offset + 2] << 8) + + hmac_text[offset + 3]) & + 0x7fffffff) % + Math.pow(10, digits); - if (otp == code) return true + if (otp == code) return true; } - return false + return false; } -const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split('') +const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split(""); export function base32enc(buffer: Uint8Array): string { - let rpos = 0 - let bits = 0 - let vbit = 0 + let rpos = 0; + let bits = 0; + let vbit = 0; - let result = "" - while ((rpos < buffer.length) || (vbit > 0)) { - if ((rpos < buffer.length) && (vbit < 5)) { + let result = ""; + while (rpos < buffer.length || vbit > 0) { + if (rpos < buffer.length && vbit < 5) { bits = (bits << 8) | buffer[rpos++]; vbit += 8; } if (vbit < 5) { - bits <<= (5 - vbit); + bits <<= 5 - vbit; vbit = 5; } result += encTable__[(bits >> (vbit - 5)) & 31]; vbit -= 5; } - return result + return result; } // const array = new Uint8Array(256) |