import { encodeCrock, stringToBytes } from "@gnu-taler/taler-util"; import { FunctionalComponent, h } from "preact"; import { useState } from "preact/hooks"; import { AnastasisReducerApi, AuthMethod, BackupStates, ReducerStateBackup, ReducerStateRecovery, useAnastasisReducer, } from "../../hooks/use-anastasis-reducer"; import style from "./style.css"; interface ContinentSelectionProps { reducer: AnastasisReducerApi; reducerState: ReducerStateBackup | ReducerStateRecovery; } function isBackup(reducer: AnastasisReducerApi) { return !!reducer.currentReducerState?.backup_state; } function ContinentSelection(props: ContinentSelectionProps) { const { reducer, reducerState } = props; return (

{isBackup(reducer) ? "Backup" : "Recovery"}: Select Continent

{reducerState.continents.map((x: any) => { const sel = (x: string) => reducer.transition("select_continent", { continent: x }); return ( ); })}
); } interface CountrySelectionProps { reducer: AnastasisReducerApi; reducerState: ReducerStateBackup | ReducerStateRecovery; } function CountrySelection(props: CountrySelectionProps) { const { reducer, reducerState } = props; return (

Backup: Select Country

{reducerState.countries.map((x: any) => { const sel = (x: any) => reducer.transition("select_country", { country_code: x.code, currencies: [x.currency], }); return ( ); })}
); } const Home: FunctionalComponent = () => { const reducer = useAnastasisReducer(); const reducerState = reducer.currentReducerState; if (!reducerState) { return (

Home

); } console.log("state", reducer.currentReducerState); if (reducerState.backup_state === BackupStates.ContinentSelecting) { return ; } if (reducerState.backup_state === BackupStates.CountrySelecting) { return ; } if (reducerState.backup_state === BackupStates.UserAttributesCollecting) { return ; } if (reducerState.backup_state === BackupStates.AuthenticationsEditing) { return ( ); } if (reducerState.backup_state === BackupStates.PoliciesReviewing) { const backupState: ReducerStateBackup = reducerState; const authMethods = backupState.authentication_methods!; return (

Backup: Review Recovery Policies

{backupState.policies?.map((p, i) => { const policyName = p.methods .map((x) => authMethods[x.authentication_method].type) .join(" + "); return (

Policy #{i + 1}: {policyName}

Required Authentications:
    {p.methods.map((x) => { const m = authMethods[x.authentication_method]; return (
  • {m.type} ({m.instructions}) at provider {x.provider}
  • ); })}
); })}
); } if (reducerState.backup_state === BackupStates.SecretEditing) { const [secretName, setSecretName] = useState(""); const [secretValue, setSecretValue] = useState(""); const secretNext = () => { reducer.runTransaction(async (tx) => { await tx.transition("enter_secret_name", { name: secretName, }); await tx.transition("enter_secret", { secret: { value: "EDJP6WK5EG50", mime: "text/plain", }, expiration: { t_ms: new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 5, }, }); await tx.transition("next", {}); }); }; return (

Backup: Provide secret

or:
); } if (reducerState.backup_state === BackupStates.BackupFinished) { const backupState: ReducerStateBackup = reducerState; return (

Backup finished

Your backup of secret "{backupState.secret_name ?? "??"}" was successful.

The backup is stored by the following providers:

    {Object.keys(backupState.success_details).map((x, i) => { const sd = backupState.success_details[x]; return (
  • {x} (Policy version {sd.policy_version})
  • ); })}
); } if (reducerState.backup_state === BackupStates.TruthsPaying) { const backupState: ReducerStateBackup = reducerState; const payments = backupState.payments ?? []; return (

Backup: Authentication Storage Payments

Some of the providers require a payment to store the encrypted authentication information.

    {payments.map((x) => { return
  • {x}
  • ; })}
); } if (reducerState.backup_state === BackupStates.PoliciesPaying) { const backupState: ReducerStateBackup = reducerState; const payments = backupState.policy_payment_requests ?? []; return (

Backup: Recovery Document Payments

Some of the providers require a payment to store the encrypted recovery document.

    {payments.map((x) => { return (
  • {x.provider}: {x.payto}
  • ); })}
); } console.log("unknown state", reducer.currentReducerState); return (

Home

Bug: Unknown state.

); }; interface AuthMethodSetupProps { method: string; addAuthMethod: (x: any) => void; cancel: () => void; } function AuthMethodSmsSetup(props: AuthMethodSetupProps) { const [mobileNumber, setMobileNumber] = useState(""); return (

Add {props.method} authentication

For SMS authentication, you need to provide a mobile number. When recovering your secret, you will be asked to enter the code you receive via SMS.

); } function AuthMethodQuestionSetup(props: AuthMethodSetupProps) { const [questionText, setQuestionText] = useState(""); const [answerText, setAnswerText] = useState(""); return (

Add {props.method} authentication

For security question authentication, you need to provide a question and its answer. When recovering your secret, you will be shown the question and you will need to type the answer exactly as you typed it here.

); } function AuthMethodNotImplemented(props: AuthMethodSetupProps) { return (

Add {props.method} authentication

This auth method is not implemented yet, please choose another one.

); } export interface AuthenticationEditorProps { reducer: AnastasisReducerApi; backupState: ReducerStateBackup; } function AuthenticationEditor(props: AuthenticationEditorProps) { const [selectedMethod, setSelectedMethod] = useState( undefined, ); const { reducer, backupState } = props; const providers = backupState.authentication_providers; const authAvailableSet = new Set(); for (const provKey of Object.keys(providers)) { const p = providers[provKey]; for (const meth of p.methods) { authAvailableSet.add(meth.type); } } if (selectedMethod) { const cancel = () => setSelectedMethod(undefined); const addMethod = (args: any) => { reducer.transition("add_authentication", args); setSelectedMethod(undefined); }; switch (selectedMethod) { case "sms": return ( ); case "question": return ( ); default: return ( ); } } function MethodButton(props: { method: string; label: String }) { return ( ); } const configuredAuthMethods: AuthMethod[] = backupState.authentication_methods ?? []; const haveMethodsConfigured = configuredAuthMethods.length; return (

Backup: Configure Authentication Methods

Add authentication method

Configured authentication methods

{haveMethodsConfigured ? ( configuredAuthMethods.map((x, i) => { return (

{x.type} ({x.instructions}){" "}

); }) ) : (

No authentication methods configured yet.

)}
); } export interface AttributeEntryProps { reducer: AnastasisReducerApi; backupState: ReducerStateBackup; } function AttributeEntry(props: AttributeEntryProps) { const { reducer, backupState } = props; const [attrs, setAttrs] = useState>({}); return (

Backup: Enter Basic User Attributes

{backupState.required_attributes.map((x: any, i: number) => { return ( setAttrs({ ...attrs, [x.name]: v })} spec={x} value={attrs[x.name]} /> ); })}
); } export interface AttributeEntryFieldProps { isFirst: boolean; value: string; setValue: (newValue: string) => void; spec: any; } function AttributeEntryField(props: AttributeEntryFieldProps) { return (
props.setValue((e as any).target.value)} />
); } interface ErrorBannerProps { reducer: AnastasisReducerApi; } /** * Show a dismissable error banner if there is a current error. */ function ErrorBanner(props: ErrorBannerProps) { const currentError = props.reducer.currentError; if (currentError) { return (

Error: {JSON.stringify(currentError)}

); } return null; } export default Home;