diff options
Diffstat (limited to 'packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx')
-rw-r--r-- | packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx | 222 |
1 files changed, 160 insertions, 62 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index e9ffccbac..93ca81194 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -1,19 +1,21 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { AuthMethod, ReducerStateBackup } from "anastasis-core"; -import { h, VNode } from "preact"; +import { AuthMethod } from "anastasis-core"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; +import { TextInput } from "../../components/fields/TextInput"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; -import { AuthMethodEmailSetup } from "./AuthMethodEmailSetup"; -import { AuthMethodPostSetup } from "./AuthMethodPostSetup"; -import { AuthMethodQuestionSetup } from "./AuthMethodQuestionSetup"; -import { AuthMethodSmsSetup } from "./AuthMethodSmsSetup"; +import { authMethods, KnownAuthMethods } from "./authMethod"; import { AnastasisClientFrame } from "./index"; + + +const getKeys = Object.keys as <T extends object>(obj: T) => Array<keyof T> + export function AuthenticationEditorScreen(): VNode { - const [selectedMethod, setSelectedMethod] = useState<string | undefined>( - undefined - ); + const [noProvidersAck, setNoProvidersAck] = useState(false) + const [selectedMethod, setSelectedMethod] = useState<KnownAuthMethods | undefined>(undefined); + const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined) + const reducer = useAnastasisContext() if (!reducer) { return <div>no reducer in context</div> @@ -21,7 +23,29 @@ export function AuthenticationEditorScreen(): VNode { if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { return <div>invalid state</div> } + const configuredAuthMethods: AuthMethod[] = reducer.currentReducerState.authentication_methods ?? []; + const haveMethodsConfigured = configuredAuthMethods.length > 0; + + function removeByIndex(index: number): void { + if (reducer) reducer.transition("delete_authentication", { + authentication_method: index, + }) + } + + const camByType: { [s: string]: AuthMethodWithRemove[] } = {} + for (let index = 0; index < configuredAuthMethods.length; index++) { + const cam = { + ...configuredAuthMethods[index], + remove: () => removeByIndex(index) + } + const prevValue = camByType[cam.type] || [] + prevValue.push(cam) + camByType[cam.type] = prevValue; + } + + const providers = reducer.currentReducerState.authentication_providers!; + const authAvailableSet = new Set<string>(); for (const provKey of Object.keys(providers)) { const p = providers[provKey]; @@ -31,79 +55,125 @@ export function AuthenticationEditorScreen(): VNode { } } } + if (selectedMethod) { const cancel = (): void => setSelectedMethod(undefined); const addMethod = (args: any): void => { reducer.transition("add_authentication", args); setSelectedMethod(undefined); }; - const methodMap: Record< - string, (props: AuthMethodSetupProps) => h.JSX.Element - > = { - sms: AuthMethodSmsSetup, - question: AuthMethodQuestionSetup, - email: AuthMethodEmailSetup, - post: AuthMethodPostSetup, - }; - const AuthSetup = methodMap[selectedMethod] ?? AuthMethodNotImplemented; - return ( + + const AuthSetup = authMethods[selectedMethod].screen ?? AuthMethodNotImplemented; + return (<Fragment> <AuthSetup cancel={cancel} + configured={camByType[selectedMethod] || []} addAuthMethod={addMethod} method={selectedMethod} /> + + {!authAvailableSet.has(selectedMethod) && <ConfirmModal active + onCancel={cancel} description="No providers founds" label="Add a provider manually" + onConfirm={() => { + null + }} + > + We have found no trusted cloud providers for your recovery secret. You can add a provider manually. + To add a provider you must know the provider URL (e.g. https://provider.com) + <p> + <a>More about cloud providers</a> + </p> + </ConfirmModal>} + + </Fragment> ); } - function MethodButton(props: { method: string; label: string }): VNode { + + if (addingProvider !== undefined) { + return <div /> + } + + function MethodButton(props: { method: KnownAuthMethods }): VNode { + if (authMethods[props.method].skip) return <div /> + return ( - <button - disabled={!authAvailableSet.has(props.method)} - onClick={() => { - setSelectedMethod(props.method); - if (reducer) reducer.dismissError(); - }} - > - {props.label} - </button> + <div class="block"> + <button + style={{ justifyContent: 'space-between' }} + class="button is-fullwidth" + onClick={() => { + setSelectedMethod(props.method); + }} + > + <div style={{ display: 'flex' }}> + <span class="icon "> + {authMethods[props.method].icon} + </span> + {authAvailableSet.has(props.method) ? + <span> + Add a {authMethods[props.method].label} challenge + </span> : + <span> + Add a {authMethods[props.method].label} provider + </span> + } + </div> + {!authAvailableSet.has(props.method) && + <span class="icon has-text-danger" > + <i class="mdi mdi-exclamation-thick" /> + </span> + } + {camByType[props.method] && + <span class="tag is-info" > + {camByType[props.method].length} + </span> + } + </button> + </div> ); } - const configuredAuthMethods: AuthMethod[] = reducer.currentReducerState.authentication_methods ?? []; - const haveMethodsConfigured = configuredAuthMethods.length; + const errors = !haveMethodsConfigured ? "There is not enough authentication methods." : undefined; return ( - <AnastasisClientFrame title="Backup: Configure Authentication Methods"> - <div> - <MethodButton method="sms" label="SMS" /> - <MethodButton method="email" label="Email" /> - <MethodButton method="question" label="Question" /> - <MethodButton method="post" label="Physical Mail" /> - <MethodButton method="totp" label="TOTP" /> - <MethodButton method="iban" label="IBAN" /> - </div> - <h2>Configured authentication methods</h2> - {haveMethodsConfigured ? ( - configuredAuthMethods.map((x, i) => { - return ( - <p key={i}> - {x.type} ({x.instructions}){" "} - <button - onClick={() => reducer.transition("delete_authentication", { - authentication_method: i, - })} - > - Delete - </button> + <AnastasisClientFrame title="Backup: Configure Authentication Methods" hideNext={errors}> + <div class="columns"> + <div class="column is-half"> + <div> + {getKeys(authMethods).map(method => <MethodButton key={method} method={method} />)} + </div> + {authAvailableSet.size === 0 && <ConfirmModal active={!noProvidersAck} + onCancel={() => setNoProvidersAck(true)} description="No providers founds" label="Add a provider manually" + onConfirm={() => { + null + }} + > + We have found no trusted cloud providers for your recovery secret. You can add a provider manually. + To add a provider you must know the provider URL (e.g. https://provider.com) + <p> + <a>More about cloud providers</a> </p> - ); - }) - ) : ( - <p>No authentication methods configured yet.</p> - )} + </ConfirmModal>} + </div> + <div class="column is-half"> + <p class="block"> + When recovering your wallet, you will be asked to verify your identity via the methods you configure here. + The list of authentication method is defined by the backup provider list. + </p> + <p class="block"> + <button class="button is-info">Manage the backup provider's list</button> + </p> + {authAvailableSet.size > 0 && <p class="block"> + We couldn't find provider for some of the authentication methods. + </p>} + </div> + </div> </AnastasisClientFrame> ); } +type AuthMethodWithRemove = AuthMethod & { remove: () => void } export interface AuthMethodSetupProps { method: string; addAuthMethod: (x: any) => void; + configured: AuthMethodWithRemove[]; cancel: () => void; } @@ -116,8 +186,36 @@ function AuthMethodNotImplemented(props: AuthMethodSetupProps): VNode { ); } -interface AuthenticationEditorProps { - reducer: AnastasisReducerApi; - backupState: ReducerStateBackup; + +function ConfirmModal({ active, description, onCancel, onConfirm, children, danger, disabled, label = 'Confirm' }: Props): VNode { + return <div class={active ? "modal is-active" : "modal"}> + <div class="modal-background " onClick={onCancel} /> + <div class="modal-card" style={{ maxWidth: 700 }}> + <header class="modal-card-head"> + {!description ? null : <p class="modal-card-title"><b>{description}</b></p>} + <button class="delete " aria-label="close" onClick={onCancel} /> + </header> + <section class="modal-card-body"> + {children} + </section> + <footer class="modal-card-foot"> + <button class="button" onClick={onCancel} >Dismiss</button> + <div class="buttons is-right" style={{ width: '100%' }}> + <button class={danger ? "button is-danger " : "button is-info "} disabled={disabled} onClick={onConfirm} >{label}</button> + </div> + </footer> + </div> + <button class="modal-close is-large " aria-label="close" onClick={onCancel} /> + </div> } +interface Props { + active?: boolean; + description?: string; + onCancel?: () => void; + onConfirm?: () => void; + label?: string; + children?: ComponentChildren; + danger?: boolean; + disabled?: boolean; +} |