("");
+ function selectVersion(p: string, n: number) {
+ reducer.runTransaction(async (tx) => {
+ await tx.transition("change_version", {
+ version: n,
+ provider_url: p,
+ });
+ setSelectingVersion(false);
+ });
+ }
+ if (selectingVersion) {
+ return (
+
+ Select a different version of the secret
+ setOtherProvider((e.target as any).value)}>
+ {Object.keys(recoveryState.authentication_providers ?? {}).map(
+ (x) => {
+ return {x} ;
+ },
+ )}
+
+
+
+ setOtherVersion(Number((e.target as HTMLInputElement).value))
+ }
+ type="number"
+ />
+ selectVersion(otherProvider, otherVersion)}>
+ Select
+
+
+
+ selectVersion(otherProvider, 0)}>
+ Use latest version
+
+
+
+ setSelectingVersion(false)}>Cancel
+
+
+ );
+ }
+ return (
+
+ Provider: {recoveryState.recovery_document!.provider_url}
+ Secret version: {recoveryState.recovery_document!.version}
+ Secret name: {recoveryState.recovery_document!.version}
+ setSelectingVersion(true)}>
+ Select different secret
+
+
+ );
+}
+
+interface AnastasisClientFrameProps {
+ onNext?(): void;
+ title: string;
+ children: ComponentChildren;
+ /**
+ * Should back/next buttons be provided?
+ */
+ hideNav?: boolean;
+ /**
+ * Hide only the "next" button.
+ */
+ hideNext?: boolean;
+}
+
+function AnastasisClientFrame(props: AnastasisClientFrameProps) {
+ return (
+
+ {(reducer) => {
+ if (!reducer) {
+ return Fatal: Reducer must be in context.
;
+ }
+ const next = () => {
+ if (props.onNext) {
+ props.onNext();
+ } else {
+ reducer.transition("next", {});
+ }
+ };
+ return (
+
+
reducer.reset()}>Reset session
+
{props.title}
+
+ {props.children}
+ {!props.hideNav ? (
+
+ reducer.back()}>Back
+ {!props.hideNext ? (
+ next()}>Next
+ ) : null}
+
+ ) : null}
+
+ );
+ }}
+
+ );
+}
+
+const AnastasisClient: FunctionalComponent = () => {
const reducer = useAnastasisReducer();
+ return (
+
+
+
+ );
+};
+
+const AnastasisClientImpl: FunctionalComponent = () => {
+ const reducer = useContext(WithReducer)!;
const reducerState = reducer.currentReducerState;
if (!reducerState) {
return (
-
-
Home
-
- reducer.startBackup()}>
- Backup
-
- reducer.startRecover()}>Recover
-
-
+
+ reducer.startBackup()}>
+ Backup
+
+ reducer.startRecover()}>Recover
+
);
}
console.log("state", reducer.currentReducerState);
@@ -122,109 +386,17 @@ const Home: FunctionalComponent = () => {
);
}
-
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}
-
- );
- })}
-
-
-
- reducer.transition("delete_policy", { policy_index: i })
- }
- >
- Delete Policy
-
-
-
- );
- })}
-
-
- reducer.back()}>Back
- reducer.transition("next", {})}>Next
-
-
- );
+ return ;
}
-
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
-
-
-
- Secret name:
-
-
-
-
- Secret value:
-
-
- or:
-
-
- File Upload:
-
-
-
- reducer.back()}>Back
- secretNext()}>Next
-
-
- );
+ return ;
}
if (reducerState.backup_state === BackupStates.BackupFinished) {
const backupState: ReducerStateBackup = reducerState;
return (
-
-
Backup finished
+
Your backup of secret "{backupState.secret_name ?? "??"}" was
successful.
@@ -240,10 +412,8 @@ const Home: FunctionalComponent = () => {
);
})}
- reducer.reset()}>
- Start a new backup/recovery
-
-
+ reducer.reset()}>Back to start
+
);
}
@@ -251,8 +421,10 @@ const Home: FunctionalComponent = () => {
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.
@@ -262,22 +434,19 @@ const Home: FunctionalComponent = () => {
return
{x} ;
})}
-
- reducer.back()}>Back
- reducer.transition("pay", {})}>
- Check payment(s)
-
-
-
+ reducer.transition("pay", {})}>
+ Check payment status now
+
+
);
}
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.
@@ -291,23 +460,111 @@ const Home: FunctionalComponent = () => {
);
})}
-
- reducer.back()}>Back
- reducer.transition("pay", {})}>
- Check payment(s)
-
-
-
+ reducer.transition("pay", {})}>
+ Check payment status now
+
+
+ );
+ }
+
+ if (reducerState.recovery_state === RecoveryStates.SecretSelecting) {
+ return ;
+ }
+
+ if (reducerState.recovery_state === RecoveryStates.ChallengeSelecting) {
+ const policies = reducerState.recovery_information!.policies;
+ const chArr = reducerState.recovery_information!.challenges;
+ const challenges: {
+ [uuid: string]: {
+ type: string;
+ instructions: string;
+ cost: string;
+ };
+ } = {};
+ for (const ch of chArr) {
+ challenges[ch.uuid] = {
+ type: ch.type,
+ cost: ch.cost,
+ instructions: ch.instructions,
+ };
+ }
+ return (
+
+ Policies
+ {policies.map((x, i) => {
+ return (
+
+
Policy #{i + 1}
+ {x.map((x) => {
+ const ch = challenges[x.uuid];
+ return (
+
+ {ch.type} ({ch.instructions})
+
+ reducer.transition("select_challenge", {
+ uuid: x.uuid,
+ })
+ }
+ >
+ Solve
+
+
+ );
+ })}
+
+ );
+ })}
+
+ );
+ }
+
+ if (reducerState.recovery_state === RecoveryStates.ChallengeSolving) {
+ const chArr = reducerState.recovery_information!.challenges;
+ const challengeFeedback = reducerState.challenge_feedback ?? {};
+ const selectedUuid = reducerState.selected_challenge_uuid!;
+ const challenges: {
+ [uuid: string]: ChallengeInfo;
+ } = {};
+ for (const ch of chArr) {
+ challenges[ch.uuid] = ch;
+ }
+ const selectedChallenge = challenges[selectedUuid];
+ if (selectedChallenge.type === "question") {
+ return (
+
+ );
+ } else {
+ return (
+
+ {JSON.stringify(selectedChallenge)}
+ Challenge not supported.
+
+ );
+ }
+ }
+
+ if (reducerState.recovery_state === RecoveryStates.RecoveryFinished) {
+ return (
+
+ Recovery Finished
+
+ Secret: {bytesToString(decodeCrock(reducerState.core_secret?.value!))}
+
+
);
}
console.log("unknown state", reducer.currentReducerState);
return (
-
-
Home
+
Bug: Unknown state.
reducer.reset()}>Reset
-
+
);
};
@@ -328,9 +585,12 @@ function AuthMethodSmsSetup(props: AuthMethodSetupProps) {
},
});
};
+ //const inputRef = useRef(null);
+ // useLayoutEffect(() => {
+ // inputRef.current?.focus();
+ // }, []);
return (
-
-
Add {props.method} authentication
+
-
+
);
}
@@ -359,8 +621,7 @@ function AuthMethodQuestionSetup(props: AuthMethodSetupProps) {
const [questionText, setQuestionText] = useState("");
const [answerText, setAnswerText] = useState("");
return (
-
-
Add {props.method} authentication
+
-
+
);
}
function AuthMethodEmailSetup(props: AuthMethodSetupProps) {
const [email, setEmail] = useState("");
return (
-
-
Add {props.method} authentication
+
+
+ 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.
+
-
- For email authentication, you need to provid an email address. When
- recovering your secret, you need to enter the code you will receive by
- email.
-
-
-
- Email address
- setEmail((e.target as any).value)}
- type="text"
- />
-
-
-
- props.cancel()}>Cancel
-
- props.addAuthMethod({
- authentication_method: {
- type: "email",
- instructions: `Email to ${email}`,
- challenge: encodeCrock(stringToBytes(email)),
- },
- })
- }
- >
- Add
-
-
+
+ Email address:{" "}
+ setEmail((e.target as any).value)}
+ type="text"
+ />
+
-
+
+ props.cancel()}>Cancel
+
+ props.addAuthMethod({
+ authentication_method: {
+ type: "email",
+ instructions: `Email to ${email}`,
+ challenge: encodeCrock(stringToBytes(email)),
+ },
+ })
+ }
+ >
+ Add
+
+
+
);
}
@@ -460,6 +721,28 @@ function AuthMethodPostSetup(props: AuthMethodSetupProps) {
const [city, setCity] = useState("");
const [postcode, setPostcode] = useState("");
const [country, setCountry] = useState("");
+
+ const addPostAuth = () => {
+ () =>
+ props.addAuthMethod({
+ authentication_method: {
+ type: "email",
+ instructions: `Letter to address in postal code ${postcode}`,
+ challenge: encodeCrock(
+ stringToBytes(
+ canonicalJson({
+ full_name: fullName,
+ street,
+ city,
+ postcode,
+ country,
+ }),
+ ),
+ ),
+ },
+ });
+ };
+
return (
Add {props.method} authentication
@@ -526,29 +809,7 @@ function AuthMethodPostSetup(props: AuthMethodSetupProps) {
props.cancel()}>Cancel
-
- props.addAuthMethod({
- authentication_method: {
- type: "email",
- instructions: `Letter to address in postal code ${postcode}`,
- challenge: encodeCrock(
- stringToBytes(
- canonicalJson({
- full_name: fullName,
- street,
- city,
- postcode,
- country,
- }),
- ),
- ),
- },
- })
- }
- >
- Add
-
+ addPostAuth()}>Add
@@ -557,15 +818,10 @@ function AuthMethodPostSetup(props: AuthMethodSetupProps) {
function AuthMethodNotImplemented(props: AuthMethodSetupProps) {
return (
-
-
Add {props.method} authentication
-
-
- This auth method is not implemented yet, please choose another one.
-
-
props.cancel()}>Cancel
-
-
+
+ This auth method is not implemented yet, please choose another one.
+ props.cancel()}>Cancel
+
);
}
@@ -583,8 +839,10 @@ function AuthenticationEditor(props: AuthenticationEditorProps) {
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 (p.methods) {
+ for (const meth of p.methods) {
+ authAvailableSet.add(meth.type);
+ }
}
}
if (selectedMethod) {
@@ -653,10 +911,7 @@ function AuthenticationEditor(props: AuthenticationEditorProps) {
backupState.authentication_methods ?? [];
const haveMethodsConfigured = configuredAuthMethods.length;
return (
-
-
Backup: Configure Authentication Methods
-
-
Add authentication method
+
@@ -686,11 +941,7 @@ function AuthenticationEditor(props: AuthenticationEditorProps) {
) : (
No authentication methods configured yet.
)}
-
- reducer.back()}>Back
- reducer.transition("next", {})}>Next
-
-
+
);
}
@@ -701,36 +952,29 @@ export interface AttributeEntryProps {
function AttributeEntry(props: AttributeEntryProps) {
const { reducer, reducerState: backupState } = props;
- const [attrs, setAttrs] = useState
>({});
+ const [attrs, setAttrs] = useState>(
+ props.reducerState.identity_attributes ?? {},
+ );
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]}
- />
- );
- })}
-
-
- reducer.back()}>Back
-
- reducer.transition("enter_user_attributes", {
- identity_attributes: attrs,
- })
- }
- >
- Next
-
-
-
+
+ reducer.transition("enter_user_attributes", {
+ identity_attributes: attrs,
+ })
+ }
+ >
+ {backupState.required_attributes.map((x: any, i: number) => {
+ return (
+ setAttrs({ ...attrs, [x.name]: v })}
+ spec={x}
+ value={attrs[x.name]}
+ />
+ );
+ })}
+
);
}
@@ -744,8 +988,9 @@ export interface AttributeEntryFieldProps {
function AttributeEntryField(props: AttributeEntryFieldProps) {
return (
- {props.spec.label}
+ {props.spec.label}: