aboutsummaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/pages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-11-03 17:30:11 -0300
committerSebastian <sebasjm@gmail.com>2021-11-03 17:35:29 -0300
commita82b5a6992fda61d6eaa0bb079e284805a394777 (patch)
tree857168ae8c28d93253ec319708ae0818bd76c30f /packages/anastasis-webui/src/pages
parent9fb6536fbc91adaf7a8a80860fcef5e1f80bfb3b (diff)
downloadwallet-core-a82b5a6992fda61d6eaa0bb079e284805a394777.tar.xz
feedback from meeting and editing policy
Diffstat (limited to 'packages/anastasis-webui/src/pages')
-rw-r--r--packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx4
-rw-r--r--packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx37
-rw-r--r--packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx4
-rw-r--r--packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx7
-rw-r--r--packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx18
-rw-r--r--packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx36
-rw-r--r--packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx39
-rw-r--r--packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx31
-rw-r--r--packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx109
-rw-r--r--packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx133
-rw-r--r--packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx8
-rw-r--r--packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx57
-rw-r--r--packages/anastasis-webui/src/pages/home/StartScreen.tsx42
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx15
-rw-r--r--packages/anastasis-webui/src/pages/home/index.tsx33
15 files changed, 406 insertions, 167 deletions
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
index 32d7817e3..549686616 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx
@@ -52,8 +52,8 @@ export const Backup = createExample(TestedComponent, {
uuid: 'asdasdsa2',
widget: 'wid',
}, {
- name: 'date',
- label: 'third',
+ name: 'birthdate',
+ label: 'birthdate',
type: 'date',
uuid: 'asdasdsa3',
widget: 'calendar',
diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
index 2c7f54c5b..52046b216 100644
--- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx
@@ -7,6 +7,7 @@ import { AnastasisClientFrame, withProcessLabel } from "./index";
import { TextInput } from "../../components/fields/TextInput";
import { DateInput } from "../../components/fields/DateInput";
import { NumberInput } from "../../components/fields/NumberInput";
+import { isAfter, parse } from "date-fns";
export function AttributeEntryScreen(): VNode {
const reducer = useAnastasisContext()
@@ -46,15 +47,14 @@ export function AttributeEntryScreen(): VNode {
identity_attributes: attrs,
})}
>
- <div class="columns">
- <div class="column is-half">
+ <div class="columns" style={{ maxWidth: 'unset' }}>
+ <div class="column is-one-third">
{fieldList}
</div>
- <div class="column is-half" >
+ <div class="column is-two-third" >
<p>This personal information will help to locate your secret.</p>
- <h1><b>This stay private</b></h1>
- <p>The information you have entered here:
- </p>
+ <h1 class="title">This stays private</h1>
+ <p>The information you have entered here:</p>
<ul>
<li>
<span class="icon is-right">
@@ -111,15 +111,17 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
bind={[props.value, props.setValue]}
/>
}
- <span>
+ <div class="block">
+ This stays private
<span class="icon is-right">
<i class="mdi mdi-eye-off" />
</span>
- This stay private
- </span>
+ </div>
</div>
);
}
+const YEAR_REGEX = /^[0-9]+-[0-9]+-[0-9]+$/
+
function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined {
const pattern = spec['validation-regex']
@@ -136,5 +138,22 @@ function checkIfValid(value: string, spec: UserAttributeSpec): string | undefine
if (!optional && !value) {
return 'This value is required'
}
+ if ("date" === spec.type) {
+ if (!YEAR_REGEX.test(value)) {
+ return "The date doesn't follow the format"
+ }
+
+ try {
+ const v = parse(value, 'yyyy-MM-dd', new Date());
+ if (Number.isNaN(v.getTime())) {
+ return "Some numeric values seems out of range for a date"
+ }
+ if ("birthdate" === spec.name && isAfter(v, new Date())) {
+ return "A birthdate cannot be in the future"
+ }
+ } catch (e) {
+ return "Could not parse the date"
+ }
+ }
return undefined
}
diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx
index 4e7819a77..ab482044f 100644
--- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx
@@ -142,6 +142,10 @@ export function AuthenticationEditorScreen(): VNode {
</div>
<div class="column is-half">
When recovering your wallet, you will be asked to verify your identity via the methods you configure here.
+
+ <b>Explain the exclamation marks</b>
+
+ <a>Explain how to add providers</a>
</div>
</div>
</AnastasisClientFrame>
diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx
index 70ac8157d..7938baca4 100644
--- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx
@@ -23,7 +23,7 @@ export function BackupFinishedScreen(): VNode {
</p>}
{details && <div class="block">
- <p>The backup is stored by the following providers:</p>
+ <p>The backup is stored by the following providers:</p>
{Object.keys(details).map((x, i) => {
const sd = details[x];
return (
@@ -31,11 +31,14 @@ export function BackupFinishedScreen(): VNode {
{x}
<p>
version {sd.policy_version}
- {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd/MM/yyyy')}` : ' without expiration date'}
+ {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd-MM-yyyy')}` : ' without expiration date'}
</p>
</div>
);
})}
</div>}
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={() => reducer.back()}>Back</button>
+ </div>
</AnastasisClientFrame>);
}
diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
index 2186eb42d..6bdb3515d 100644
--- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/camelcase */
/*
This file is part of GNU Taler
(C) 2021 Taler Systems S.A.
@@ -19,12 +20,13 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
+import { ReducerState } from 'anastasis-core';
import { createExample, reducerStatesExample } from '../../utils';
import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectionScreen';
export default {
- title: 'Pages/ContinentSelectionScreen',
+ title: 'Pages/Location',
component: TestedComponent,
args: {
order: 2,
@@ -35,6 +37,16 @@ export default {
},
};
-export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
+export const BackupSelectContinent = createExample(TestedComponent, reducerStatesExample.backupSelectContinent);
-export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
+export const BackupSelectCountry = createExample(TestedComponent, {
+ ...reducerStatesExample.backupSelectContinent,
+ selected_continent: 'Testcontinent',
+} as ReducerState);
+
+export const RecoverySelectContinent = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent);
+
+export const RecoverySelectCountry = createExample(TestedComponent, {
+ ...reducerStatesExample.recoverySelectContinent,
+ selected_continent: 'Testcontinent',
+} as ReducerState);
diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
index 713655625..4ab0e6a9b 100644
--- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx
@@ -36,20 +36,21 @@ export function ContinentSelectionScreen(): VNode {
})
}
- const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
- reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting;
+ // const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting ||
+ // reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting;
const errors = !theCountry ? "Select a country" : undefined
return (
- <AnastasisClientFrame hideNext={errors} title={withProcessLabel(reducer, "Select location")} onNext={selectCountryAction}>
- <div class="columns">
- <div class="column is-half">
+ <AnastasisClientFrame hideNext={errors} title={withProcessLabel(reducer, "Where do you live?")} onNext={selectCountryAction}>
+
+ <div class="columns" >
+ <div class="column is-one-third">
<div class="field">
<label class="label">Continent</label>
- <div class="control has-icons-left">
- <div class="select " >
- <select onChange={(e) => selectContinent(e.currentTarget.value)} value={theContinent} disabled={!step1}>
+ <div class="control is-expanded has-icons-left">
+ <div class="select is-fullwidth" >
+ <select onChange={(e) => selectContinent(e.currentTarget.value)} value={theContinent} >
<option key="none" disabled selected value=""> Choose a continent </option>
{continentList.map(prov => (
<option key={prov.name} value={prov.name}>
@@ -61,18 +62,13 @@ export function ContinentSelectionScreen(): VNode {
<i class="mdi mdi-earth" />
</div>
</div>
- {!step1 && <span class="control">
- <a class="button is-danger" onClick={() => reducer.back()}>
- X
- </a>
- </span>}
</div>
</div>
<div class="field">
<label class="label">Country</label>
- <div class="control has-icons-left">
- <div class="select" >
+ <div class="control is-expanded has-icons-left">
+ <div class="select is-fullwidth" >
<select onChange={(e) => selectCountry((e.target as any).value)} disabled={!theContinent} value={theCountry?.code || ""}>
<option key="none" disabled selected value=""> Choose a country </option>
{countryList.map(prov => (
@@ -88,17 +84,17 @@ export function ContinentSelectionScreen(): VNode {
</div>
</div>
- {theCountry && <div class="field">
+ {/* {theCountry && <div class="field">
<label class="label">Available currencies:</label>
<div class="control">
<input class="input is-small" type="text" readonly value={theCountry.currency} />
</div>
- </div>}
+ </div>} */}
</div>
- <div class="column is-half">
+ <div class="column is-two-third">
<p>
- A location will help to define a common information that will be use to locate your secret and a currency
- for payments if needed.
+ Your location will help us to determine which personal information
+ ask you for the next step.
</p>
</div>
</div>
diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx
deleted file mode 100644
index 3a642748a..000000000
--- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { createExample, reducerStatesExample } from '../../utils';
-import { CountrySelectionScreen as TestedComponent } from './CountrySelectionScreen';
-
-
-export default {
- title: 'Pages/CountrySelectionScreen',
- component: TestedComponent,
- args: {
- order: 3,
- },
- argTypes: {
- onUpdate: { action: 'onUpdate' },
- onBack: { action: 'onBack' },
- },
-};
-
-export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectCountry);
-export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectCountry);
diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx
deleted file mode 100644
index b64e1a096..000000000
--- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint-disable @typescript-eslint/camelcase */
-import { h, VNode } from "preact";
-import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisClientFrame, withProcessLabel } from "./index";
-
-export function CountrySelectionScreen(): VNode {
- const reducer = useAnastasisContext()
- if (!reducer) {
- return <div>no reducer in context</div>
- }
- if (!reducer.currentReducerState || !("countries" in reducer.currentReducerState)) {
- return <div>invalid state</div>
- }
- const sel = (x: any): void => reducer.transition("select_country", {
- country_code: x.code,
- currencies: [x.currency],
- });
- return (
- <AnastasisClientFrame hideNext={"FIXME"} title={withProcessLabel(reducer, "Select Country")} >
- <div style={{ display: 'flex', flexDirection: 'column' }}>
- {reducer.currentReducerState.countries!.map((x: any) => (
- <div key={x.name}>
- <button class="button" onClick={() => sel(x)} >
- {x.name} ({x.currency})
- </button>
- </div>
- ))}
- </div>
- </AnastasisClientFrame>
- );
-}
diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
new file mode 100644
index 000000000..fc339e48e
--- /dev/null
+++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx
@@ -0,0 +1,109 @@
+/* eslint-disable @typescript-eslint/camelcase */
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { ReducerState } from 'anastasis-core';
+import { createExample, reducerStatesExample } from '../../utils';
+import { EditPoliciesScreen as TestedComponent } from './EditPoliciesScreen';
+
+
+export default {
+ title: 'Pages/backup/ReviewPoliciesScreen/EditPoliciesScreen',
+ args: {
+ order: 6,
+ },
+ component: TestedComponent,
+ argTypes: {
+ onUpdate: { action: 'onUpdate' },
+ onBack: { action: 'onBack' },
+ },
+};
+
+export const EditingAPolicy = createExample(TestedComponent, {
+ ...reducerStatesExample.policyReview,
+ policies: [{
+ methods: [{
+ authentication_method: 1,
+ provider: 'https://anastasis.demo.taler.net/'
+ }, {
+ authentication_method: 2,
+ provider: 'http://localhost:8086/'
+ }]
+ }, {
+ methods: [{
+ authentication_method: 1,
+ provider: 'http://localhost:8086/'
+ }]
+ }],
+ authentication_methods: [{
+ type: "email",
+ instructions: "Email to qwe@asd.com",
+ challenge: "E5VPA"
+ }, {
+ type: "totp",
+ instructions: "Response code for 'Anastasis'",
+ challenge: "E5VPA"
+ }, {
+ type: "sms",
+ instructions: "SMS to 6666-6666",
+ challenge: ""
+ }, {
+ type: "question",
+ instructions: "How did the chicken cross the road?",
+ challenge: "C5SP8"
+ }]
+} as ReducerState, { index : 0});
+
+export const CreatingAPolicy = createExample(TestedComponent, {
+ ...reducerStatesExample.policyReview,
+ policies: [{
+ methods: [{
+ authentication_method: 1,
+ provider: 'https://anastasis.demo.taler.net/'
+ }, {
+ authentication_method: 2,
+ provider: 'http://localhost:8086/'
+ }]
+ }, {
+ methods: [{
+ authentication_method: 1,
+ provider: 'http://localhost:8086/'
+ }]
+ }],
+ authentication_methods: [{
+ type: "email",
+ instructions: "Email to qwe@asd.com",
+ challenge: "E5VPA"
+ }, {
+ type: "totp",
+ instructions: "Response code for 'Anastasis'",
+ challenge: "E5VPA"
+ }, {
+ type: "sms",
+ instructions: "SMS to 6666-6666",
+ challenge: ""
+ }, {
+ type: "question",
+ instructions: "How did the chicken cross the road?",
+ challenge: "C5SP8"
+ }]
+} as ReducerState, { index : 3});
+
diff --git a/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx
new file mode 100644
index 000000000..85cc96c46
--- /dev/null
+++ b/packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx
@@ -0,0 +1,133 @@
+/* eslint-disable @typescript-eslint/camelcase */
+import { AuthMethod, Policy } from "anastasis-core";
+import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { useAnastasisContext } from "../../context/anastasis";
+import { authMethods, KnownAuthMethods } from "./authMethod";
+import { AnastasisClientFrame } from "./index";
+
+export interface ProviderInfo {
+ url: string;
+ cost: string;
+ isFree: boolean;
+}
+
+export type ProviderInfoByType = {
+ [type in KnownAuthMethods]?: ProviderInfo[];
+};
+
+interface Props {
+ index: number;
+ cancel: () => void;
+ confirm: (changes: MethodProvider[]) => void;
+
+}
+
+export interface MethodProvider {
+ authentication_method: number;
+ provider: string;
+}
+
+export function EditPoliciesScreen({ index: policy_index, cancel, confirm }: Props): VNode {
+ const [changedProvider, setChangedProvider] = useState<Array<string>>([])
+
+ const reducer = useAnastasisContext()
+ if (!reducer) {
+ return <div>no reducer in context</div>
+ }
+ if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
+ return <div>invalid state</div>
+ }
+
+ const selectableProviders: ProviderInfoByType = {}
+ const allProviders = Object.entries(reducer.currentReducerState.authentication_providers || {})
+ for (let index = 0; index < allProviders.length; index++) {
+ const [url, status] = allProviders[index]
+ if ("methods" in status) {
+ status.methods.map(m => {
+ const type: KnownAuthMethods = m.type as KnownAuthMethods
+ const values = selectableProviders[type] || []
+ const isFree = !m.usage_fee || m.usage_fee.endsWith(":0")
+ values.push({ url, cost: m.usage_fee, isFree })
+ selectableProviders[type] = values
+ })
+ }
+ }
+
+ const allAuthMethods = reducer.currentReducerState.authentication_methods ?? [];
+ const policies = reducer.currentReducerState.policies ?? [];
+ const policy = policies[policy_index]
+
+ for(let method_index = 0; method_index < allAuthMethods.length; method_index++ ) {
+ policy?.methods.find(m => m.authentication_method === method_index)?.provider
+ }
+
+ function sendChanges(): void {
+ const newMethods: MethodProvider[] = []
+ allAuthMethods.forEach((method, index) => {
+ const oldValue = policy?.methods.find(m => m.authentication_method === index)
+ if (changedProvider[index] === undefined && oldValue !== undefined) {
+ newMethods.push(oldValue)
+ }
+ if (changedProvider[index] !== undefined && changedProvider[index] !== "") {
+ newMethods.push({
+ authentication_method: index,
+ provider: changedProvider[index]
+ })
+ }
+ })
+ confirm(newMethods)
+ }
+
+ return <AnastasisClientFrame hideNav title={!policy ? "Backup: New Policy" : "Backup: Edit Policy"}>
+ <section class="section">
+ {!policy ? <p>
+ Creating a new policy #{policy_index}
+ </p> : <p>
+ Editing policy #{policy_index}
+ </p>}
+ {allAuthMethods.map((method, index) => {
+ //take the url from the updated change or from the policy
+ const providerURL = changedProvider[index] === undefined ?
+ policy?.methods.find(m => m.authentication_method === index)?.provider :
+ changedProvider[index];
+
+ const type: KnownAuthMethods = method.type as KnownAuthMethods
+ function changeProviderTo(url: string): void {
+ const copy = [...changedProvider]
+ copy[index] = url
+ setChangedProvider(copy)
+ }
+ return (
+ <div key={index} class="block" style={{ display: 'flex', alignItems: 'center' }}>
+ <span class="icon">
+ {authMethods[type]?.icon}
+ </span>
+ <span>
+ {method.instructions}
+ </span>
+ <span>
+ <span class="select " >
+ <select onChange={(e) => changeProviderTo(e.currentTarget.value)} value={providerURL ?? ""}>
+ <option key="none" value=""> &lt;&lt; off &gt;&gt; </option>
+ {selectableProviders[type]?.map(prov => (
+ <option key={prov.url} value={prov.url}>
+ {prov.url}
+ </option>
+ ))}
+ </select>
+ </span>
+ </span>
+ </div>
+ );
+ })}
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span class="buttons">
+ <button class="button" onClick={() => setChangedProvider([])}>Reset</button>
+ <button class="button is-info" onClick={sendChanges}>Confirm</button>
+ </span>
+ </div>
+ </section>
+ </AnastasisClientFrame>
+}
diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
index 5ba0c937d..9f7e26c16 100644
--- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
+++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx
@@ -233,16 +233,16 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, {
instructions: "Does P equal NP?",
challenge: "C5SP8"
},{
- type: "email",
- instructions: "Email to qwe@asd.com",
+ type: "totp",
+ instructions: "Response code for 'Anastasis'",
challenge: "E5VPA"
}, {
type: "sms",
- instructions: "SMS to 555-555",
+ instructions: "SMS to 6666-6666",
challenge: ""
}, {
type: "question",
- instructions: "Does P equal NP?",
+ instructions: "How did the chicken cross the road?",
challenge: "C5SP8"
}]
} as ReducerState);
diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx
index 673f215e2..b8beb7b47 100644
--- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx
@@ -1,10 +1,14 @@
/* eslint-disable @typescript-eslint/camelcase */
+import { AuthMethod } from "anastasis-core";
import { h, VNode } from "preact";
+import { useState } from "preact/hooks";
import { useAnastasisContext } from "../../context/anastasis";
-import { AnastasisClientFrame } from "./index";
import { authMethods, KnownAuthMethods } from "./authMethod";
+import { EditPoliciesScreen } from "./EditPoliciesScreen";
+import { AnastasisClientFrame } from "./index";
export function ReviewPoliciesScreen(): VNode {
+ const [editingPolicy, setEditingPolicy] = useState<number | undefined>()
const reducer = useAnastasisContext()
if (!reducer) {
return <div>no reducer in context</div>
@@ -12,20 +16,44 @@ export function ReviewPoliciesScreen(): VNode {
if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) {
return <div>invalid state</div>
}
+
const configuredAuthMethods = reducer.currentReducerState.authentication_methods ?? [];
const policies = reducer.currentReducerState.policies ?? [];
+ if (editingPolicy !== undefined) {
+ return (
+ <EditPoliciesScreen
+ index={editingPolicy}
+ cancel={() => setEditingPolicy(undefined)}
+ confirm={(newMethods) => {
+ reducer.runTransaction(async (tx) => {
+ await tx.transition("delete_policy", {
+ policy_index: editingPolicy
+ });
+ await tx.transition("add_policy", {
+ policy: newMethods
+ });
+ });
+ setEditingPolicy(undefined)
+ }}
+ />
+ )
+ }
+
const errors = policies.length < 1 ? 'Need more policies' : undefined
return (
<AnastasisClientFrame hideNext={errors} title="Backup: Review Recovery Policies">
{policies.length > 0 && <p class="block">
Based on your configured authentication method you have created, some policies
- have been configured. In order to recover your secret you have to solve all the
+ have been configured. In order to recover your secret you have to solve all the
challenges of at least one policy.
- </p> }
+ </p>}
{policies.length < 1 && <p class="block">
No policies had been created. Go back and add more authentication methods.
- </p> }
+ </p>}
+ <div class="block" onClick={() => setEditingPolicy(policies.length + 1)}>
+ <button class="button is-success">Add new policy</button>
+ </div>
{policies.map((p, policy_index) => {
const methods = p.methods
.map(x => configuredAuthMethods[x.authentication_method] && ({ ...configuredAuthMethods[x.authentication_method], provider: x.provider }))
@@ -44,18 +72,21 @@ export function ReviewPoliciesScreen(): VNode {
</p>}
{methods.map((m, i) => {
return (
- <p key={i} class="block" style={{display:'flex', alignItems:'center'}}>
- <span class="icon">
- {authMethods[m.type as KnownAuthMethods]?.icon}
- </span>
- <span>
- {m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a>
- </span>
- </p>
+ <p key={i} class="block" style={{ display: 'flex', alignItems: 'center' }}>
+ <span class="icon">
+ {authMethods[m.type as KnownAuthMethods]?.icon}
+ </span>
+ <span>
+ {m.instructions} recovery provided by <a href={m.provider}>{m.provider}</a>
+ </span>
+ </p>
);
})}
</div>
- <div style={{ marginTop: 'auto', marginBottom: 'auto' }}><button class="button is-danger" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button></div>
+ <div style={{ marginTop: 'auto', marginBottom: 'auto', display: 'flex', justifyContent: 'space-between', flexDirection: 'column' }}>
+ <button class="button is-info block" onClick={() => setEditingPolicy(policy_index)}>Edit</button>
+ <button class="button is-danger block" onClick={() => reducer.transition("delete_policy", { policy_index })}>Delete</button>
+ </div>
</div>
);
})}
diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx
index c751ad9e4..6e97eb586 100644
--- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx
+++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx
@@ -10,33 +10,29 @@ export function StartScreen(): VNode {
}
return (
<AnastasisClientFrame hideNav title="Home">
- <div>
- <section class="section is-main-section">
- <div class="columns">
- <div class="column" />
- <div class="column is-four-fifths">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
- <div class="buttons">
- <button class="button is-success" autoFocus onClick={() => reducer.startBackup()}>
- <div class="icon"><i class="mdi mdi-arrow-up" /></div>
- <span>Backup a secret</span>
- </button>
+ <div class="buttons">
+ <button class="button is-success" autoFocus onClick={() => reducer.startBackup()}>
+ <div class="icon"><i class="mdi mdi-arrow-up" /></div>
+ <span>Backup a secret</span>
+ </button>
- <button class="button is-info" onClick={() => reducer.startRecover()}>
- <div class="icon"><i class="mdi mdi-arrow-down" /></div>
- <span>Recover a secret</span>
- </button>
+ <button class="button is-info" onClick={() => reducer.startRecover()}>
+ <div class="icon"><i class="mdi mdi-arrow-down" /></div>
+ <span>Recover a secret</span>
+ </button>
- <button class="button">
- <div class="icon"><i class="mdi mdi-file" /></div>
- <span>Restore a session</span>
- </button>
- </div>
-
- </div>
- <div class="column" />
+ <button class="button">
+ <div class="icon"><i class="mdi mdi-file" /></div>
+ <span>Restore a session</span>
+ </button>
</div>
- </section>
+
+ </div>
+ <div class="column" />
</div>
</AnastasisClientFrame>
);
diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx
index eab800e35..04fa00d59 100644
--- a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx
+++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx
@@ -27,7 +27,7 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
<AnastasisClientFrame hideNav title="Add Security Question">
<div>
<p>
- For security question authentication, you need to provide a question
+ For2 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.
@@ -47,6 +47,13 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
/>
</div>
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button>
+ </span>
+ </div>
+
{configured.length > 0 && <section class="section">
<div class="block">
Your security questions:
@@ -58,12 +65,6 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
</div>
})}
</div></section>}
- <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
- <button class="button" onClick={cancel}>Cancel</button>
- <span data-tooltip={errors}>
- <button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button>
- </span>
- </div>
</div>
</AnastasisClientFrame >
);
diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx
index fefaa184c..415cf6e98 100644
--- a/packages/anastasis-webui/src/pages/home/index.tsx
+++ b/packages/anastasis-webui/src/pages/home/index.tsx
@@ -13,6 +13,7 @@ import {
import {
useErrorBoundary
} from "preact/hooks";
+import { AsyncButton } from "../../components/AsyncButton";
import { Menu } from "../../components/menu";
import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis";
import {
@@ -25,7 +26,6 @@ import { BackupFinishedScreen } from "./BackupFinishedScreen";
import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen";
import { ChallengePayingScreen } from "./ChallengePayingScreen";
import { ContinentSelectionScreen } from "./ContinentSelectionScreen";
-import { CountrySelectionScreen } from "./CountrySelectionScreen";
import { PoliciesPayingScreen } from "./PoliciesPayingScreen";
import { RecoveryFinishedScreen } from "./RecoveryFinishedScreen";
import { ReviewPoliciesScreen } from "./ReviewPoliciesScreen";
@@ -95,12 +95,19 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
if (!reducer) {
return <p>Fatal: Reducer must be in context.</p>;
}
- const next = (): void => {
- if (props.onNext) {
- props.onNext();
- } else {
- reducer.transition("next", {});
- }
+ const next = async (): Promise<void> => {
+ return new Promise((res, rej) => {
+ try {
+ if (props.onNext) {
+ props.onNext();
+ } else {
+ reducer.transition("next", {});
+ }
+ res()
+ } catch {
+ rej()
+ }
+ })
};
const handleKeyPress = (
e: h.JSX.TargetedKeyboardEvent<HTMLDivElement>,
@@ -111,20 +118,18 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
return (
<Fragment>
<Menu title="Anastasis" />
- <div>
- <div class="home" onKeyPress={(e) => handleKeyPress(e)}>
- <h1 class="title">{props.title}</h1>
+ <div class="home" onKeyPress={(e) => handleKeyPress(e)}>
+ <h1 class="title">{props.title}</h1>
+ <section class="section is-main-section">
<ErrorBanner />
{props.children}
{!props.hideNav ? (
<div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
<button class="button" onClick={() => reducer.back()}>Back</button>
- <span data-tooltip={props.hideNext}>
- <button class="button is-info" onClick={next} disabled={props.hideNext !== undefined}>Next</button>
- </span>
+ <AsyncButton data-tooltip={props.hideNext} onClick={next} disabled={props.hideNext !== undefined}>Next</AsyncButton>
</div>
) : null}
- </div>
+ </section>
</div>
</Fragment>
);