diff options
Diffstat (limited to 'packages/anastasis-webui/src')
5 files changed, 101 insertions, 50 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx index a96734caa..08e2b4371 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx @@ -40,6 +40,12 @@ export const NewProvider = createExample(TestedComponent, {    ...reducerStatesExample.authEditing,  } as ReducerState); + +export const NewProviderWithoutProviderList = createExample(TestedComponent, { +  ...reducerStatesExample.authEditing, +  authentication_providers: {} +} as ReducerState); +  export const NewVideoProvider = createExample(TestedComponent, {    ...reducerStatesExample.authEditing,  } as ReducerState, { providerType: 'video'}); diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx index 5cf6fbb09..7504f4d2b 100644 --- a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx @@ -1,6 +1,6 @@  import { AuthenticationProviderStatusOk } from "anastasis-core";  import { h, VNode } from "preact"; -import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; +import { useEffect, useRef, useState } from "preact/hooks";  import { TextInput } from "../../components/fields/TextInput";  import { useAnastasisContext } from "../../context/anastasis";  import { authMethods, KnownAuthMethods } from "./authMethod"; @@ -8,13 +8,13 @@ import { AnastasisClientFrame } from "./index";  interface Props {    providerType?: KnownAuthMethods; -  cancel: () => void; +  onCancel: () => void;  }  async function testProvider(url: string, expectedMethodType?: string): Promise<void> {    try { -    const response = await fetch(`${url}/config`) +    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") @@ -41,7 +41,7 @@ async function testProvider(url: string, expectedMethodType?: string): Promise<v  } -export function AddingProviderScreen({ providerType, cancel }: Props): VNode { +export function AddingProviderScreen({ providerType, onCancel }: Props): VNode {    const reducer = useAnastasisContext();    const [providerURL, setProviderURL] = useState(""); @@ -54,8 +54,8 @@ export function AddingProviderScreen({ providerType, cancel }: Props): VNode {    useEffect(() => {      if (timeout) window.clearTimeout(timeout.current)      timeout.current = window.setTimeout(async () => { -      const url = providerURL.endsWith('/') ? providerURL.substring(0, providerURL.length - 1) : providerURL -      if (!url) return; +      const url = providerURL.endsWith('/') ? providerURL : (providerURL + '/') +      if (!providerURL || authProviders.includes(url)) return;        try {          setTesting(true)          await testProvider(url, providerType) @@ -67,40 +67,50 @@ export function AddingProviderScreen({ providerType, cancel }: Props): VNode {          if (e instanceof Error) setError(e.message)        }        setTesting(false) -    }, 1000); -  }, [providerURL]) +    }, 200); +  }, [providerURL, reducer])    if (!reducer) {      return <div>no reducer in context</div>;    } -  function addProvider(): void { -    // addAuthMethod({ -    //   authentication_method: { -    //     type: "sms", -    //     instructions: `SMS to ${providerURL}`, -    //     challenge: encodeCrock(stringToBytes(providerURL)), -    //   }, -    // }); +  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() +  } +  function deleteProvider(provider_url: string): void { +    reducer?.transition("delete_provider", { provider_url })    } +  const allAuthProviders = reducer.currentReducerState.authentication_providers || {} +  const authProviders = Object.keys(allAuthProviders).filter(provUrl => { +    const p = allAuthProviders[provUrl]; +    if (!providerLabel) { +      return p && ("currency" in p) +    } else { +      return p && ("currency" in p) && p.methods.findIndex(m => m.type === providerType) !== -1 +    } +  }) +    let errors = !providerURL ? 'Add provider URL' : undefined +  let url: string | undefined;    try { -    new URL(providerURL) +    url = new URL("",providerURL).href    } catch {      errors = 'Check the URL'    }    if (!!error && !errors) {      errors = error    } - -  if (!reducer.currentReducerState || !("authentication_providers" in reducer.currentReducerState)) { -    return <div>invalid state</div> +  if (!errors && authProviders.includes(url!)) { +    errors = 'That provider is already known'    } -  const authProviders = reducer.currentReducerState.authentication_providers || {} -    return (      <AnastasisClientFrame hideNav        title="Backup: Manage providers" @@ -119,40 +129,45 @@ export function AddingProviderScreen({ providerType, cancel }: Props): VNode {              label="Provider URL"              placeholder="https://provider.com"              grabFocus +            error={errors}              bind={[providerURL, setProviderURL]} />          </div>          <p class="block">            Example: https://kudos.demo.anastasis.lu          </p> - -        {testing && <p class="block has-text-info">Testing</p>} -        {!!error && <p class="block has-text-danger">{error}</p>} -        {error === "" && <p class="block has-text-success">This provider worked!</p>} - +        {testing && <p class="has-text-info">Testing</p>} +                  <div class="block" style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}> -          <button class="button" onClick={cancel}>Cancel</button> +          <button class="button" onClick={onCancel}>Cancel</button>            <span data-tooltip={errors}> -            <button class="button is-info" disabled={error !== "" || testing} onClick={addProvider}>Add</button> +            <button class="button is-info" disabled={error !== "" || testing} onClick={() => addProvider(url!)}>Add</button>            </span>          </div> -        <p class="subtitle"> -          Current providers -        </p> -        {/* <table class="table"> */} -        {Object.keys(authProviders).map(k => { -          const p = authProviders[k] -          if (("currency" in p)) { -            return <TableRow url={k} info={p} /> -          } -        } +        {authProviders.length > 0 ? ( +          !providerLabel ? +            <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> : <p class="subtitle"> +            No known providers for {providerLabel} service +          </p>          )} -        {/* </table> */} + +        {authProviders.map(k => { +          const p = allAuthProviders[k] as AuthenticationProviderStatusOk +          return <TableRow url={k} info={p} onDelete={deleteProvider} /> +        })}        </div>      </AnastasisClientFrame>    );  } -function TableRow({ url, info }: { url: string, info: AuthenticationProviderStatusOk }) { +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) @@ -174,7 +189,7 @@ function TableRow({ url, info }: { url: string, info: AuthenticationProviderStat        </dl>      </div>      <div class="block" style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}> -      <button class="button is-danger" >Remove</button> +      <button class="button is-danger" onClick={() => onDelete(url)}>Remove</button>      </div>    </div>  }
\ No newline at end of file diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index 0bc735f34..00eb54d4d 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -2,10 +2,12 @@ import { AuthMethod, ReducerStateBackup } from "anastasis-core";  import { ComponentChildren, Fragment, h, VNode } from "preact";  import { useState } from "preact/hooks";  import { useAnastasisContext } from "../../context/anastasis"; +import { AddingProviderScreen } from "./AddingProviderScreen";  import {    authMethods,    AuthMethodSetupProps,    AuthMethodWithRemove, +  isKnownAuthMethods,    KnownAuthMethods,  } from "./authMethod";  import { AnastasisClientFrame } from "./index"; @@ -18,6 +20,8 @@ export function AuthenticationEditorScreen(): VNode {      KnownAuthMethods | undefined    >(undefined);    const [tooFewAuths, setTooFewAuths] = useState(false); +  const [manageProvider, setManageProvider] = useState<string | undefined>(undefined) +    // const [addingProvider, setAddingProvider] = useState<string | undefined>(undefined)    const reducer = useAnastasisContext();    if (!reducer) { @@ -63,6 +67,14 @@ export function AuthenticationEditorScreen(): VNode {      }    } +  if (manageProvider !== undefined) { +     +    return <AddingProviderScreen +      onCancel={() => setManageProvider(undefined)} +      providerType={isKnownAuthMethods(manageProvider) ? manageProvider : undefined} +    /> +  } +    if (selectedMethod) {      const cancel = (): void => setSelectedMethod(undefined);      const addMethod = (args: any): void => { @@ -86,9 +98,9 @@ export function AuthenticationEditorScreen(): VNode {              active              onCancel={cancel}              description="No providers founds" -            label="Add a provider manually (not implemented!)" +            label="Add a provider manually"              onConfirm={() => { -              null; +              setManageProvider(selectedMethod)              }}            >              <p> @@ -179,9 +191,9 @@ export function AuthenticationEditorScreen(): VNode {                active={!noProvidersAck}                onCancel={() => setNoProvidersAck(true)}                description="No providers founds" -              label="Add a provider manually (not implemented!)" +              label="Add a provider manually"                onConfirm={() => { -                null; +                setManageProvider("")                }}              >                <p> @@ -201,11 +213,11 @@ export function AuthenticationEditorScreen(): VNode {              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"> +          <p class="block"> +            <button class="button is-info" onClick={() => setManageProvider("")}>                Manage backup providers              </button> -          </p> */} +          </p>            {authAvailableSet.size > 0 && (              <p class="block">                We couldn't find provider for some of the authentication methods. diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index cf38d3f18..b1ec2856a 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -3,12 +3,14 @@ import { useState } from "preact/hooks";  import { AsyncButton } from "../../components/AsyncButton";  import { NumberInput } from "../../components/fields/NumberInput";  import { useAnastasisContext } from "../../context/anastasis"; +import { AddingProviderScreen } from "./AddingProviderScreen";  import { AnastasisClientFrame } from "./index";  export function SecretSelectionScreen(): VNode {    const [selectingVersion, setSelectingVersion] = useState<boolean>(false);    const reducer = useAnastasisContext() +  const [manageProvider, setManageProvider] = useState(false)    const currentVersion = (reducer?.currentReducerState      && ("recovery_document" in reducer.currentReducerState)      && reducer.currentReducerState.recovery_document?.version) || 0; @@ -49,6 +51,10 @@ export function SecretSelectionScreen(): VNode {      />    } +  if (manageProvider) { +    return <AddingProviderScreen onCancel={() => setManageProvider(false)} /> +  } +    return (      <AnastasisClientFrame title="Recovery: Select secret">        <div class="columns"> @@ -69,6 +75,12 @@ export function SecretSelectionScreen(): VNode {          </div>          <div class="column">            <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)}> +              Manage recovery providers +            </button> +          </p> +          </div>        </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 07f6ec206..8b0126ce7 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -41,7 +41,13 @@ interface AuthMethodConfiguration {    solve: (props: AuthMethodSolveProps) => VNode;    skip?: boolean;  } -export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; +// 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]; +export function isKnownAuthMethods(value: string): value is KnownAuthMethods { +  return ALL_METHODS.includes(value as KnownAuthMethods) +}  type KnowMethodConfig = {    [name in KnownAuthMethods]: AuthMethodConfiguration;  | 
