aboutsummaryrefslogtreecommitdiff
path: root/packages/anastasis-webui
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-04-12 12:54:57 +0200
committerFlorian Dold <florian@dold.me>2022-04-12 12:55:32 +0200
commit1e92093a50962f4702339e872caa4f82af90af70 (patch)
tree10d927c7e51e8fd1cf52629dd3d19d3fbd28b1bf /packages/anastasis-webui
parentafecab8000fa59475fe02a402176789e360651ba (diff)
downloadwallet-core-1e92093a50962f4702339e872caa4f82af90af70.tar.xz
anastasis: discovery
Diffstat (limited to 'packages/anastasis-webui')
-rw-r--r--packages/anastasis-webui/src/components/menu/NavigationBar.tsx2
-rw-r--r--packages/anastasis-webui/src/context/anastasis.ts7
-rw-r--r--packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts77
-rw-r--r--packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx100
4 files changed, 172 insertions, 14 deletions
diff --git a/packages/anastasis-webui/src/components/menu/NavigationBar.tsx b/packages/anastasis-webui/src/components/menu/NavigationBar.tsx
index 8d5a0473b..bc6d923d7 100644
--- a/packages/anastasis-webui/src/components/menu/NavigationBar.tsx
+++ b/packages/anastasis-webui/src/components/menu/NavigationBar.tsx
@@ -46,7 +46,7 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode {
Contact us
</a>
<a
- href="https://bugs.anastasis.li/"
+ href="https://bugs.anastasis.lu/"
style={{ alignSelf: "center", padding: "0.5em" }}
>
Report a bug
diff --git a/packages/anastasis-webui/src/context/anastasis.ts b/packages/anastasis-webui/src/context/anastasis.ts
index c2e7b2a47..40d25d144 100644
--- a/packages/anastasis-webui/src/context/anastasis.ts
+++ b/packages/anastasis-webui/src/context/anastasis.ts
@@ -23,11 +23,9 @@ import { createContext, h, VNode } from "preact";
import { useContext } from "preact/hooks";
import { AnastasisReducerApi } from "../hooks/use-anastasis-reducer";
-type Type = AnastasisReducerApi | undefined;
-
const initial = undefined;
-const Context = createContext<Type>(initial);
+const Context = createContext<AnastasisReducerApi | undefined>(initial);
interface Props {
value: AnastasisReducerApi;
@@ -38,4 +36,5 @@ export const AnastasisProvider = ({ value, children }: Props): VNode => {
return h(Context.Provider, { value, children });
};
-export const useAnastasisContext = (): Type => useContext(Context);
+export const useAnastasisContext = (): AnastasisReducerApi | undefined =>
+ useContext(Context);
diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
index b18610427..321cf3f0a 100644
--- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
+++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts
@@ -1,8 +1,12 @@
import { TalerErrorCode } from "@gnu-taler/taler-util";
import {
+ AggregatedPolicyMetaInfo,
BackupStates,
+ discoverPolicies,
+ DiscoveryCursor,
getBackupStartState,
getRecoveryStartState,
+ PolicyMetaInfo,
RecoveryStates,
reduceAction,
ReducerState,
@@ -15,6 +19,7 @@ const remoteReducer = false;
interface AnastasisState {
reducerState: ReducerState | undefined;
currentError: any;
+ discoveryState: DiscoveryUiState;
}
async function getBackupStartStateRemote(): Promise<ReducerState> {
@@ -98,9 +103,21 @@ export interface ReducerTransactionHandle {
transition(action: string, args: any): Promise<ReducerState>;
}
+/**
+ * UI-relevant state of the policy discovery process.
+ */
+export interface DiscoveryUiState {
+ state: "none" | "active" | "finished";
+
+ aggregatedPolicies?: AggregatedPolicyMetaInfo[];
+
+ cursor?: DiscoveryCursor;
+}
+
export interface AnastasisReducerApi {
currentReducerState: ReducerState | undefined;
currentError: any;
+ discoveryState: DiscoveryUiState;
dismissError: () => void;
startBackup: () => void;
startRecover: () => void;
@@ -109,6 +126,8 @@ export interface AnastasisReducerApi {
transition(action: string, args: any): Promise<void>;
exportState: () => string;
importState: (s: string) => void;
+ discoverStart(): Promise<void>;
+ discoverMore(): Promise<void>;
/**
* Run multiple reducer steps in a transaction without
* affecting the UI-visible transition state in-between.
@@ -152,6 +171,9 @@ export function useAnastasisReducer(): AnastasisReducerApi {
() => ({
reducerState: getStateFromStorage(),
currentError: undefined,
+ discoveryState: {
+ state: "none",
+ },
}),
);
@@ -192,6 +214,7 @@ export function useAnastasisReducer(): AnastasisReducerApi {
return {
currentReducerState: anastasisState.reducerState,
currentError: anastasisState.currentError,
+ discoveryState: anastasisState.discoveryState,
async startBackup() {
let s: ReducerState;
if (remoteReducer) {
@@ -213,17 +236,59 @@ export function useAnastasisReducer(): AnastasisReducerApi {
}
},
exportState() {
- const state = getStateFromStorage()
- return JSON.stringify(state)
+ const state = getStateFromStorage();
+ return JSON.stringify(state);
},
importState(s: string) {
try {
- const state = JSON.parse(s)
- setAnastasisState({ reducerState: state, currentError: undefined })
+ const state = JSON.parse(s);
+ setAnastasisState({
+ reducerState: state,
+ currentError: undefined,
+ discoveryState: {
+ state: "none",
+ },
+ });
} catch (e) {
- throw Error('could not restore the state')
+ throw Error("could not restore the state");
+ }
+ },
+ async discoverStart(): Promise<void> {
+ const res = await discoverPolicies(this.currentReducerState!, undefined);
+ const aggregatedPolicies: AggregatedPolicyMetaInfo[] = [];
+ const polHashToIndex: Record<string, number> = {};
+ for (const pol of res.policies) {
+ const oldIndex = polHashToIndex[pol.policy_hash];
+ if (oldIndex != null) {
+ aggregatedPolicies[oldIndex].providers.push({
+ provider_url: pol.provider_url,
+ version: pol.version,
+ });
+ } else {
+ aggregatedPolicies.push({
+ attribute_mask: pol.attribute_mask,
+ policy_hash: pol.policy_hash,
+ providers: [
+ {
+ provider_url: pol.provider_url,
+ version: pol.version,
+ },
+ ],
+ secret_name: pol.secret_name,
+ });
+ polHashToIndex[pol.policy_hash] = aggregatedPolicies.length - 1;
+ }
}
+ setAnastasisState({
+ ...anastasisState,
+ discoveryState: {
+ state: "finished",
+ aggregatedPolicies,
+ cursor: res.cursor,
+ },
+ });
},
+ async discoverMore(): Promise<void> {},
async startRecover() {
let s: ReducerState;
if (remoteReducer) {
@@ -301,7 +366,7 @@ export function useAnastasisReducer(): AnastasisReducerApi {
}
class ReducerTxImpl implements ReducerTransactionHandle {
- constructor(public transactionState: ReducerState) { }
+ constructor(public transactionState: ReducerState) {}
async transition(action: string, args: any): Promise<ReducerState> {
let s: ReducerState;
if (remoteReducer) {
diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
index 076d205b6..84f0303fe 100644
--- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx
@@ -1,9 +1,10 @@
import {
AuthenticationProviderStatus,
AuthenticationProviderStatusOk,
+ PolicyMetaInfo,
} from "@gnu-taler/anastasis-core";
import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import { AsyncButton } from "../../components/AsyncButton";
import { PhoneNumberInput } from "../../components/fields/NumberInput";
import { useAnastasisContext } from "../../context/anastasis";
@@ -13,8 +14,100 @@ import { AnastasisClientFrame } from "./index";
export function SecretSelectionScreen(): VNode {
const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
const reducer = useAnastasisContext();
+ const [manageProvider, setManageProvider] = useState(false);
+
+ useEffect(() => {
+ async function f() {
+ if (reducer) {
+ await reducer.discoverStart();
+ }
+ }
+ f().catch((e) => console.log(e));
+ }, []);
+
+ if (!reducer) {
+ return <div>no reducer in context</div>;
+ }
+
+ if (
+ !reducer.currentReducerState ||
+ reducer.currentReducerState.recovery_state === undefined
+ ) {
+ return <div>invalid state</div>;
+ }
+ const provs = reducer.currentReducerState.authentication_providers ?? {};
+ const recoveryDocument = reducer.currentReducerState.recovery_document;
+
+ if (manageProvider) {
+ return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
+ }
+
+ if (reducer.discoveryState.state === "none") {
+ // Can this even happen?
+ return (
+ <AnastasisClientFrame title="Recovery: Select secret">
+ <div>waiting to start discovery</div>
+ </AnastasisClientFrame>
+ );
+ }
+
+ if (reducer.discoveryState.state === "active") {
+ return (
+ <AnastasisClientFrame title="Recovery: Select secret">
+ <div>loading secret versions</div>
+ </AnastasisClientFrame>
+ );
+ }
+
+ const policies = reducer.discoveryState.aggregatedPolicies ?? [];
+
+ if (policies.length === 0) {
+ return (
+ <ChooseAnotherProviderScreen
+ providers={provs}
+ selected=""
+ onChange={(newProv) => () => {}}
+ ></ChooseAnotherProviderScreen>
+ );
+ }
+
+ return (
+ <AnastasisClientFrame title="Recovery: Select secret" hideNext="Please select version to recover">
+ <p>Found versions:</p>
+ {policies.map((x) => (
+ <div>
+ {x.policy_hash} / {x.secret_name}
+ <button
+ onClick={async () => {
+ await reducer.transition("change_version", {
+ selection: x,
+ });
+ }}
+ >
+ Recover
+ </button>
+ </div>
+ ))}
+ <button>Load older versions</button>
+ </AnastasisClientFrame>
+ );
+}
+
+export function OldSecretSelectionScreen(): VNode {
+ const [selectingVersion, setSelectingVersion] = useState<boolean>(false);
+ const reducer = useAnastasisContext();
const [manageProvider, setManageProvider] = useState(false);
+
+ useEffect(() => {
+ async function f() {
+ if (reducer) {
+ await reducer.discoverStart();
+ }
+ }
+ f().catch((e) => console.log(e));
+ }, []);
+
const currentVersion =
(reducer?.currentReducerState &&
"recovery_document" in reducer.currentReducerState &&
@@ -71,15 +164,16 @@ export function SecretSelectionScreen(): VNode {
return <AddingProviderScreen onCancel={() => setManageProvider(false)} />;
}
- const provierInfo = provs[
+ const providerInfo = provs[
recoveryDocument.provider_url
] as AuthenticationProviderStatusOk;
+
return (
<AnastasisClientFrame title="Recovery: Select secret">
<div class="columns">
<div class="column">
<div class="box" style={{ border: "2px solid green" }}>
- <h1 class="subtitle">{provierInfo.business_name}</h1>
+ <h1 class="subtitle">{providerInfo.business_name}</h1>
<div class="block">
{currentVersion === 0 ? (
<p>Set to recover the latest version</p>