From 21b60c8f6ff69bf114779a767a3ac3355f69a34f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 26 Oct 2021 12:08:03 -0300 Subject: added core validators, worked on look and feel --- .../src/components/fields/DateInput.tsx | 63 ++++ .../src/components/fields/LabeledInput.tsx | 40 +++ .../src/components/menu/SideBar.tsx | 18 +- .../src/components/picker/DatePicker.tsx | 324 +++++++++++++++++++++ .../components/picker/DurationPicker.stories.tsx | 50 ++++ .../src/components/picker/DurationPicker.tsx | 154 ++++++++++ .../pages/home/AttributeEntryScreen.stories.tsx | 67 ++++- .../src/pages/home/AttributeEntryScreen.tsx | 112 +++++-- .../src/pages/home/AuthMethodEmailSetup.tsx | 3 +- .../src/pages/home/AuthMethodPostSetup.tsx | 2 +- .../src/pages/home/AuthMethodQuestionSetup.tsx | 3 +- .../home/AuthenticationEditorScreen.stories.tsx | 5 +- .../pages/home/BackupFinishedScreen.stories.tsx | 5 +- .../pages/home/ChallengeOverviewScreen.stories.tsx | 35 ++- .../pages/home/ChallengePayingScreen.stories.tsx | 38 +++ .../src/pages/home/ChallengePayingScreen.tsx | 33 +++ .../home/ContinentSelectionScreen.stories.tsx | 7 +- .../src/pages/home/ContinentSelectionScreen.tsx | 4 +- .../pages/home/CountrySelectionScreen.stories.tsx | 3 + .../src/pages/home/CountrySelectionScreen.tsx | 14 +- .../pages/home/PoliciesPayingScreen.stories.tsx | 5 +- .../pages/home/RecoveryFinishedScreen.stories.tsx | 5 +- .../pages/home/ReviewPoliciesScreen.stories.tsx | 5 +- .../src/pages/home/SecretEditorScreen.stories.tsx | 5 +- .../src/pages/home/SecretEditorScreen.tsx | 5 +- .../pages/home/SecretSelectionScreen.stories.tsx | 5 +- .../src/pages/home/SolveEmailEntry.tsx | 3 +- .../src/pages/home/SolvePostEntry.tsx | 3 +- .../src/pages/home/SolveQuestionEntry.tsx | 3 +- .../src/pages/home/SolveScreen.stories.tsx | 5 +- .../src/pages/home/SolveSmsEntry.tsx | 3 +- .../src/pages/home/StartScreen.stories.tsx | 3 + .../anastasis-webui/src/pages/home/StartScreen.tsx | 15 +- .../src/pages/home/TruthsPayingScreen.stories.tsx | 5 +- .../src/pages/home/TruthsPayingScreen.tsx | 2 +- packages/anastasis-webui/src/pages/home/index.tsx | 42 +-- .../anastasis-webui/src/scss/_custom-calendar.scss | 4 + packages/anastasis-webui/src/utils/index.tsx | 20 +- 38 files changed, 991 insertions(+), 127 deletions(-) create mode 100644 packages/anastasis-webui/src/components/fields/DateInput.tsx create mode 100644 packages/anastasis-webui/src/components/fields/LabeledInput.tsx create mode 100644 packages/anastasis-webui/src/components/picker/DatePicker.tsx create mode 100644 packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx create mode 100644 packages/anastasis-webui/src/components/picker/DurationPicker.tsx create mode 100644 packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx new file mode 100644 index 000000000..c45acc6d2 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -0,0 +1,63 @@ +import { format } from "date-fns"; +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { DatePicker } from "../picker/DatePicker"; + +export interface DateInputProps { + label: string; + grabFocus?: boolean; + tooltip?: string; + error?: string; + bind: [string, (x: string) => void]; +} + +export function DateInput(props: DateInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + const [opened, setOpened2] = useState(false) + function setOpened(v: boolean) { + console.log('dale', v) + setOpened2(v) + } + + const value = props.bind[0]; + const [dirty, setDirty] = useState(false) + const showError = dirty && props.error + + return
+ +
+ { setOpened(true) }} + value={value} + ref={inputRef} /> + + + + +
+ {showError &&

{props.error}

} + setOpened(false)} + dateReceiver={(d) => { + setDirty(true) + const v = format(d, 'yyyy/MM/dd') + props.bind[1](v); + }} + /> +
+ ; + +} diff --git a/packages/anastasis-webui/src/components/fields/LabeledInput.tsx b/packages/anastasis-webui/src/components/fields/LabeledInput.tsx new file mode 100644 index 000000000..96d634a4f --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/LabeledInput.tsx @@ -0,0 +1,40 @@ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; + +export interface LabeledInputProps { + label: string; + grabFocus?: boolean; + error?: string; + tooltip?: string; + bind: [string, (x: string) => void]; +} + +export function LabeledInput(props: LabeledInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + const value = props.bind[0]; + const [dirty, setDirty] = useState(false) + const showError = dirty && props.error + return (
+ +
+ {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + ref={inputRef} + style={{ display: "block" }} /> +
+ {showError &&

{props.error}

} +
+ ); +} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index df582a5d0..12223d473 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -59,25 +59,21 @@ export function Sidebar({ mobile }: Props): VNode { {!reducer.currentReducerState &&
  • - Start one options + Select one option
  • } {reducer.currentReducerState && reducer.currentReducerState.backup_state ? -
  • +
  • - Continent selection -
    -
  • -
  • -
    - Country selection + Location & Currency
  • - - User attributes + Personal information
  • @@ -119,7 +115,7 @@ export function Sidebar({ mobile }: Props): VNode { : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state &&
  • - TruthsPaying + ContinentSelecting
  • diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx new file mode 100644 index 000000000..e51b3db68 --- /dev/null +++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx @@ -0,0 +1,324 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, Component } from "preact"; + +interface Props { + closeFunction?: () => void; + dateReceiver?: (d: Date) => void; + opened?: boolean; +} +interface State { + displayedMonth: number; + displayedYear: number; + selectYearMode: boolean; + currentDate: Date; +} +const now = new Date() + +const monthArrShortFull = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' +] + +const monthArrShort = [ + 'Jan', + 'Feb', + 'Mar', + 'Apr', + 'May', + 'Jun', + 'Jul', + 'Aug', + 'Sep', + 'Oct', + 'Nov', + 'Dec' +] + +const dayArr = [ + 'Sun', + 'Mon', + 'Tue', + 'Wed', + 'Thu', + 'Fri', + 'Sat' +] + +const yearArr: number[] = [] + + +// inspired by https://codepen.io/m4r1vs/pen/MOOxyE +export class DatePicker extends Component { + + closeDatePicker() { + this.props.closeFunction && this.props.closeFunction(); // Function gets passed by parent + } + + /** + * Gets fired when a day gets clicked. + * @param {object} e The event thrown by the element clicked + */ + dayClicked(e: any) { + + const element = e.target; // the actual element clicked + + if (element.innerHTML === '') return false; // don't continue if empty + + // get date from clicked element (gets attached when rendered) + const date = new Date(element.getAttribute('data-value')); + + // update the state + this.setState({ currentDate: date }); + this.passDateToParent(date) + } + + /** + * returns days in month as array + * @param {number} month the month to display + * @param {number} year the year to display + */ + getDaysByMonth(month: number, year: number) { + + const calendar = []; + + const date = new Date(year, month, 1); // month to display + + const firstDay = new Date(year, month, 1).getDay(); // first weekday of month + const lastDate = new Date(year, month + 1, 0).getDate(); // last date of month + + let day: number | null = 0; + + // the calendar is 7*6 fields big, so 42 loops + for (let i = 0; i < 42; i++) { + + if (i >= firstDay && day !== null) day = day + 1; + if (day !== null && day > lastDate) day = null; + + // append the calendar Array + calendar.push({ + day: (day === 0 || day === null) ? null : day, // null or number + date: (day === 0 || day === null) ? null : new Date(year, month, day), // null or Date() + today: (day === now.getDate() && month === now.getMonth() && year === now.getFullYear()) // boolean + }); + } + + return calendar; + } + + /** + * Display previous month by updating state + */ + displayPrevMonth() { + if (this.state.displayedMonth <= 0) { + this.setState({ + displayedMonth: 11, + displayedYear: this.state.displayedYear - 1 + }); + } + else { + this.setState({ + displayedMonth: this.state.displayedMonth - 1 + }); + } + } + + /** + * Display next month by updating state + */ + displayNextMonth() { + if (this.state.displayedMonth >= 11) { + this.setState({ + displayedMonth: 0, + displayedYear: this.state.displayedYear + 1 + }); + } + else { + this.setState({ + displayedMonth: this.state.displayedMonth + 1 + }); + } + } + + /** + * Display the selected month (gets fired when clicking on the date string) + */ + displaySelectedMonth() { + if (this.state.selectYearMode) { + this.toggleYearSelector(); + } + else { + if (!this.state.currentDate) return false; + this.setState({ + displayedMonth: this.state.currentDate.getMonth(), + displayedYear: this.state.currentDate.getFullYear() + }); + } + } + + toggleYearSelector() { + this.setState({ selectYearMode: !this.state.selectYearMode }); + } + + changeDisplayedYear(e: any) { + const element = e.target; + this.toggleYearSelector(); + this.setState({ displayedYear: parseInt(element.innerHTML, 10), displayedMonth: 0 }); + } + + /** + * Pass the selected date to parent when 'OK' is clicked + */ + passSavedDateDateToParent() { + this.passDateToParent(this.state.currentDate) + } + passDateToParent(date: Date) { + if (typeof this.props.dateReceiver === 'function') this.props.dateReceiver(date); + this.closeDatePicker(); + } + + componentDidUpdate() { + if (this.state.selectYearMode) { + document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it + } + } + + constructor() { + super(); + + this.closeDatePicker = this.closeDatePicker.bind(this); + this.dayClicked = this.dayClicked.bind(this); + this.displayNextMonth = this.displayNextMonth.bind(this); + this.displayPrevMonth = this.displayPrevMonth.bind(this); + this.getDaysByMonth = this.getDaysByMonth.bind(this); + this.changeDisplayedYear = this.changeDisplayedYear.bind(this); + this.passDateToParent = this.passDateToParent.bind(this); + this.toggleYearSelector = this.toggleYearSelector.bind(this); + this.displaySelectedMonth = this.displaySelectedMonth.bind(this); + + + this.state = { + currentDate: now, + displayedMonth: now.getMonth(), + displayedYear: now.getFullYear(), + selectYearMode: false + } + } + + render() { + + const { currentDate, displayedMonth, displayedYear, selectYearMode } = this.state; + + return ( +
    +
    + +
    +

    {currentDate.getFullYear()}

    +

    + {dayArr[currentDate.getDay()]}, {monthArrShort[currentDate.getMonth()]} {currentDate.getDate()} +

    +
    + + {!selectYearMode && } + +
    + + {!selectYearMode &&
    + +
    + {['S', 'M', 'T', 'W', 'T', 'F', 'S'].map((day,i) => {day})} +
    + +
    + + {/* + Loop through the calendar object returned by getDaysByMonth(). + */} + + {this.getDaysByMonth(this.state.displayedMonth, this.state.displayedYear) + .map( + day => { + let selected = false; + + if (currentDate && day.date) selected = (currentDate.toLocaleDateString() === day.date.toLocaleDateString()); + + return ( + {day.day} + ) + } + ) + } + +
    + +
    } + + {selectYearMode &&
    + + {yearArr.map(year => ( + + {year} + + ))} + +
    } + +
    +
    + +
    + +
    + ) + } +} + + +for (let i = 2010; i <= now.getFullYear() + 10; i++) { + yearArr.push(i); +} diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx new file mode 100644 index 000000000..275c80fa6 --- /dev/null +++ b/packages/anastasis-webui/src/components/picker/DurationPicker.stories.tsx @@ -0,0 +1,50 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, FunctionalComponent } from 'preact'; +import { useState } from 'preact/hooks'; +import { DurationPicker as TestedComponent } from './DurationPicker'; + + +export default { + title: 'Components/Picker/Duration', + component: TestedComponent, + argTypes: { + onCreate: { action: 'onCreate' }, + goBack: { action: 'goBack' }, + } +}; + +function createExample(Component: FunctionalComponent, props: Partial) { + const r = (args: any) => + r.args = props + return r +} + +export const Example = createExample(TestedComponent, { + days: true, minutes: true, hours: true, seconds: true, + value: 10000000 +}); + +export const WithState = () => { + const [v,s] = useState(1000000) + return +} diff --git a/packages/anastasis-webui/src/components/picker/DurationPicker.tsx b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx new file mode 100644 index 000000000..235a63e2d --- /dev/null +++ b/packages/anastasis-webui/src/components/picker/DurationPicker.tsx @@ -0,0 +1,154 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { useTranslator } from "../../i18n"; +import "../../scss/DurationPicker.scss"; + +export interface Props { + hours?: boolean; + minutes?: boolean; + seconds?: boolean; + days?: boolean; + onChange: (value: number) => void; + value: number +} + +// inspiration taken from https://github.com/flurmbo/react-duration-picker +export function DurationPicker({ days, hours, minutes, seconds, onChange, value }: Props): VNode { + const ss = 1000 + const ms = ss * 60 + const hs = ms * 60 + const ds = hs * 24 + const i18n = useTranslator() + + return
    + {days && = ds ? () => onChange(value - ds) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ds) : undefined} + onChange={diff => onChange(value + diff * ds)} + />} + {hours && = hs ? () => onChange(value - hs) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + hs) : undefined} + onChange={diff => onChange(value + diff * hs)} + />} + {minutes && = ms ? () => onChange(value - ms) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ms) : undefined} + onChange={diff => onChange(value + diff * ms)} + />} + {seconds && = ss ? () => onChange(value - ss) : undefined} + onIncrease={value < 99 * ds ? () => onChange(value + ss) : undefined} + onChange={diff => onChange(value + diff * ss)} + />} +
    +} + +interface ColProps { + unit: string, + min?: number, + max: number, + value: number, + onIncrease?: () => void; + onDecrease?: () => void; + onChange?: (diff: number) => void; +} + +function InputNumber({ initial, onChange }: { initial: number, onChange: (n: number) => void }) { + const [value, handler] = useState<{v:string}>({ + v: toTwoDigitString(initial) + }) + + return onChange(parseInt(value.v, 10))} + onInput={(e) => { + e.preventDefault() + const n = Number.parseInt(e.currentTarget.value, 10); + if (isNaN(n)) return handler({v:toTwoDigitString(initial)}) + return handler({v:toTwoDigitString(n)}) + }} + style={{ width: 50, border: 'none', fontSize: 'inherit', background: 'inherit' }} /> +} + +function DurationColumn({ unit, min = 0, max, value, onIncrease, onDecrease, onChange }: ColProps): VNode { + + const cellHeight = 35 + return ( +
    +
    +
    +
    + +
    + +
    + {onDecrease && } +
    +
    + {value > min ? toTwoDigitString(value - 1) : ''} +
    +
    + {onChange ? + onChange(n - value)} /> : + toTwoDigitString(value) + } +
    {unit}
    +
    + +
    + {value < max ? toTwoDigitString(value + 1) : ''} +
    + +
    + {onIncrease && } +
    + +
    +
    +
    + ); +} + + +function toTwoDigitString(n: number) { + if (n < 10) { + return `0${n}`; + } + return `${n}`; +} \ No newline at end of file diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx index d28a6df43..d9be48fb4 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx @@ -28,14 +28,40 @@ import { AttributeEntryScreen as TestedComponent } from './AttributeEntryScreen' export default { title: 'Pages/AttributeEntryScreen', component: TestedComponent, + args: { + order: 4, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, }, }; -export const WithSomeAttributes = createExample(TestedComponent, { - ...reducerStatesExample.attributeEditing, +export const Backup = createExample(TestedComponent, { + ...reducerStatesExample.backupAttributeEditing, + required_attributes: [{ + name: 'first', + label: 'first', + type: 'type', + uuid: 'asdasdsa1', + widget: 'wid', + }, { + name: 'pepe', + label: 'second', + type: 'type', + uuid: 'asdasdsa2', + widget: 'wid', + }, { + name: 'pepe2', + label: 'third', + type: 'type', + uuid: 'asdasdsa3', + widget: 'calendar', + }] +} as ReducerState); + +export const Recovery = createExample(TestedComponent, { + ...reducerStatesExample.recoveryAttributeEditing, required_attributes: [{ name: 'first', label: 'first', @@ -57,7 +83,40 @@ export const WithSomeAttributes = createExample(TestedComponent, { }] } as ReducerState); -export const Empty = createExample(TestedComponent, { - ...reducerStatesExample.attributeEditing, +export const WithNoRequiredAttribute = createExample(TestedComponent, { + ...reducerStatesExample.backupAttributeEditing, required_attributes: undefined } as ReducerState); + +const allWidgets = [ + "anastasis_gtk_ia_aadhar_in", + "anastasis_gtk_ia_ahv", + "anastasis_gtk_ia_birthdate", + "anastasis_gtk_ia_birthnumber_cz", + "anastasis_gtk_ia_birthnumber_sk", + "anastasis_gtk_ia_birthplace", + "anastasis_gtk_ia_cf_it", + "anastasis_gtk_ia_cpr_dk", + "anastasis_gtk_ia_es_dni", + "anastasis_gtk_ia_es_ssn", + "anastasis_gtk_ia_full_name", + "anastasis_gtk_ia_my_jp", + "anastasis_gtk_ia_nid_al", + "anastasis_gtk_ia_nid_be", + "anastasis_gtk_ia_ssn_de", + "anastasis_gtk_ia_ssn_us", + "anastasis_gtk_ia_tax_de", + "anastasis_gtk_xx_prime", + "anastasis_gtk_xx_square", +] + +export const WithAllPosibleWidget = createExample(TestedComponent, { + ...reducerStatesExample.backupAttributeEditing, + required_attributes: allWidgets.map(w => ({ + name: w, + label: `widget: ${w}`, + type: 'type', + uuid: `uuid-${w}`, + widget: w + })) +} as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 2f804f940..3b39cf9c4 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -1,10 +1,11 @@ /* eslint-disable @typescript-eslint/camelcase */ +import { UserAttributeSpec, validators } from "anastasis-core"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { ReducerStateRecovery, ReducerStateBackup, UserAttributeSpec } from "anastasis-core/lib"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; -import { AnastasisClientFrame, withProcessLabel, LabeledInput } from "./index"; +import { AnastasisClientFrame, withProcessLabel } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; +import { DateInput } from "../../components/fields/DateInput"; export function AttributeEntryScreen(): VNode { const reducer = useAnastasisContext() @@ -18,48 +19,105 @@ export function AttributeEntryScreen(): VNode { if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) { return
    invalid state
    } - + + return ( reducer.transition("enter_user_attributes", { identity_attributes: attrs, })} > - {reducer.currentReducerState.required_attributes?.map((x, i: number) => { - return ( - setAttrs({ ...attrs, [x.name]: v })} - spec={x} - value={attrs[x.name]} /> - ); - })} +
    +
    + + {reducer.currentReducerState.required_attributes?.map((x, i: number) => { + const value = attrs[x.name] + function checkIfValid(): string | undefined { + const pattern = x['validation-regex'] + if (pattern) { + const re = new RegExp(pattern) + if (!re.test(value)) return 'The value is invalid' + } + const logic = x['validation-logic'] + if (logic) { + const func = (validators as any)[logic]; + if (func && typeof func === 'function' && !func(value)) return 'Please check the value' + } + const optional = x.optional + console.log('optiona', optional) + if (!optional && !value) { + return 'This value is required' + } + return undefined + } + + return ( + setAttrs({ ...attrs, [x.name]: v })} + spec={x} + isValid={checkIfValid} + value={value} /> + ); + })} + +
    +
    +

    This stay private

    +

    The information you have entered here: +

    +
      +
    • + + + + Will be hashed, and therefore unreadable +
    • +
    • + + The non-hashed version is not shared
    • +
    +
    +
    ); } -interface AttributeEntryProps { - reducer: AnastasisReducerApi; - reducerState: ReducerStateRecovery | ReducerStateBackup; -} - -export interface AttributeEntryFieldProps { +interface AttributeEntryFieldProps { isFirst: boolean; value: string; setValue: (newValue: string) => void; spec: UserAttributeSpec; + isValid: () => string | undefined; } -export function AttributeEntryField(props: AttributeEntryFieldProps): VNode { +function AttributeEntryField(props: AttributeEntryFieldProps): VNode { + const errorMessage = props.isValid() + return (
    - + {props.spec.type === 'date' ? + : + + } + + + + + This stay private +
    ); } diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx index 9567e0ef7..5243c5259 100644 --- a/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx @@ -6,7 +6,8 @@ import { import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { AnastasisClientFrame, LabeledInput } from "./index"; +import { AnastasisClientFrame } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode { const [email, setEmail] = useState(""); diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx index 55e37a968..1c2a9a92e 100644 --- a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx @@ -6,7 +6,7 @@ import { import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { LabeledInput } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode { const [fullName, setFullName] = useState(""); diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx index 7699cdf34..c2bd24ef9 100644 --- a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx @@ -6,7 +6,8 @@ import { import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { AnastasisClientFrame, LabeledInput } from "./index"; +import { AnastasisClientFrame } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode { const [questionText, setQuestionText] = useState(""); diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx index 44d3795b2..8f86831a9 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx @@ -24,8 +24,11 @@ import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationE export default { - title: 'Pages/AuthenticationEditorScreen', + title: 'Pages/backup/AuthenticationEditorScreen', component: TestedComponent, + args: { + order: 5, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index 65a2b7e13..0c9d007bc 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -26,8 +26,11 @@ import { BackupFinishedScreen as TestedComponent } from './BackupFinishedScreen' export default { - title: 'Pages/BackupFinishedScreen', + title: 'Pages/backup/FinishedScreen', component: TestedComponent, + args: { + order: 9, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index 4f186c031..def44c5a6 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -20,23 +20,27 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { ReducerState } from 'anastasis-core'; +import { RecoveryStates, ReducerState } from 'anastasis-core'; import { createExample, reducerStatesExample } from '../../utils'; import { ChallengeOverviewScreen as TestedComponent } from './ChallengeOverviewScreen'; export default { - title: 'Pages/ChallengeOverviewScreen', + title: 'Pages/recovery/ChallengeOverviewScreen', component: TestedComponent, + args: { + order: 5, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, }, }; -export const OneChallenge = createExample(TestedComponent, {...reducerStatesExample.challengeSelecting, +export const OneChallenge = createExample(TestedComponent, { + ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{uuid:'1'}]], + policies: [[{ uuid: '1' }]], challenges: [{ cost: 'USD:1', instructions: 'just go for it', @@ -46,37 +50,44 @@ export const OneChallenge = createExample(TestedComponent, {...reducerStatesExam }, } as ReducerState); -export const MoreChallenges = createExample(TestedComponent, {...reducerStatesExample.challengeSelecting, +export const MoreChallenges = createExample(TestedComponent, { + ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{uuid:'1'}, {uuid:'2'}],[{uuid:'3'}]], + policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]], challenges: [{ cost: 'USD:1', instructions: 'just go for it', type: 'question', uuid: '1', - },{ + }, { cost: 'USD:1', instructions: 'just go for it', type: 'question', uuid: '2', - },{ + }, { cost: 'USD:1', instructions: 'just go for it', type: 'question', - uuid: '3', + uuid: 'uuid-3', }] }, + challenge_feedback: { + 'uuid-3': { + state: 'solved' + } + } } as ReducerState); -export const OneBadConfiguredPolicy = createExample(TestedComponent, {...reducerStatesExample.challengeSelecting, +export const OneBadConfiguredPolicy = createExample(TestedComponent, { + ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{uuid:'2'}]], + policies: [[{ uuid: '2' }]], challenges: [{ cost: 'USD:1', instructions: 'just go for it', type: 'sasd', uuid: '1', - }] + }], }, } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx new file mode 100644 index 000000000..e5fe09e99 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.stories.tsx @@ -0,0 +1,38 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../utils'; +import { ChallengePayingScreen as TestedComponent } from './ChallengePayingScreen'; + + +export default { + title: 'Pages/recovery/__ChallengePayingScreen', + component: TestedComponent, + args: { + order: 10, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +export const Example = createExample(TestedComponent, reducerStatesExample.challengePaying); diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx new file mode 100644 index 000000000..d87afdf46 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx @@ -0,0 +1,33 @@ +import { h, VNode } from "preact"; +import { useAnastasisContext } from "../../context/anastasis"; +import { AnastasisClientFrame } from "./index"; + +export function ChallengePayingScreen(): VNode { + const reducer = useAnastasisContext() + if (!reducer) { + return
    no reducer in context
    + } + if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { + return
    invalid state
    + } + const payments = ['']; //reducer.currentReducerState.payments ?? + return ( + +

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

    +
      + {payments.map((x, i) => { + return
    • {x}
    • ; + })} +
    + +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx index aad37cd7f..8744a2b79 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx @@ -26,11 +26,14 @@ import { ContinentSelectionScreen as TestedComponent } from './ContinentSelectio export default { title: 'Pages/ContinentSelectionScreen', component: TestedComponent, + args: { + order: 2, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, }, }; -export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectCountry); -export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectCountry); +export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent); +export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent); diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx index ad529a4a7..94c0409da 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx @@ -7,11 +7,11 @@ export function ContinentSelectionScreen(): VNode { if (!reducer || !reducer.currentReducerState || !("continents" in reducer.currentReducerState)) { return
    } - const sel = (x: string): void => reducer.transition("select_continent", { continent: x }); + const select = (continent: string) => (): void => reducer.transition("select_continent", { continent }); return ( {reducer.currentReducerState.continents.map((x: any) => ( - ))} diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx index adf36980f..3a642748a 100644 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx @@ -26,6 +26,9 @@ import { CountrySelectionScreen as TestedComponent } from './CountrySelectionScr export default { title: 'Pages/CountrySelectionScreen', component: TestedComponent, + args: { + order: 3, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx index 555622c1d..417c08633 100644 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx @@ -17,11 +17,15 @@ export function CountrySelectionScreen(): VNode { }); return ( - {reducer.currentReducerState.countries.map((x: any) => ( - - ))} +
    + {reducer.currentReducerState.countries.map((x: any) => ( +
    + +
    + ))} +
    ); } diff --git a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx index 1a9462b88..e952ab28d 100644 --- a/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/PoliciesPayingScreen.stories.tsx @@ -26,8 +26,11 @@ import { PoliciesPayingScreen as TestedComponent } from './PoliciesPayingScreen' export default { - title: 'Pages/PoliciesPayingScreen', + title: 'Pages/backup/PoliciesPayingScreen', component: TestedComponent, + args: { + order: 8, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx index 0c1842420..b5933db17 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx @@ -26,7 +26,10 @@ import { RecoveryFinishedScreen as TestedComponent } from './RecoveryFinishedScr export default { - title: 'Pages/RecoveryFinishedScreen', + title: 'Pages/recovery/FinishedScreen', + args: { + order: 7, + }, component: TestedComponent, argTypes: { onUpdate: { action: 'onUpdate' }, diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx index b52699e7b..91855b023 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx @@ -26,7 +26,10 @@ import { ReviewPoliciesScreen as TestedComponent } from './ReviewPoliciesScreen' export default { - title: 'Pages/ReviewPoliciesScreen', + title: 'Pages/backup/ReviewPoliciesScreen', + args: { + order: 6, + }, component: TestedComponent, argTypes: { onUpdate: { action: 'onUpdate' }, diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx index 18560356a..49dd8fca8 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.stories.tsx @@ -26,8 +26,11 @@ import { SecretEditorScreen as TestedComponent } from './SecretEditorScreen'; export default { - title: 'Pages/SecretEditorScreen', + title: 'Pages/backup/SecretEditorScreen', component: TestedComponent, + args: { + order: 7, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx index a5235d66c..f5fd7c0d1 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx @@ -4,9 +4,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; import { - AnastasisClientFrame, - LabeledInput -} from "./index"; + AnastasisClientFrame} from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; export function SecretEditorScreen(): VNode { const reducer = useAnastasisContext() diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx index e9c597023..f56939090 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.stories.tsx @@ -26,8 +26,11 @@ import { SecretSelectionScreen as TestedComponent } from './SecretSelectionScree export default { - title: 'Pages/SecretSelectionScreen', + title: 'Pages/recovery/SecretSelectionScreen', component: TestedComponent, + args: { + order: 4, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx index 2c27895c2..0d70405e5 100644 --- a/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx @@ -1,7 +1,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame, LabeledInput } from "./index"; +import { AnastasisClientFrame } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; import { SolveEntryProps } from "./SolveScreen"; export function SolveEmailEntry({ challenge, feedback }: SolveEntryProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx b/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx index 1a824acb8..22b8d470b 100644 --- a/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx +++ b/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx @@ -1,7 +1,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame, LabeledInput } from "./index"; +import { AnastasisClientFrame } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; import { SolveEntryProps } from "./SolveScreen"; export function SolvePostEntry({ challenge, feedback }: SolveEntryProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx index 72dadbe89..319289381 100644 --- a/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx @@ -1,7 +1,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame, LabeledInput } from "./index"; +import { AnastasisClientFrame } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; import { SolveEntryProps } from "./SolveScreen"; export function SolveQuestionEntry({ challenge, feedback }: SolveEntryProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx index 69af9be42..c05c36b07 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx @@ -26,8 +26,11 @@ import { SolveScreen as TestedComponent } from './SolveScreen'; export default { - title: 'Pages/SolveScreen', + title: 'Pages/recovery/SolveScreen', component: TestedComponent, + args: { + order: 6, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx index 163e0d1f3..c4cf3a680 100644 --- a/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx @@ -1,7 +1,8 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame, LabeledInput } from "./index"; +import { AnastasisClientFrame } from "./index"; +import { LabeledInput } from "../../components/fields/LabeledInput"; import { SolveEntryProps } from "./SolveScreen"; export function SolveSmsEntry({ challenge, feedback }: SolveEntryProps): VNode { diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx index ad84cd8f2..657a2dd74 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.stories.tsx @@ -26,6 +26,9 @@ import { StartScreen as TestedComponent } from './StartScreen'; export default { title: 'Pages/StartScreen', component: TestedComponent, + args: { + order: 1, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/StartScreen.tsx b/packages/anastasis-webui/src/pages/home/StartScreen.tsx index 6625ec5b8..c751ad9e4 100644 --- a/packages/anastasis-webui/src/pages/home/StartScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/StartScreen.tsx @@ -16,12 +16,21 @@ export function StartScreen(): VNode {
    -
    +
    - + + +
    diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx index e2f3d521e..7568ccd69 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.stories.tsx @@ -25,8 +25,11 @@ import { TruthsPayingScreen as TestedComponent } from './TruthsPayingScreen'; export default { - title: 'Pages/TruthsPayingScreen', + title: 'Pages/backup/__TruthsPayingScreen', component: TestedComponent, + args: { + order: 10, + }, argTypes: { onUpdate: { action: 'onUpdate' }, onBack: { action: 'onBack' }, diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx index 319f590a0..098a8ba8d 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx @@ -14,7 +14,7 @@ export function TruthsPayingScreen(): VNode { return (

    Some of the providers require a payment to store the encrypted diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx index 4cec47ec8..5cef4ee9c 100644 --- a/packages/anastasis-webui/src/pages/home/index.tsx +++ b/packages/anastasis-webui/src/pages/home/index.tsx @@ -11,10 +11,7 @@ import { VNode } from "preact"; import { - useErrorBoundary, - useLayoutEffect, - useRef -} from "preact/hooks"; + useErrorBoundary} from "preact/hooks"; import { Menu } from "../../components/menu"; import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis"; import { @@ -25,6 +22,7 @@ import { AttributeEntryScreen } from "./AttributeEntryScreen"; import { AuthenticationEditorScreen } from "./AuthenticationEditorScreen"; import { BackupFinishedScreen } from "./BackupFinishedScreen"; import { ChallengeOverviewScreen } from "./ChallengeOverviewScreen"; +import { ChallengePayingScreen } from "./ChallengePayingScreen"; import { ContinentSelectionScreen } from "./ContinentSelectionScreen"; import { CountrySelectionScreen } from "./CountrySelectionScreen"; import { PoliciesPayingScreen } from "./PoliciesPayingScreen"; @@ -118,9 +116,9 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { {props.children} {!props.hideNav ? ( -

    - - {!props.hideNext ? : null} +
    + + {!props.hideNext ? : null}
    ) : null}
    @@ -222,7 +220,9 @@ const AnastasisClientImpl: FunctionalComponent = () => { ); } - + if (state.recovery_state === RecoveryStates.ChallengePaying) { + return ; + } console.log("unknown state", reducer.currentReducerState); return ( @@ -234,32 +234,6 @@ const AnastasisClientImpl: FunctionalComponent = () => { ); }; -interface LabeledInputProps { - label: string; - grabFocus?: boolean; - bind: [string, (x: string) => void]; -} - -export function LabeledInput(props: LabeledInputProps): VNode { - const inputRef = useRef(null); - useLayoutEffect(() => { - if (props.grabFocus) { - inputRef.current?.focus(); - } - }, [props.grabFocus]); - return ( - - ); -} - /** * Show a dismissible error banner if there is a current error. */ diff --git a/packages/anastasis-webui/src/scss/_custom-calendar.scss b/packages/anastasis-webui/src/scss/_custom-calendar.scss index 9ac877ce0..bff68cf79 100644 --- a/packages/anastasis-webui/src/scss/_custom-calendar.scss +++ b/packages/anastasis-webui/src/scss/_custom-calendar.scss @@ -41,6 +41,10 @@ } +.home .datePicker div { + margin-top: 0px; + margin-bottom: 0px; +} .datePicker { text-align: left; background: var(--primary-card-color); diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx index d1d861469..670e229cd 100644 --- a/packages/anastasis-webui/src/utils/index.tsx +++ b/packages/anastasis-webui/src/utils/index.tsx @@ -115,15 +115,9 @@ export const reducerStatesExample = { recoverySelectCountry: {...base, recovery_state: RecoveryStates.CountrySelecting } as ReducerState, - backupSelectCountry: {...base, - backup_state: BackupStates.CountrySelecting - } as ReducerState, recoverySelectContinent: {...base, recovery_state: RecoveryStates.ContinentSelecting, } as ReducerState, - backupSelectContinent: {...base, - backup_state: BackupStates.ContinentSelecting, - } as ReducerState, secretSelection: {...base, recovery_state: RecoveryStates.SecretSelecting, } as ReducerState, @@ -136,6 +130,18 @@ export const reducerStatesExample = { challengeSolving: {...base, recovery_state: RecoveryStates.ChallengeSolving, } as ReducerState, + challengePaying: {...base, + recovery_state: RecoveryStates.ChallengePaying, + } as ReducerState, + recoveryAttributeEditing: {...base, + recovery_state: RecoveryStates.UserAttributesCollecting + } as ReducerState, + backupSelectCountry: {...base, + backup_state: BackupStates.CountrySelecting + } as ReducerState, + backupSelectContinent: {...base, + backup_state: BackupStates.ContinentSelecting, + } as ReducerState, secretEdition: {...base, backup_state: BackupStates.SecretEditing, } as ReducerState, @@ -151,7 +157,7 @@ export const reducerStatesExample = { authEditing: {...base, backup_state: BackupStates.AuthenticationsEditing } as ReducerState, - attributeEditing: {...base, + backupAttributeEditing: {...base, backup_state: BackupStates.UserAttributesCollecting } as ReducerState, truthsPaying: {...base, -- cgit v1.2.3 From 32318a80f48bf52ca7823a0c055164f43bdaf1d6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 27 Oct 2021 15:13:35 -0300 Subject: working version with improved ui --- .../src/components/fields/DateInput.tsx | 4 +- .../src/components/fields/LabeledInput.tsx | 40 ------ .../src/components/fields/NumberInput.tsx | 41 +++++++ .../src/components/fields/TextInput.tsx | 40 ++++++ .../src/components/menu/SideBar.tsx | 43 +++---- .../src/components/picker/DatePicker.tsx | 10 +- .../pages/home/AttributeEntryScreen.stories.tsx | 28 +++-- .../src/pages/home/AttributeEntryScreen.tsx | 25 +++- .../src/pages/home/AuthMethodEmailSetup.tsx | 4 +- .../src/pages/home/AuthMethodPostSetup.tsx | 12 +- .../src/pages/home/AuthMethodQuestionSetup.tsx | 6 +- .../pages/home/ChallengeOverviewScreen.stories.tsx | 134 ++++++++++++++++++++- .../src/pages/home/ChallengeOverviewScreen.tsx | 118 +++++++++++------- .../home/ContinentSelectionScreen.stories.tsx | 1 + .../pages/home/RecoveryFinishedScreen.stories.tsx | 2 +- .../src/pages/home/SecretEditorScreen.tsx | 6 +- .../src/pages/home/SecretSelectionScreen.tsx | 116 ++++++++++++------ .../src/pages/home/SolveEmailEntry.tsx | 26 ---- .../src/pages/home/SolvePostEntry.tsx | 24 ---- .../src/pages/home/SolveQuestionEntry.tsx | 24 ---- .../anastasis-webui/src/pages/home/SolveScreen.tsx | 121 ++++++++++++++++--- .../src/pages/home/SolveSmsEntry.tsx | 26 ---- .../src/pages/home/SolveUnsupportedEntry.tsx | 12 -- 23 files changed, 552 insertions(+), 311 deletions(-) delete mode 100644 packages/anastasis-webui/src/components/fields/LabeledInput.tsx create mode 100644 packages/anastasis-webui/src/components/fields/NumberInput.tsx create mode 100644 packages/anastasis-webui/src/components/fields/TextInput.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/SolveUnsupportedEntry.tsx (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index c45acc6d2..e1c354f7b 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -8,6 +8,7 @@ export interface DateInputProps { grabFocus?: boolean; tooltip?: string; error?: string; + years?: Array; bind: [string, (x: string) => void]; } @@ -19,7 +20,7 @@ export function DateInput(props: DateInputProps): VNode { } }, [props.grabFocus]); const [opened, setOpened2] = useState(false) - function setOpened(v: boolean) { + function setOpened(v: boolean): void { console.log('dale', v) setOpened2(v) } @@ -50,6 +51,7 @@ export function DateInput(props: DateInputProps): VNode { {showError &&

    {props.error}

    } setOpened(false)} dateReceiver={(d) => { setDirty(true) diff --git a/packages/anastasis-webui/src/components/fields/LabeledInput.tsx b/packages/anastasis-webui/src/components/fields/LabeledInput.tsx deleted file mode 100644 index 96d634a4f..000000000 --- a/packages/anastasis-webui/src/components/fields/LabeledInput.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { h, VNode } from "preact"; -import { useLayoutEffect, useRef, useState } from "preact/hooks"; - -export interface LabeledInputProps { - label: string; - grabFocus?: boolean; - error?: string; - tooltip?: string; - bind: [string, (x: string) => void]; -} - -export function LabeledInput(props: LabeledInputProps): VNode { - const inputRef = useRef(null); - useLayoutEffect(() => { - if (props.grabFocus) { - inputRef.current?.focus(); - } - }, [props.grabFocus]); - const value = props.bind[0]; - const [dirty, setDirty] = useState(false) - const showError = dirty && props.error - return (
    - -
    - {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} - ref={inputRef} - style={{ display: "block" }} /> -
    - {showError &&

    {props.error}

    } -
    - ); -} diff --git a/packages/anastasis-webui/src/components/fields/NumberInput.tsx b/packages/anastasis-webui/src/components/fields/NumberInput.tsx new file mode 100644 index 000000000..af9bbe66b --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/NumberInput.tsx @@ -0,0 +1,41 @@ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; + +export interface TextInputProps { + label: string; + grabFocus?: boolean; + error?: string; + tooltip?: string; + bind: [string, (x: string) => void]; +} + +export function NumberInput(props: TextInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + const value = props.bind[0]; + const [dirty, setDirty] = useState(false) + const showError = dirty && props.error + return (
    + +
    + {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + ref={inputRef} + style={{ display: "block" }} /> +
    + {showError &&

    {props.error}

    } +
    + ); +} diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx new file mode 100644 index 000000000..fa6fd9792 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx @@ -0,0 +1,40 @@ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; + +export interface TextInputProps { + label: string; + grabFocus?: boolean; + error?: string; + tooltip?: string; + bind: [string, (x: string) => void]; +} + +export function TextInput(props: TextInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + const value = props.bind[0]; + const [dirty, setDirty] = useState(false) + const showError = dirty && props.error + return (
    + +
    + {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + ref={inputRef} + style={{ display: "block" }} /> +
    + {showError &&

    {props.error}

    } +
    + ); +} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index 12223d473..87e771009 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -64,9 +64,8 @@ export function Sidebar({ mobile }: Props): VNode {
  • } {reducer.currentReducerState && reducer.currentReducerState.backup_state ? -
  • +
  • Location & Currency
    @@ -79,73 +78,65 @@ export function Sidebar({ mobile }: Props): VNode {
  • - Auth methods + Authorization methods
  • - PoliciesReviewing + Policies reviewing
  • - SecretEditing + Secret input
  • - PoliciesPaying + Payment (optional)
  • - BackupFinished + Backup completed
  • - TruthsPaying + Truth Paying
  • : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state && -
  • +
  • - ContinentSelecting -
    -
  • -
  • -
    - CountrySelecting + Location & Currency
  • - UserAttributesCollecting + Personal information
  • - SecretSelecting -
    -
  • -
  • -
    - ChallengeSelecting + Secret selection
  • -
  • +
  • - ChallengeSolving + Solve Challenges
  • - RecoveryFinished + Secret recovered
  • )} diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx index e51b3db68..5b33fa8be 100644 --- a/packages/anastasis-webui/src/components/picker/DatePicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx @@ -24,6 +24,7 @@ import { h, Component } from "preact"; interface Props { closeFunction?: () => void; dateReceiver?: (d: Date) => void; + years?: Array; opened?: boolean; } interface State { @@ -207,9 +208,9 @@ export class DatePicker extends Component { } componentDidUpdate() { - if (this.state.selectYearMode) { - document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it - } + // if (this.state.selectYearMode) { + // document.getElementsByClassName('selected')[0].scrollIntoView(); // works in every browser incl. IE, replace with scrollIntoViewIfNeeded when browsers support it + // } } constructor() { @@ -296,8 +297,7 @@ export class DatePicker extends Component { } {selectYearMode &&
    - - {yearArr.map(year => ( + {(this.props.years || yearArr).map(year => ( {year} diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx index d9be48fb4..32d7817e3 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.stories.tsx @@ -40,21 +40,21 @@ export default { export const Backup = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, required_attributes: [{ - name: 'first', + name: 'first name', label: 'first', - type: 'type', + type: 'string', uuid: 'asdasdsa1', widget: 'wid', }, { - name: 'pepe', + name: 'last name', label: 'second', - type: 'type', + type: 'string', uuid: 'asdasdsa2', widget: 'wid', }, { - name: 'pepe2', + name: 'date', label: 'third', - type: 'type', + type: 'date', uuid: 'asdasdsa3', widget: 'calendar', }] @@ -65,19 +65,19 @@ export const Recovery = createExample(TestedComponent, { required_attributes: [{ name: 'first', label: 'first', - type: 'type', + type: 'string', uuid: 'asdasdsa1', widget: 'wid', }, { name: 'pepe', label: 'second', - type: 'type', + type: 'string', uuid: 'asdasdsa2', widget: 'wid', }, { name: 'pepe2', label: 'third', - type: 'type', + type: 'date', uuid: 'asdasdsa3', widget: 'calendar', }] @@ -110,12 +110,20 @@ const allWidgets = [ "anastasis_gtk_xx_square", ] +function typeForWidget(name: string): string { + if (["anastasis_gtk_xx_prime", + "anastasis_gtk_xx_square", + ].includes(name)) return "number"; + if (["anastasis_gtk_ia_birthdate"].includes(name)) return "date" + return "string"; +} + export const WithAllPosibleWidget = createExample(TestedComponent, { ...reducerStatesExample.backupAttributeEditing, required_attributes: allWidgets.map(w => ({ name: w, label: `widget: ${w}`, - type: 'type', + type: typeForWidget(w), uuid: `uuid-${w}`, widget: w })) diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 3b39cf9c4..f74dcefba 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -4,8 +4,9 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame, withProcessLabel } from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; +import { TextInput } from "../../components/fields/TextInput"; import { DateInput } from "../../components/fields/DateInput"; +import { NumberInput } from "../../components/fields/NumberInput"; export function AttributeEntryScreen(): VNode { const reducer = useAnastasisContext() @@ -65,6 +66,7 @@ export function AttributeEntryScreen(): VNode {
    +

    This personal information will help to locate your secret in the first place

    This stay private

    The information you have entered here:

    @@ -92,20 +94,33 @@ interface AttributeEntryFieldProps { spec: UserAttributeSpec; isValid: () => string | undefined; } - +const possibleBirthdayYear: Array = [] +for (let i = 0; i < 100; i++ ) { + possibleBirthdayYear.push(2020 - i) +} function AttributeEntryField(props: AttributeEntryFieldProps): VNode { const errorMessage = props.isValid() return (
    - {props.spec.type === 'date' ? + {props.spec.type === 'date' && } + {props.spec.type === 'number' && + : - + } + {props.spec.type === 'string' && +
    - diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx index 1c2a9a92e..c4ddeff91 100644 --- a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx @@ -6,7 +6,7 @@ import { import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { LabeledInput } from "../../components/fields/LabeledInput"; +import { TextInput } from "../../components/fields/TextInput"; export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode { const [fullName, setFullName] = useState(""); @@ -42,22 +42,22 @@ export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode { code that you will receive in a letter to that address.

    -
    - +
    - +
    - +
    - +
    diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx index c2bd24ef9..f1bab94ab 100644 --- a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx @@ -7,7 +7,7 @@ import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; import { AnastasisClientFrame } from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; +import { TextInput } from "../../components/fields/TextInput"; export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode { const [questionText, setQuestionText] = useState(""); @@ -29,13 +29,13 @@ export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode { here.

    -
    - +
    diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index def44c5a6..758963574 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -37,7 +37,7 @@ export default { }, }; -export const OneChallenge = createExample(TestedComponent, { +export const OneUnsolvedPolicy = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { policies: [[{ uuid: '1' }]], @@ -50,7 +50,7 @@ export const OneChallenge = createExample(TestedComponent, { }, } as ReducerState); -export const MoreChallenges = createExample(TestedComponent, { +export const SomePoliciesOneSolved = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]], @@ -75,13 +75,13 @@ export const MoreChallenges = createExample(TestedComponent, { 'uuid-3': { state: 'solved' } - } + }, } as ReducerState); export const OneBadConfiguredPolicy = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '2' }]], + policies: [[{ uuid: '1' }, { uuid: '2' }]], challenges: [{ cost: 'USD:1', instructions: 'just go for it', @@ -91,4 +91,130 @@ export const OneBadConfiguredPolicy = createExample(TestedComponent, { }, } as ReducerState); +export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, { + ...reducerStatesExample.challengeSelecting, + recovery_information: { + policies: [[ + { uuid: '1' }, + { uuid: '2' }, + { uuid: '3' }, + { uuid: '4' }, + { uuid: '5' }, + { uuid: '6' }, + ]], + challenges: [{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '1', + },{ + cost: 'USD:1', + instructions: 'enter a text received by a sms', + type: 'sms', + uuid: '2', + },{ + cost: 'USD:1', + instructions: 'enter a text received by a email', + type: 'email', + uuid: '3', + },{ + cost: 'USD:1', + instructions: 'enter a code based on a time-based one-time password', + type: 'totp', + uuid: '4', + },{ + cost: 'USD:1', + instructions: 'send a wire transfer to an account', + type: 'iban', + uuid: '5', + },{ + cost: 'USD:1', + instructions: 'just go for it', + type: 'new-type-of-challenge', + uuid: '6', + }], + }, +} as ReducerState); + + +export const OnePolicyWithAllTheChallengesInDifferentState = createExample(TestedComponent, { + ...reducerStatesExample.challengeSelecting, + recovery_information: { + policies: [[ + { uuid: '1' }, + { uuid: '2' }, + { uuid: '3' }, + { uuid: '4' }, + { uuid: '5' }, + { uuid: '6' }, + { uuid: '7' }, + { uuid: '8' }, + { uuid: '9' }, + { uuid: '10' }, + ]], + challenges: [{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '1', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '2', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '3', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '4', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '5', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '6', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '7', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '8', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '9', + },{ + cost: 'USD:1', + instructions: 'answer the a question correctly', + type: 'question', + uuid: '10', + }], + }, + challenge_feedback: { + 1: { state: 'solved' }, + 2: { state: 'hint' }, + 3: { state: 'details' }, + 4: { state: 'body' }, + 5: { state: 'redirect' }, + 6: { state: 'server-failure' }, + 7: { state: 'truth-unknown' }, + 8: { state: 'rate-limit-exceeded' }, + 9: { state: 'authentication-timeout' }, + 10: { state: 'external-instructions' }, + } +} as ReducerState); export const NoPolicies = createExample(TestedComponent, reducerStatesExample.challengeSelecting); diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index c9b52e91b..3bb3fb837 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -1,3 +1,4 @@ +import { ChallengeFeedback } from "anastasis-core"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; @@ -13,65 +14,94 @@ export function ChallengeOverviewScreen(): VNode { } const policies = reducer.currentReducerState.recovery_information?.policies ?? []; - const chArr = reducer.currentReducerState.recovery_information?.challenges ?? []; - const challengeFeedback = reducer.currentReducerState?.challenge_feedback; + const knownChallengesArray = reducer.currentReducerState.recovery_information?.challenges ?? []; + const challengeFeedback = reducer.currentReducerState?.challenge_feedback ?? {}; - const challenges: { + const knownChallengesMap: { [uuid: string]: { type: string; instructions: string; cost: string; + feedback: ChallengeFeedback | undefined; }; } = {}; - for (const ch of chArr) { - challenges[ch.uuid] = { + for (const ch of knownChallengesArray) { + knownChallengesMap[ch.uuid] = { type: ch.type, cost: ch.cost, instructions: ch.instructions, + feedback: challengeFeedback[ch.uuid] }; } + const policiesWithInfo = policies.map(row => { + let isPolicySolved = true + const challenges = row.map(({ uuid }) => { + const info = knownChallengesMap[uuid]; + const isChallengeSolved = info?.feedback?.state === 'solved' + isPolicySolved = isPolicySolved && isChallengeSolved + return { info, uuid, isChallengeSolved } + }).filter(ch => ch.info !== undefined) + + return { isPolicySolved, challenges } + }) + + const atLeastThereIsOnePolicySolved = policiesWithInfo.find(p => p.isPolicySolved) !== undefined + return ( - -

    Policies

    - {!policies.length &&

    - No policies found -

    } - {policies.map((row, i) => { + + {!policies.length ?

    + No policies found, try with another version of the secret +

    : (policies.length === 1 ?

    + One policy found for this secret. You need to solve all the challenges in order to recover your secret. +

    :

    + We have found {policies.length} polices. You need to solve all the challenges from one policy in order + to recover your secret. +

    )} + {policiesWithInfo.map((row, i) => { + const tableBody = row.challenges.map(({ info, uuid }) => { + return ( + + {info.type} + + {info.instructions} + + {info.feedback?.state ?? "unknown"} + {info.cost} + + {info.feedback?.state !== "solved" ? ( + reducer.transition("select_challenge", { uuid })}> + Solve + + ) : null} + + + ); + }) return (
    -

    Policy #{i + 1}

    - {row.map(column => { - const ch = challenges[column.uuid]; - if (!ch) return
    - There is no challenge for this policy -
    - const feedback = challengeFeedback?.[column.uuid]; - return ( -
    -

    - {ch.type} ({ch.instructions}) -

    -

    Status: {feedback?.state ?? "unknown"}

    - {feedback?.state !== "solved" ? ( - - ) : null} -
    - ); - })} + Policy #{i + 1} + {row.challenges.length === 0 &&

    + This policy doesn't have challenges +

    } + {row.challenges.length === 1 &&

    + This policy just have one challenge to be solved +

    } + {row.challenges.length > 1 &&

    + This policy have {row.challenges.length} challenges +

    } + + + + + + + + + + + {tableBody} + +
    Challenge typeDescriptionStatusCost
    ); })} diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx index 8744a2b79..2186eb42d 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.stories.tsx @@ -36,4 +36,5 @@ export default { }; export const Backup = createExample(TestedComponent, reducerStatesExample.backupSelectContinent); + export const Recovery = createExample(TestedComponent, reducerStatesExample.recoverySelectContinent); diff --git a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx index b5933db17..0d2ebb778 100644 --- a/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/RecoveryFinishedScreen.stories.tsx @@ -37,7 +37,7 @@ export default { }, }; -export const NormalEnding = createExample(TestedComponent, { +export const GoodEnding = createExample(TestedComponent, { ...reducerStatesExample.recoveryFinished, core_secret: { mime: 'text/plain', value: 'hello' } } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx index f5fd7c0d1..79a46761c 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx @@ -5,7 +5,7 @@ import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame} from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; +import { TextInput } from "../../components/fields/TextInput"; export function SecretEditorScreen(): VNode { const reducer = useAnastasisContext() @@ -47,14 +47,14 @@ export function SecretEditorScreen(): VNode { onNext={() => secretNext()} >
    -
    - diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 903f57868..5d67ee472 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -29,15 +29,31 @@ export function SecretSelectionScreen(): VNode { version: n, provider_url: p, }); - setSelectingVersion(false); }); + setSelectingVersion(false); } + const providerList = Object.keys(reducer.currentReducerState.authentication_providers ?? {}) const recoveryDocument = reducer.currentReducerState.recovery_document if (!recoveryDocument) { return ( - -

    No recovery document found

    + +

    No recovery document found, try with another provider

    + + + + + +
    Provider + +
    ) } @@ -45,43 +61,75 @@ export function SecretSelectionScreen(): VNode { return (

    Select a different version of the secret

    - -
    - setOtherVersion(Number((e.target as HTMLInputElement).value))} - type="number" /> - -
    -
    - -
    -
    - + + + + + + + + + + +
    Provider + +
    Version + setOtherVersion(Number((e.target as HTMLInputElement).value))} + type="number" /> + + setOtherVersion(0)}>set to latest version +
    +
    + +
    + ); } return ( -

    Provider: {recoveryDocument.provider_url}

    -

    Secret version: {recoveryDocument.version}

    -

    Secret name: {recoveryDocument.secret_name}

    - +

    Secret found, you can select another version or continue to the challenges solving

    + + + + + + + + + + + + + + + + +
    + Provider + + + + {recoveryDocument.provider_url} setSelectingVersion(true)}>use another provider
    + Secret version + + + + {recoveryDocument.version} setSelectingVersion(true)}>use another version
    + Secret name + + + + {recoveryDocument.secret_name}
    ); } diff --git a/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx deleted file mode 100644 index 0d70405e5..000000000 --- a/packages/anastasis-webui/src/pages/home/SolveEmailEntry.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame } from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; -import { SolveEntryProps } from "./SolveScreen"; - -export function SolveEmailEntry({ challenge, feedback }: SolveEntryProps): VNode { - const [answer, setAnswer] = useState(""); - const reducer = useAnastasisContext() - const next = (): void => { - if (reducer) reducer.transition("solve_challenge", { - answer, - }) - }; - return ( - next()} - > -

    Feedback: {JSON.stringify(feedback)}

    -

    {challenge.instructions}

    - -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx b/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx deleted file mode 100644 index 22b8d470b..000000000 --- a/packages/anastasis-webui/src/pages/home/SolvePostEntry.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame } from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; -import { SolveEntryProps } from "./SolveScreen"; - -export function SolvePostEntry({ challenge, feedback }: SolveEntryProps): VNode { - const [answer, setAnswer] = useState(""); - const reducer = useAnastasisContext() - const next = (): void => { - if (reducer) reducer.transition("solve_challenge", { answer }) - }; - return ( - next()} - > -

    Feedback: {JSON.stringify(feedback)}

    -

    {challenge.instructions}

    - -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx deleted file mode 100644 index 319289381..000000000 --- a/packages/anastasis-webui/src/pages/home/SolveQuestionEntry.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame } from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; -import { SolveEntryProps } from "./SolveScreen"; - -export function SolveQuestionEntry({ challenge, feedback }: SolveEntryProps): VNode { - const [answer, setAnswer] = useState(""); - const reducer = useAnastasisContext() - const next = (): void => { - if (reducer) reducer.transition("solve_challenge", { answer }) - }; - return ( - next()} - > -

    Feedback: {JSON.stringify(feedback)}

    -

    Question: {challenge.instructions}

    - -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 05ae50b48..077726e02 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -1,28 +1,36 @@ -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AnastasisClientFrame } from "."; import { ChallengeFeedback, ChallengeInfo } from "../../../../anastasis-core/lib"; +import { TextInput } from "../../components/fields/TextInput"; import { useAnastasisContext } from "../../context/anastasis"; -import { SolveEmailEntry } from "./SolveEmailEntry"; -import { SolvePostEntry } from "./SolvePostEntry"; -import { SolveQuestionEntry } from "./SolveQuestionEntry"; -import { SolveSmsEntry } from "./SolveSmsEntry"; -import { SolveUnsupportedEntry } from "./SolveUnsupportedEntry"; export function SolveScreen(): VNode { const reducer = useAnastasisContext() - + const [answer, setAnswer] = useState(""); + if (!reducer) { - return
    no reducer in context
    + return +
    no reducer in context
    +
    } if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return
    invalid state
    + return +
    invalid state
    +
    } if (!reducer.currentReducerState.recovery_information) { - return
    no recovery information found
    + return +
    no recovery information found
    +
    } if (!reducer.currentReducerState.selected_challenge_uuid) { - return
    no selected uuid
    + return +
    no selected uuid
    +
    } + const chArr = reducer.currentReducerState.recovery_information.challenges; const challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {}; const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; @@ -39,16 +47,99 @@ export function SolveScreen(): VNode { email: SolveEmailEntry, post: SolvePostEntry, }; - const SolveDialog = dialogMap[selectedChallenge?.type] ?? SolveUnsupportedEntry; + const SolveDialog = selectedChallenge === undefined ? SolveUndefinedEntry : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry; + + function onNext(): void { + reducer?.transition("solve_challenge", { answer }) + } + function onCancel(): void { + reducer?.back() + } + + return ( - + + + +
    + + +
    +
    ); } export interface SolveEntryProps { + id: string; challenge: ChallengeInfo; feedback?: ChallengeFeedback; + answer: string; + setAnswer: (s:string) => void; } +function SolveSmsEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { + return ( +

    An sms has been sent to "{challenge.instructions}". Type the code below

    + +
    + ); +} +function SolveQuestionEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { + return ( + +

    Type the answer to the following question:

    +
    +        {challenge.instructions}
    +      
    + +
    + ); +} + +function SolvePostEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { + return ( + +

    instruction for post type challenge "{challenge.instructions}"

    + +
    + ); +} + +function SolveEmailEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { + return ( + +

    An email has been sent to "{challenge.instructions}". Type the code below

    + +
    + ); +} + +function SolveUnsupportedEntry(props: SolveEntryProps): VNode { + return ( + +

    + The challenge selected is not supported for this UI. Please update this version or try using another policy. +

    +

    + Challenge type: {props.challenge.type} +

    +
    + ); +} +function SolveUndefinedEntry(props: SolveEntryProps): VNode { + return ( + +

    + There is no challenge information for id "{props.id}". Try resetting the recovery session. +

    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx deleted file mode 100644 index c4cf3a680..000000000 --- a/packages/anastasis-webui/src/pages/home/SolveSmsEntry.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisClientFrame } from "./index"; -import { LabeledInput } from "../../components/fields/LabeledInput"; -import { SolveEntryProps } from "./SolveScreen"; - -export function SolveSmsEntry({ challenge, feedback }: SolveEntryProps): VNode { - const [answer, setAnswer] = useState(""); - const reducer = useAnastasisContext() - const next = (): void => { - if (reducer) reducer.transition("solve_challenge", { - answer, - }) - }; - return ( - next()} - > -

    Feedback: {JSON.stringify(feedback)}

    -

    {challenge.instructions}

    - -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/SolveUnsupportedEntry.tsx b/packages/anastasis-webui/src/pages/home/SolveUnsupportedEntry.tsx deleted file mode 100644 index 7f538d249..000000000 --- a/packages/anastasis-webui/src/pages/home/SolveUnsupportedEntry.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { h, VNode } from "preact"; -import { AnastasisClientFrame } from "./index"; -import { SolveEntryProps } from "./SolveScreen"; - -export function SolveUnsupportedEntry(props: SolveEntryProps): VNode { - return ( - -

    {JSON.stringify(props.challenge)}

    -

    Challenge not supported.

    -
    - ); -} -- cgit v1.2.3 From 88d142d2098ad87613222e9a0c6df478a78f6528 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 1 Nov 2021 16:10:49 -0300 Subject: more styling added placeholders for inputs import declaration for png next button now has tooltip providing info about whats missing a lot more of examples for UI testing added qr dependency for totp rendering added email and field input types added all auth method setup screens added modal when there is not auth provider merge continent and country into location section others improvements as well... --- packages/anastasis-webui/src/assets/empty.png | Bin 0 -> 2785 bytes .../anastasis-webui/src/assets/example/id1.jpg | Bin 0 -> 103558 bytes .../src/assets/icons/auth_method/email.svg | 1 + .../src/assets/icons/auth_method/postal.svg | 1 + .../src/assets/icons/auth_method/question.svg | 1 + .../src/assets/icons/auth_method/sms.svg | 1 + .../src/assets/icons/auth_method/video.svg | 1 + packages/anastasis-webui/src/components/QR.tsx | 35 ++++ .../src/components/fields/DateInput.tsx | 5 +- .../src/components/fields/EmailInput.tsx | 44 +++++ .../src/components/fields/FileInput.tsx | 81 ++++++++ .../src/components/fields/ImageInput.tsx | 81 ++++++++ .../src/components/fields/NumberInput.tsx | 2 + .../src/components/fields/TextInput.tsx | 2 + .../src/components/menu/SideBar.tsx | 5 +- packages/anastasis-webui/src/declaration.d.ts | 4 + .../src/pages/home/AttributeEntryScreen.tsx | 84 +++++---- .../src/pages/home/AuthMethodEmailSetup.tsx | 43 ----- .../src/pages/home/AuthMethodPostSetup.tsx | 69 ------- .../src/pages/home/AuthMethodQuestionSetup.tsx | 47 ----- .../src/pages/home/AuthMethodSmsSetup.tsx | 51 ----- .../home/AuthenticationEditorScreen.stories.tsx | 55 ++++++ .../src/pages/home/AuthenticationEditorScreen.tsx | 199 ++++++++++++++------ .../pages/home/BackupFinishedScreen.stories.tsx | 2 +- .../src/pages/home/BackupFinishedScreen.tsx | 30 +-- .../src/pages/home/ChallengeOverviewScreen.tsx | 3 +- .../src/pages/home/ChallengePayingScreen.tsx | 2 +- .../src/pages/home/ContinentSelectionScreen.tsx | 102 +++++++++- .../src/pages/home/CountrySelectionScreen.tsx | 2 +- .../src/pages/home/PoliciesPayingScreen.tsx | 2 +- .../src/pages/home/RecoveryFinishedScreen.tsx | 4 +- .../pages/home/ReviewPoliciesScreen.stories.tsx | 208 ++++++++++++++++++--- .../src/pages/home/ReviewPoliciesScreen.tsx | 52 +++--- .../src/pages/home/SecretEditorScreen.tsx | 5 + .../src/pages/home/SecretSelectionScreen.tsx | 2 +- .../src/pages/home/SolveScreen.stories.tsx | 12 +- .../anastasis-webui/src/pages/home/SolveScreen.tsx | 26 +-- .../AuthMethodEmailSetup.stories.tsx | 66 +++++++ .../home/authMethodSetup/AuthMethodEmailSetup.tsx | 62 ++++++ .../AuthMethodIbanSetup.stories.tsx | 65 +++++++ .../home/authMethodSetup/AuthMethodIbanSetup.tsx | 68 +++++++ .../AuthMethodPostSetup.stories.tsx | 66 +++++++ .../home/authMethodSetup/AuthMethodPostSetup.tsx | 102 ++++++++++ .../AuthMethodQuestionSetup.stories.tsx | 66 +++++++ .../authMethodSetup/AuthMethodQuestionSetup.tsx | 70 +++++++ .../authMethodSetup/AuthMethodSmsSetup.stories.tsx | 66 +++++++ .../home/authMethodSetup/AuthMethodSmsSetup.tsx | 63 +++++++ .../AuthMethodTotpSetup.stories.tsx | 64 +++++++ .../home/authMethodSetup/AuthMethodTotpSetup.tsx | 47 +++++ .../AuthMethodVideoSetup.stories.tsx | 66 +++++++ .../home/authMethodSetup/AuthMethodVideoSetup.tsx | 56 ++++++ .../src/pages/home/authMethodSetup/index.tsx | 68 +++++++ packages/anastasis-webui/src/pages/home/index.tsx | 23 +-- packages/anastasis-webui/src/scss/main.scss | 8 +- packages/anastasis-webui/src/utils/index.tsx | 8 +- 55 files changed, 1875 insertions(+), 423 deletions(-) create mode 100644 packages/anastasis-webui/src/assets/empty.png create mode 100644 packages/anastasis-webui/src/assets/example/id1.jpg create mode 100644 packages/anastasis-webui/src/assets/icons/auth_method/email.svg create mode 100644 packages/anastasis-webui/src/assets/icons/auth_method/postal.svg create mode 100644 packages/anastasis-webui/src/assets/icons/auth_method/question.svg create mode 100644 packages/anastasis-webui/src/assets/icons/auth_method/sms.svg create mode 100644 packages/anastasis-webui/src/assets/icons/auth_method/video.svg create mode 100644 packages/anastasis-webui/src/components/QR.tsx create mode 100644 packages/anastasis-webui/src/components/fields/EmailInput.tsx create mode 100644 packages/anastasis-webui/src/components/fields/FileInput.tsx create mode 100644 packages/anastasis-webui/src/components/fields/ImageInput.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/AuthMethodSmsSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/assets/empty.png b/packages/anastasis-webui/src/assets/empty.png new file mode 100644 index 000000000..5120d3138 Binary files /dev/null and b/packages/anastasis-webui/src/assets/empty.png differ diff --git a/packages/anastasis-webui/src/assets/example/id1.jpg b/packages/anastasis-webui/src/assets/example/id1.jpg new file mode 100644 index 000000000..5d022a379 Binary files /dev/null and b/packages/anastasis-webui/src/assets/example/id1.jpg differ diff --git a/packages/anastasis-webui/src/assets/icons/auth_method/email.svg b/packages/anastasis-webui/src/assets/icons/auth_method/email.svg new file mode 100644 index 000000000..3e44b8779 --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/auth_method/email.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/anastasis-webui/src/assets/icons/auth_method/postal.svg b/packages/anastasis-webui/src/assets/icons/auth_method/postal.svg new file mode 100644 index 000000000..3787b8350 --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/auth_method/postal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/anastasis-webui/src/assets/icons/auth_method/question.svg b/packages/anastasis-webui/src/assets/icons/auth_method/question.svg new file mode 100644 index 000000000..a346556b2 --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/auth_method/question.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/anastasis-webui/src/assets/icons/auth_method/sms.svg b/packages/anastasis-webui/src/assets/icons/auth_method/sms.svg new file mode 100644 index 000000000..ed15679bf --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/auth_method/sms.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/anastasis-webui/src/assets/icons/auth_method/video.svg b/packages/anastasis-webui/src/assets/icons/auth_method/video.svg new file mode 100644 index 000000000..69de5e0b4 --- /dev/null +++ b/packages/anastasis-webui/src/assets/icons/auth_method/video.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/anastasis-webui/src/components/QR.tsx b/packages/anastasis-webui/src/components/QR.tsx new file mode 100644 index 000000000..48f1a7c12 --- /dev/null +++ b/packages/anastasis-webui/src/components/QR.tsx @@ -0,0 +1,35 @@ +/* + 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 + */ + +import { h, VNode } from "preact"; +import { useEffect, useRef } from "preact/hooks"; +import qrcode from "qrcode-generator"; + +export function QR({ text }: { text: string }): VNode { + const divRef = useRef(null); + useEffect(() => { + const qr = qrcode(0, 'L'); + qr.addData(text); + qr.make(); + if (divRef.current) divRef.current.innerHTML = qr.createSvgTag({ + scalable: true, + }); + }); + + return
    +
    +
    ; +} diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index e1c354f7b..69a05fcf3 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -25,7 +25,7 @@ export function DateInput(props: DateInputProps): VNode { setOpened2(v) } - const value = props.bind[0]; + const value = props.bind[0] || ""; const [dirty, setDirty] = useState(false) const showError = dirty && props.error @@ -40,7 +40,8 @@ export function DateInput(props: DateInputProps): VNode { { setOpened(true) }} + readonly + onFocus={() => { setOpened(true) } } value={value} ref={inputRef} /> diff --git a/packages/anastasis-webui/src/components/fields/EmailInput.tsx b/packages/anastasis-webui/src/components/fields/EmailInput.tsx new file mode 100644 index 000000000..e0fca0f46 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/EmailInput.tsx @@ -0,0 +1,44 @@ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; + +export interface TextInputProps { + label: string; + grabFocus?: boolean; + error?: string; + placeholder?: string; + tooltip?: string; + bind: [string, (x: string) => void]; +} + +export function EmailInput(props: TextInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + const value = props.bind[0]; + const [dirty, setDirty] = useState(false) + const showError = dirty && props.error + return (
    + +
    + {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + ref={inputRef} + style={{ display: "block" }} /> +
    + {showError &&

    {props.error}

    } +
    + ); +} diff --git a/packages/anastasis-webui/src/components/fields/FileInput.tsx b/packages/anastasis-webui/src/components/fields/FileInput.tsx new file mode 100644 index 000000000..8b144ea43 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/FileInput.tsx @@ -0,0 +1,81 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { TextInputProps } from "./TextInput"; + +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 + +export function FileInput(props: TextInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + + const value = props.bind[0]; + // const [dirty, setDirty] = useState(false) + const image = useRef(null) + const [sizeError, setSizeError] = useState(false) + function onChange(v: string): void { + // setDirty(true); + props.bind[1](v); + } + return
    + +
    + { + const f: FileList | null = e.currentTarget.files + if (!f || f.length != 1) { + return onChange("") + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true) + return onChange("") + } + setSizeError(false) + return f[0].arrayBuffer().then(b => { + const b64 = btoa( + new Uint8Array(b) + .reduce((data, byte) => data + String.fromCharCode(byte), '') + ) + return onChange(`data:${f[0].type};base64,${b64}` as any) + }) + }} /> + {props.error &&

    {props.error}

    } + {sizeError &&

    + File should be smaller than 1 MB +

    } +
    +
    +} + diff --git a/packages/anastasis-webui/src/components/fields/ImageInput.tsx b/packages/anastasis-webui/src/components/fields/ImageInput.tsx new file mode 100644 index 000000000..d5bf643d4 --- /dev/null +++ b/packages/anastasis-webui/src/components/fields/ImageInput.tsx @@ -0,0 +1,81 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import emptyImage from "../../assets/empty.png"; +import { TextInputProps } from "./TextInput"; + +const MAX_IMAGE_UPLOAD_SIZE = 1024 * 1024 + +export function ImageInput(props: TextInputProps): VNode { + const inputRef = useRef(null); + useLayoutEffect(() => { + if (props.grabFocus) { + inputRef.current?.focus(); + } + }, [props.grabFocus]); + + const value = props.bind[0]; + // const [dirty, setDirty] = useState(false) + const image = useRef(null) + const [sizeError, setSizeError] = useState(false) + function onChange(v: string): void { + // setDirty(true); + props.bind[1](v); + } + return
    + +
    + image.current?.click()} /> + { + const f: FileList | null = e.currentTarget.files + if (!f || f.length != 1) { + return onChange(emptyImage) + } + if (f[0].size > MAX_IMAGE_UPLOAD_SIZE) { + setSizeError(true) + return onChange(emptyImage) + } + setSizeError(false) + return f[0].arrayBuffer().then(b => { + const b64 = btoa( + new Uint8Array(b) + .reduce((data, byte) => data + String.fromCharCode(byte), '') + ) + return onChange(`data:${f[0].type};base64,${b64}` as any) + }) + }} /> + {props.error &&

    {props.error}

    } + {sizeError &&

    + Image should be smaller than 1 MB +

    } +
    +
    +} + diff --git a/packages/anastasis-webui/src/components/fields/NumberInput.tsx b/packages/anastasis-webui/src/components/fields/NumberInput.tsx index af9bbe66b..2b6cdcd2c 100644 --- a/packages/anastasis-webui/src/components/fields/NumberInput.tsx +++ b/packages/anastasis-webui/src/components/fields/NumberInput.tsx @@ -5,6 +5,7 @@ export interface TextInputProps { label: string; grabFocus?: boolean; error?: string; + placeholder?: string; tooltip?: string; bind: [string, (x: string) => void]; } @@ -30,6 +31,7 @@ export function NumberInput(props: TextInputProps): VNode { {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx index fa6fd9792..4bb785cd3 100644 --- a/packages/anastasis-webui/src/components/fields/TextInput.tsx +++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx @@ -5,6 +5,7 @@ export interface TextInputProps { label: string; grabFocus?: boolean; error?: string; + placeholder?: string; tooltip?: string; bind: [string, (x: string) => void]; } @@ -29,6 +30,7 @@ export function TextInput(props: TextInputProps): VNode {
    {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index 87e771009..35720e0f1 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -33,6 +33,7 @@ interface Props { export function Sidebar({ mobile }: Props): VNode { // const config = useConfigContext(); const config = { version: 'none' } + // FIXME: add replacement for __VERSION__ with the current version const process = { env: { __VERSION__: '0.0.0' } } const reducer = useAnastasisContext()! @@ -105,12 +106,12 @@ export function Sidebar({ mobile }: Props): VNode { Backup completed
    -
  • + {/*
  • Truth Paying
    -
  • + */} : (reducer.currentReducerState && reducer.currentReducerState?.recovery_state &&
  • diff --git a/packages/anastasis-webui/src/declaration.d.ts b/packages/anastasis-webui/src/declaration.d.ts index b32fb70fc..edd3a07a3 100644 --- a/packages/anastasis-webui/src/declaration.d.ts +++ b/packages/anastasis-webui/src/declaration.d.ts @@ -10,6 +10,10 @@ declare module '*.jpeg' { const content: any; export default content; } +declare module '*.png' { + const content: any; + export default content; +} declare module 'jed' { const x: any; export = x; diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index f74dcefba..2c7f54c5b 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ import { UserAttributeSpec, validators } from "anastasis-core"; -import { h, VNode } from "preact"; +import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame, withProcessLabel } from "./index"; @@ -20,53 +20,38 @@ export function AttributeEntryScreen(): VNode { if (!reducer.currentReducerState || !("required_attributes" in reducer.currentReducerState)) { return
    invalid state
    } + const reqAttr = reducer.currentReducerState.required_attributes || [] + let hasErrors = false; + const fieldList: VNode[] = reqAttr.map((spec, i: number) => { + const value = attrs[spec.name] + const error = checkIfValid(value, spec) + hasErrors = hasErrors || error !== undefined + return ( + setAttrs({ ...attrs, [spec.name]: v })} + spec={spec} + errorMessage={error} + value={value} /> + ); + }) return ( reducer.transition("enter_user_attributes", { identity_attributes: attrs, })} >
    - - {reducer.currentReducerState.required_attributes?.map((x, i: number) => { - const value = attrs[x.name] - function checkIfValid(): string | undefined { - const pattern = x['validation-regex'] - if (pattern) { - const re = new RegExp(pattern) - if (!re.test(value)) return 'The value is invalid' - } - const logic = x['validation-logic'] - if (logic) { - const func = (validators as any)[logic]; - if (func && typeof func === 'function' && !func(value)) return 'Please check the value' - } - const optional = x.optional - console.log('optiona', optional) - if (!optional && !value) { - return 'This value is required' - } - return undefined - } - - return ( - setAttrs({ ...attrs, [x.name]: v })} - spec={x} - isValid={checkIfValid} - value={value} /> - ); - })} - + {fieldList}
    -

    This personal information will help to locate your secret in the first place

    +

    This personal information will help to locate your secret.

    This stay private

    The information you have entered here:

    @@ -92,14 +77,13 @@ interface AttributeEntryFieldProps { value: string; setValue: (newValue: string) => void; spec: UserAttributeSpec; - isValid: () => string | undefined; + errorMessage: string | undefined; } const possibleBirthdayYear: Array = [] -for (let i = 0; i < 100; i++ ) { +for (let i = 0; i < 100; i++) { possibleBirthdayYear.push(2020 - i) } function AttributeEntryField(props: AttributeEntryFieldProps): VNode { - const errorMessage = props.isValid() return (
    @@ -108,14 +92,14 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { grabFocus={props.isFirst} label={props.spec.label} years={possibleBirthdayYear} - error={errorMessage} + error={props.errorMessage} bind={[props.value, props.setValue]} />} {props.spec.type === 'number' && } @@ -123,7 +107,7 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { } @@ -136,3 +120,21 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode {
    ); } + +function checkIfValid(value: string, spec: UserAttributeSpec): string | undefined { + const pattern = spec['validation-regex'] + if (pattern) { + const re = new RegExp(pattern) + if (!re.test(value)) return 'The value is invalid' + } + const logic = spec['validation-logic'] + if (logic) { + const func = (validators as any)[logic]; + if (func && typeof func === 'function' && !func(value)) return 'Please check the value' + } + const optional = spec.optional + if (!optional && !value) { + return 'This value is required' + } + return undefined +} diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx deleted file mode 100644 index c3783ea6c..000000000 --- a/packages/anastasis-webui/src/pages/home/AuthMethodEmailSetup.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "./index"; -import { TextInput } from "../../components/fields/TextInput"; - -export function AuthMethodEmailSetup(props: AuthMethodSetupProps): VNode { - const [email, setEmail] = useState(""); - return ( - -

    - 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. -

    -
    - -
    -
    - - -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx deleted file mode 100644 index c4ddeff91..000000000 --- a/packages/anastasis-webui/src/pages/home/AuthMethodPostSetup.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - canonicalJson, encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { TextInput } from "../../components/fields/TextInput"; - -export function AuthMethodPostSetup(props: AuthMethodSetupProps): VNode { - const [fullName, setFullName] = useState(""); - const [street, setStreet] = useState(""); - const [city, setCity] = useState(""); - const [postcode, setPostcode] = useState(""); - const [country, setCountry] = useState(""); - - const addPostAuth = () => { - const challengeJson = { - full_name: fullName, - street, - city, - postcode, - country, - }; - props.addAuthMethod({ - authentication_method: { - type: "email", - instructions: `Letter to address in postal code ${postcode}`, - challenge: encodeCrock(stringToBytes(canonicalJson(challengeJson))), - }, - }); - }; - - return ( -
    -

    Add {props.method} authentication

    -
    -

    - For postal letter authentication, you need to provide a postal - address. When recovering your secret, you will be asked to enter a - code that you will receive in a letter to that address. -

    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx deleted file mode 100644 index f1bab94ab..000000000 --- a/packages/anastasis-webui/src/pages/home/AuthMethodQuestionSetup.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "./index"; -import { TextInput } from "../../components/fields/TextInput"; - -export function AuthMethodQuestionSetup(props: AuthMethodSetupProps): VNode { - const [questionText, setQuestionText] = useState(""); - const [answerText, setAnswerText] = useState(""); - const addQuestionAuth = (): void => props.addAuthMethod({ - authentication_method: { - type: "question", - instructions: questionText, - challenge: encodeCrock(stringToBytes(answerText)), - }, - }); - return ( - -
    -

    - 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. -

    -
    - -
    -
    - -
    -
    - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/AuthMethodSmsSetup.tsx deleted file mode 100644 index 6f4797275..000000000 --- a/packages/anastasis-webui/src/pages/home/AuthMethodSmsSetup.tsx +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState, useRef, useLayoutEffect } from "preact/hooks"; -import { AuthMethodSetupProps } from "./AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "./index"; - -export function AuthMethodSmsSetup(props: AuthMethodSetupProps): VNode { - const [mobileNumber, setMobileNumber] = useState(""); - const addSmsAuth = (): void => { - props.addAuthMethod({ - authentication_method: { - type: "sms", - instructions: `SMS to ${mobileNumber}`, - challenge: encodeCrock(stringToBytes(mobileNumber)), - }, - }); - }; - const inputRef = useRef(null); - useLayoutEffect(() => { - inputRef.current?.focus(); - }, []); - return ( - -
    -

    - 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. -

    - -
    - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx index 8f86831a9..5077c3eb0 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.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,6 +20,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { ReducerState } from 'anastasis-core'; import { createExample, reducerStatesExample } from '../../utils'; import { AuthenticationEditorScreen as TestedComponent } from './AuthenticationEditorScreen'; @@ -36,3 +38,56 @@ export default { }; export const Example = createExample(TestedComponent, reducerStatesExample.authEditing); +export const OneAuthMethodConfigured = createExample(TestedComponent, { + ...reducerStatesExample.authEditing, + authentication_methods: [{ + type: 'question', + instructions: 'what time is it?', + challenge: 'asd', + }] +} as ReducerState); + + +export const SomeMoreAuthMethodConfigured = createExample(TestedComponent, { + ...reducerStatesExample.authEditing, + authentication_methods: [{ + type: 'question', + instructions: 'what time is it?', + challenge: 'asd', + },{ + type: 'question', + instructions: 'what time is it?', + challenge: 'qwe', + },{ + type: 'sms', + instructions: 'what time is it?', + challenge: 'asd', + },{ + type: 'email', + instructions: 'what time is it?', + challenge: 'asd', + },{ + type: 'email', + instructions: 'what time is it?', + challenge: 'asd', + },{ + type: 'email', + instructions: 'what time is it?', + challenge: 'asd', + },{ + type: 'email', + instructions: 'what time is it?', + challenge: 'asd', + }] +} as ReducerState); + +export const NoAuthMethodProvided = createExample(TestedComponent, { + ...reducerStatesExample.authEditing, + authentication_providers: {}, + authentication_methods: [] +} as ReducerState); + + // type: string; + // instructions: string; + // challenge: string; + // mime_type?: string; diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index e9ffccbac..f4d2aee58 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -1,19 +1,19 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { AuthMethod, ReducerStateBackup } from "anastasis-core"; -import { h, VNode } from "preact"; +import { AuthMethod } from "anastasis-core"; +import { ComponentChildren, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { AnastasisReducerApi } from "../../hooks/use-anastasis-reducer"; -import { AuthMethodEmailSetup } from "./AuthMethodEmailSetup"; -import { AuthMethodPostSetup } from "./AuthMethodPostSetup"; -import { AuthMethodQuestionSetup } from "./AuthMethodQuestionSetup"; -import { AuthMethodSmsSetup } from "./AuthMethodSmsSetup"; +import { authMethods, KnownAuthMethods } from "./authMethodSetup"; import { AnastasisClientFrame } from "./index"; + + +const getKeys = Object.keys as (obj: T) => Array + export function AuthenticationEditorScreen(): VNode { - const [selectedMethod, setSelectedMethod] = useState( - undefined - ); + const [noProvidersAck, setNoProvidersAck] = useState(false) + const [selectedMethod, setSelectedMethod] = useState(undefined); + const reducer = useAnastasisContext() if (!reducer) { return
    no reducer in context
    @@ -21,7 +21,29 @@ export function AuthenticationEditorScreen(): VNode { if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { return
    invalid state
    } + const configuredAuthMethods: AuthMethod[] = reducer.currentReducerState.authentication_methods ?? []; + const haveMethodsConfigured = configuredAuthMethods.length > 0; + + function removeByIndex(index: number): void { + if (reducer) reducer.transition("delete_authentication", { + authentication_method: index, + }) + } + + const camByType: { [s: string]: AuthMethodWithRemove[] } = {} + for (let index = 0; index < configuredAuthMethods.length; index++) { + const cam = { + ...configuredAuthMethods[index], + remove: () => removeByIndex(index) + } + const prevValue = camByType[cam.type] || [] + prevValue.push(cam) + camByType[cam.type] = prevValue; + } + + const providers = reducer.currentReducerState.authentication_providers!; + const authAvailableSet = new Set(); for (const provKey of Object.keys(providers)) { const p = providers[provKey]; @@ -31,79 +53,106 @@ export function AuthenticationEditorScreen(): VNode { } } } + if (selectedMethod) { const cancel = (): void => setSelectedMethod(undefined); const addMethod = (args: any): void => { reducer.transition("add_authentication", args); setSelectedMethod(undefined); }; - const methodMap: Record< - string, (props: AuthMethodSetupProps) => h.JSX.Element - > = { - sms: AuthMethodSmsSetup, - question: AuthMethodQuestionSetup, - email: AuthMethodEmailSetup, - post: AuthMethodPostSetup, - }; - const AuthSetup = methodMap[selectedMethod] ?? AuthMethodNotImplemented; + + const AuthSetup = authMethods[selectedMethod].screen ?? AuthMethodNotImplemented; return ( ); } - function MethodButton(props: { method: string; label: string }): VNode { + function MethodButton(props: { method: KnownAuthMethods }): VNode { return ( - +
    + +
    ); } - const configuredAuthMethods: AuthMethod[] = reducer.currentReducerState.authentication_methods ?? []; - const haveMethodsConfigured = configuredAuthMethods.length; + const errors = !haveMethodsConfigured ? "There is not enough authentication methods." : undefined; return ( - -
    - - - - - - -
    -

    Configured authentication methods

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

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

    +
    +
    + {getKeys(authMethods).map(method => )} +
    + {authAvailableSet.size === 0 && setNoProvidersAck(true)} description="No providers founds" label="Add a provider manually"> + We have found no trusted cloud providers for your recovery secret. You can add a provider manually. + To add a provider you must know the provider URL (e.g. https://provider.com) +

    + More about cloud providers

    - ); - }) - ) : ( -

    No authentication methods configured yet.

    - )} +
    } + + {/* {haveMethodsConfigured && ( + configuredAuthMethods.map((x, i) => { + return ( +

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

    + ); + }) + )} */} +
    +
    + When recovering your wallet, you will be asked to verify your identity via the methods you configure here. +
    +
    ); } +type AuthMethodWithRemove = AuthMethod & { remove: () => void } export interface AuthMethodSetupProps { method: string; addAuthMethod: (x: any) => void; + configured: AuthMethodWithRemove[]; cancel: () => void; } @@ -116,8 +165,36 @@ function AuthMethodNotImplemented(props: AuthMethodSetupProps): VNode { ); } -interface AuthenticationEditorProps { - reducer: AnastasisReducerApi; - backupState: ReducerStateBackup; + +function ConfirmModal({ active, description, onCancel, onConfirm, children, danger, disabled, label = 'Confirm' }: Props): VNode { + return
    + } +interface Props { + active?: boolean; + description?: string; + onCancel?: () => void; + onConfirm?: () => void; + label?: string; + children?: ComponentChildren; + danger?: boolean; + disabled?: boolean; +} diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index 0c9d007bc..b71a79727 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -37,7 +37,7 @@ export default { }, }; -export const Simple = createExample(TestedComponent, reducerStatesExample.backupFinished); +export const WithoutName = createExample(TestedComponent, reducerStatesExample.backupFinished); export const WithName = createExample(TestedComponent, {...reducerStatesExample.backupFinished, secret_name: 'super_secret', diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index 218f1d1fd..70ac8157d 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -1,3 +1,4 @@ +import { format } from "date-fns"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; @@ -11,23 +12,30 @@ export function BackupFinishedScreen(): VNode { return
    invalid state
    } const details = reducer.currentReducerState.success_details - return ( -

    - Your backup of secret "{reducer.currentReducerState.secret_name ?? "??"}" was + + return ( + {reducer.currentReducerState.secret_name ?

    + Your backup of secret "{reducer.currentReducerState.secret_name}" was successful. -

    -

    The backup is stored by the following providers:

    +

    : +

    + Your secret was successfully backed up. +

    } - {details &&
      + {details &&
      +

      The backup is stored by the following providers:

      {Object.keys(details).map((x, i) => { const sd = details[x]; return ( -
    • - {x} (Policy version {sd.policy_version}) -
    • +
      + {x} +

      + version {sd.policy_version} + {sd.policy_expiration.t_ms !== 'never' ? ` expires at: ${format(sd.policy_expiration.t_ms, 'dd/MM/yyyy')}` : ' without expiration date'} +

      +
      ); })} -
    } - +
    } ); } diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index 3bb3fb837..cf44d5bf4 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -47,8 +47,9 @@ export function ChallengeOverviewScreen(): VNode { const atLeastThereIsOnePolicySolved = policiesWithInfo.find(p => p.isPolicySolved) !== undefined + const errors = !atLeastThereIsOnePolicySolved ? "Solve one policy before proceeding" : undefined; return ( - + {!policies.length ?

    No policies found, try with another version of the secret

    : (policies.length === 1 ?

    diff --git a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx index d87afdf46..84896a2ec 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengePayingScreen.tsx @@ -13,7 +13,7 @@ export function ChallengePayingScreen(): VNode { const payments = ['']; //reducer.currentReducerState.payments ?? return (

    diff --git a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx index 94c0409da..713655625 100644 --- a/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ContinentSelectionScreen.tsx @@ -1,20 +1,108 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { BackupStates, ContinentInfo, RecoveryStates } from "anastasis-core"; import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame, withProcessLabel } from "./index"; export function ContinentSelectionScreen(): VNode { const reducer = useAnastasisContext() + + //FIXME: remove this when #7056 is fixed + const [countryCode, setCountryCode] = useState("") + if (!reducer || !reducer.currentReducerState || !("continents" in reducer.currentReducerState)) { return

    } - const select = (continent: string) => (): void => reducer.transition("select_continent", { continent }); + const selectContinent = (continent: string): void => { + reducer.transition("select_continent", { continent }) + }; + const selectCountry = (country: string): void => { + setCountryCode(country) + }; + + + const continentList = reducer.currentReducerState.continents || []; + const countryList = reducer.currentReducerState.countries || []; + const theContinent = reducer.currentReducerState.selected_continent || "" + // const cc = reducer.currentReducerState.selected_country || ""; + const theCountry = countryList.find(c => c.code === countryCode) + const selectCountryAction = () => { + //selection should be when the select box changes it value + if (!theCountry) return; + reducer.transition("select_country", { + country_code: countryCode, + currencies: [theCountry.currency], + }) + } + + const step1 = reducer.currentReducerState.backup_state === BackupStates.ContinentSelecting || + reducer.currentReducerState.recovery_state === RecoveryStates.ContinentSelecting; + + const errors = !theCountry ? "Select a country" : undefined + return ( - - {reducer.currentReducerState.continents.map((x: any) => ( - - ))} + +
    +
    +
    + +
    +
    + +
    + +
    +
    + {!step1 && + reducer.back()}> + X + + } +
    +
    + +
    + +
    +
    + +
    + +
    +
    +
    +
    + + {theCountry &&
    + +
    + +
    +
    } +
    +
    +

    + A location will help to define a common information that will be use to locate your secret and a currency + for payments if needed. +

    +
    +
    +
    ); } diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx index 417c08633..77329f4fa 100644 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx @@ -18,7 +18,7 @@ export function CountrySelectionScreen(): VNode { return (
    - {reducer.currentReducerState.countries.map((x: any) => ( + {reducer.currentReducerState.countries!.map((x: any) => (
    +
    ); })} diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx index 79a46761c..915465c3f 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx @@ -6,6 +6,7 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame} from "./index"; import { TextInput } from "../../components/fields/TextInput"; +import { FileInput } from "../../components/fields/FileInput"; export function SecretEditorScreen(): VNode { const reducer = useAnastasisContext() @@ -57,6 +58,10 @@ export function SecretEditorScreen(): VNode { or import a file +
    diff --git a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx index 5d67ee472..d0b83bda5 100644 --- a/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretSelectionScreen.tsx @@ -37,7 +37,7 @@ export function SecretSelectionScreen(): VNode { const recoveryDocument = reducer.currentReducerState.recovery_document if (!recoveryDocument) { return ( - +

    No recovery document found, try with another provider

    diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx index c05c36b07..cb6561b3f 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.stories.tsx @@ -44,7 +44,7 @@ export const NotSupportedChallenge = createExample(TestedComponent, { recovery_information: { challenges: [{ cost: 'USD:1', - instructions: 'follow htis instructions', + instructions: 'does P equals NP?', type: 'chall-type', uuid: 'ASDASDSAD!1' }], @@ -58,7 +58,7 @@ export const MismatchedChallengeId = createExample(TestedComponent, { recovery_information: { challenges: [{ cost: 'USD:1', - instructions: 'follow htis instructions', + instructions: 'does P equals NP?', type: 'chall-type', uuid: 'ASDASDSAD!1' }], @@ -72,7 +72,7 @@ export const SmsChallenge = createExample(TestedComponent, { recovery_information: { challenges: [{ cost: 'USD:1', - instructions: 'follow htis instructions', + instructions: 'SMS to 555-5555', type: 'sms', uuid: 'ASDASDSAD!1' }], @@ -86,7 +86,7 @@ export const QuestionChallenge = createExample(TestedComponent, { recovery_information: { challenges: [{ cost: 'USD:1', - instructions: 'follow htis instructions', + instructions: 'does P equals NP?', type: 'question', uuid: 'ASDASDSAD!1' }], @@ -100,7 +100,7 @@ export const EmailChallenge = createExample(TestedComponent, { recovery_information: { challenges: [{ cost: 'USD:1', - instructions: 'follow htis instructions', + instructions: 'Email to sebasjm@some-domain.com', type: 'email', uuid: 'ASDASDSAD!1' }], @@ -114,7 +114,7 @@ export const PostChallenge = createExample(TestedComponent, { recovery_information: { challenges: [{ cost: 'USD:1', - instructions: 'follow htis instructions', + instructions: 'Letter to address in postal code ABC123', type: 'post', uuid: 'ASDASDSAD!1' }], diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index 077726e02..b0cfa9bb0 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -8,26 +8,26 @@ import { useAnastasisContext } from "../../context/anastasis"; export function SolveScreen(): VNode { const reducer = useAnastasisContext() const [answer, setAnswer] = useState(""); - + if (!reducer) { - return + return
    no reducer in context
    } if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return + return
    invalid state
    } if (!reducer.currentReducerState.recovery_information) { - return + return
    no recovery information found
    } if (!reducer.currentReducerState.selected_challenge_uuid) { - return -
    no selected uuid
    + return +
    invalid state
    } @@ -55,7 +55,7 @@ export function SolveScreen(): VNode { function onCancel(): void { reducer?.back() } - + return (
    - - -
    + + +
    ); } @@ -82,13 +82,13 @@ export interface SolveEntryProps { challenge: ChallengeInfo; feedback?: ChallengeFeedback; answer: string; - setAnswer: (s:string) => void; + setAnswer: (s: string) => void; } function SolveSmsEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { return ( -

    An sms has been sent to "{challenge.instructions}". Type the code below

    - +

    An sms has been sent to "{challenge.instructions}". Type the code below

    +
    ); } diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx new file mode 100644 index 000000000..e178a4955 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/email', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'email' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Email to sebasjm@email.com ', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Email to sebasjm@email.com', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Email to someone@sebasjm.com', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx new file mode 100644 index 000000000..e8cee9cb4 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; +import { EmailInput } from "../../../components/fields/EmailInput"; + +const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + +export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { + const [email, setEmail] = useState(""); + const addEmailAuth = (): void => addAuthMethod({ + authentication_method: { + type: "email", + instructions: `Email to ${email}`, + challenge: encodeCrock(stringToBytes(email)), + }, + }); + const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined + const errors = !email ? 'Add your email' : emailError + + return ( + +

    + 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. +

    +
    + +
    + {configured.length > 0 &&
    +
    + Your emails: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx new file mode 100644 index 000000000..71f618646 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx @@ -0,0 +1,65 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/IBAN', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'iban' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', + remove: () => null + }] +}); +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Wire transfer from QWEASD123123 with holder Javier', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', + remove: () => null + }] +},); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx new file mode 100644 index 000000000..c9edbfa07 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + canonicalJson, + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { TextInput } from "../../../components/fields/TextInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [name, setName] = useState(""); + const [account, setAccount] = useState(""); + const addIbanAuth = (): void => addAuthMethod({ + authentication_method: { + type: "iban", + instructions: `Wire transfer from ${account} with holder ${name}`, + challenge: encodeCrock(stringToBytes(canonicalJson({ + name, account + }))), + }, + }); + const errors = !name ? 'Add an account name' : ( + !account ? 'Add an account IBAN number' : undefined + ) + return ( + +

    + For bank transfer authentication, you need to provide a bank + account (account holder name and IBAN). When recovering your + secret, you will be asked to pay the recovery fee via bank + transfer from the account you provided here. +

    +
    + + +
    + {configured.length > 0 &&
    +
    + Your bank accounts: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx new file mode 100644 index 000000000..0f1c17495 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/Post', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'post' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Letter to address in postal code QWE456', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Letter to address in postal code QWE456', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Letter to address in postal code ABC123', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx new file mode 100644 index 000000000..bfeaaa832 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + canonicalJson, encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { TextInput } from "../../../components/fields/TextInput"; +import { AnastasisClientFrame } from ".."; + +export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [fullName, setFullName] = useState(""); + const [street, setStreet] = useState(""); + const [city, setCity] = useState(""); + const [postcode, setPostcode] = useState(""); + const [country, setCountry] = useState(""); + + const addPostAuth = () => { + const challengeJson = { + full_name: fullName, + street, + city, + postcode, + country, + }; + addAuthMethod({ + authentication_method: { + type: "post", + instructions: `Letter to address in postal code ${postcode}`, + challenge: encodeCrock(stringToBytes(canonicalJson(challengeJson))), + }, + }); + }; + + const errors = !fullName ? 'The full name is missing' : ( + !street ? 'The street is missing' : ( + !city ? 'The city is missing' : ( + !postcode ? 'The postcode is missing' : ( + !country ? 'The country is missing' : undefined + ) + ) + ) + ) + return ( + +

    + For postal letter authentication, you need to provide a postal + address. When recovering your secret, you will be asked to enter a + code that you will receive in a letter to that address. +

    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + + {configured.length > 0 &&
    +
    + Your postal code: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    +
    } +
    + + + + +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx new file mode 100644 index 000000000..3ba4a84ca --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/Question', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'question' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Is integer factorization polynomial? (non-quantum computer)', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Does P equal NP?', + remove: () => null + },{ + challenge: 'asd', + type, + instructions: 'Are continuous groups automatically differential groups?', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx new file mode 100644 index 000000000..eab800e35 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; + +export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { + const [questionText, setQuestionText] = useState(""); + const [answerText, setAnswerText] = useState(""); + const addQuestionAuth = (): void => addAuthMethod({ + authentication_method: { + type: "question", + instructions: questionText, + challenge: encodeCrock(stringToBytes(answerText)), + }, + }); + + const errors = !questionText ? "Add your security question" : ( + !answerText ? 'Add the answer to your question' : undefined + ) + return ( + +
    +

    + 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. +

    +
    + +
    +
    + +
    + + {configured.length > 0 &&
    +
    + Your security questions: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx new file mode 100644 index 000000000..ae8297ef7 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/Sms', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'sms' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'SMS to +11-1234-2345', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'SMS to +11-1234-2345', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'SMS to +11-5555-2345', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx new file mode 100644 index 000000000..9e85af2b2 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { NumberInput } from "../../../components/fields/NumberInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [mobileNumber, setMobileNumber] = useState(""); + const addSmsAuth = (): void => { + addAuthMethod({ + authentication_method: { + type: "sms", + instructions: `SMS to ${mobileNumber}`, + challenge: encodeCrock(stringToBytes(mobileNumber)), + }, + }); + }; + const inputRef = useRef(null); + useLayoutEffect(() => { + inputRef.current?.focus(); + }, []); + const errors = !mobileNumber ? 'Add a mobile number' : undefined + return ( + +
    +

    + 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. +

    +
    + +
    + {configured.length > 0 &&
    +
    + Your mobile numbers: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx new file mode 100644 index 000000000..3447e3d61 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx @@ -0,0 +1,64 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/TOTP', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'totp' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'instr', + remove: () => null + }] +}); +export const WithMoreExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'instr', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'instr', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx new file mode 100644 index 000000000..bbffedad6 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; +import { QR } from "../../../components/QR"; + +export function AuthMethodTotpSetup({addAuthMethod, cancel, configured}: AuthMethodSetupProps): VNode { + const [name, setName] = useState(""); + const addTotpAuth = (): void => addAuthMethod({ + authentication_method: { + type: "totp", + instructions: `Enter code for ${name}`, + challenge: encodeCrock(stringToBytes(name)), + }, + }); + const errors = !name ? 'The TOTP name is missing' : undefined; + return ( + +

    + For Time-based One-Time Password (TOTP) authentication, you need to set + a name for the TOTP secret. Then, you must scan the generated QR code + with your TOTP App to import the TOTP secret into your TOTP App. +

    +
    + +
    + +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx new file mode 100644 index 000000000..3c4c7bf39 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; +import logoImage from '../../../assets/logo.jpeg' + +export default { + title: 'Pages/backup/authMethods/Video', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'video' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: logoImage, + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: logoImage, + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: logoImage, + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx new file mode 100644 index 000000000..d292a9d24 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { ImageInput } from "../../../components/fields/ImageInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { + const [image, setImage] = useState(""); + const addVideoAuth = (): void => { + addAuthMethod({ + authentication_method: { + type: "video", + instructions: image, + challenge: encodeCrock(stringToBytes(image)), + }, + }) + }; + return ( + +

    + For video identification, you need to provide a passport-style + photograph. When recovering your secret, you will be asked to join a + video call. During that call, a human will use the photograph to + verify your identity. +

    +
    + +
    + {configured.length > 0 &&
    +
    + Your photographs: +
    + {configured.map((c, i) => { + return
    + +
    +
    + })} +
    } +
    +
    + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx new file mode 100644 index 000000000..1e1d7bc03 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx @@ -0,0 +1,68 @@ +import { h, VNode } from "preact"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; + +import { AuthMethodEmailSetup as EmailScreen } from "./AuthMethodEmailSetup"; +import { AuthMethodIbanSetup as IbanScreen } from "./AuthMethodIbanSetup"; +import { AuthMethodPostSetup as PostalScreen } from "./AuthMethodPostSetup"; +import { AuthMethodQuestionSetup as QuestionScreen } from "./AuthMethodQuestionSetup"; +import { AuthMethodSmsSetup as SmsScreen } from "./AuthMethodSmsSetup"; +import { AuthMethodTotpSetup as TotpScreen } from "./AuthMethodTotpSetup"; +import { AuthMethodVideoSetup as VideScreen } from "./AuthMethodVideoSetup"; +import postalIcon from '../../../assets/icons/auth_method/postal.svg'; +import questionIcon from '../../../assets/icons/auth_method/question.svg'; +import smsIcon from '../../../assets/icons/auth_method/sms.svg'; +import videoIcon from '../../../assets/icons/auth_method/video.svg'; + +interface AuthMethodConfiguration { + icon: VNode; + label: string; + screen: (props: AuthMethodSetupProps) => VNode; +} +export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; + +type KnowMethodConfig = { + [name in KnownAuthMethods]: AuthMethodConfiguration; +}; + +export const authMethods: KnowMethodConfig = { + question: { + icon: , + label: "Question", + screen: QuestionScreen + }, + sms: { + icon: , + label: "SMS", + screen: SmsScreen + }, + email: { + icon: , + label: "Email", + screen: EmailScreen + + }, + iban: { + icon: , + label: "IBAN", + screen: IbanScreen + + }, + post: { + icon: , + label: "Physical mail", + screen: PostalScreen + + }, + totp: { + icon: , + label: "TOTP", + screen: TotpScreen + + }, + video: { + icon: , + label: "Video", + screen: VideScreen + + } +} \ No newline at end of file diff --git a/packages/anastasis-webui/src/pages/home/index.tsx b/packages/anastasis-webui/src/pages/home/index.tsx index 5cef4ee9c..fefaa184c 100644 --- a/packages/anastasis-webui/src/pages/home/index.tsx +++ b/packages/anastasis-webui/src/pages/home/index.tsx @@ -11,7 +11,8 @@ import { VNode } from "preact"; import { - useErrorBoundary} from "preact/hooks"; + useErrorBoundary +} from "preact/hooks"; import { Menu } from "../../components/menu"; import { AnastasisProvider, useAnastasisContext } from "../../context/anastasis"; import { @@ -59,7 +60,7 @@ interface AnastasisClientFrameProps { /** * Hide only the "next" button. */ - hideNext?: boolean; + hideNext?: string; } function ErrorBoundary(props: { @@ -112,13 +113,15 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode {
    handleKeyPress(e)}> -

    {props.title}

    +

    {props.title}

    {props.children} {!props.hideNav ? ( -
    +
    - {!props.hideNext ? : null} + + +
    ) : null}
    @@ -151,18 +154,12 @@ const AnastasisClientImpl: FunctionalComponent = () => { if ( state.backup_state === BackupStates.ContinentSelecting || - state.recovery_state === RecoveryStates.ContinentSelecting - ) { - return ( - - ); - } - if ( + state.recovery_state === RecoveryStates.ContinentSelecting || state.backup_state === BackupStates.CountrySelecting || state.recovery_state === RecoveryStates.CountrySelecting ) { return ( - + ); } if ( diff --git a/packages/anastasis-webui/src/scss/main.scss b/packages/anastasis-webui/src/scss/main.scss index 2e60bf6f9..1e0d3fded 100644 --- a/packages/anastasis-webui/src/scss/main.scss +++ b/packages/anastasis-webui/src/scss/main.scss @@ -198,10 +198,10 @@ div[data-tooltip]::before { max-width: 40em; } -.home div { - margin-top: 0.5em; - margin-bottom: 0.5em; -} +// .home div { +// margin-top: 0.5em; +// margin-bottom: 0.5em; +// } .policy { padding: 0.5em; diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx index 670e229cd..48ac47544 100644 --- a/packages/anastasis-webui/src/utils/index.tsx +++ b/packages/anastasis-webui/src/utils/index.tsx @@ -86,7 +86,13 @@ const base = { { type: "question", usage_fee: "COL:0" - } + },{ + type: "sms", + usage_fee: "COL:0" + },{ + type: "email", + usage_fee: "COL:0" + }, ], salt: "WBMDD76BR1E90YQ5AHBMKPH7GW", storage_limit_in_megabytes: 16, -- cgit v1.2.3 From a4cdc02e5017ba587c169cb28a7e7927fc64c7cf Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 2 Nov 2021 10:12:52 -0300 Subject: totp qr code --- packages/anastasis-webui/src/declaration.d.ts | 3 +- .../pages/home/ReviewPoliciesScreen.stories.tsx | 2 +- .../AuthMethodTotpSetup.stories.tsx | 6 +-- .../home/authMethodSetup/AuthMethodTotpSetup.tsx | 54 +++++++++++++++++---- .../src/pages/home/authMethodSetup/totp.ts | 56 ++++++++++++++++++++++ 5 files changed, 105 insertions(+), 16 deletions(-) create mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/declaration.d.ts b/packages/anastasis-webui/src/declaration.d.ts index edd3a07a3..2c4b7cb3a 100644 --- a/packages/anastasis-webui/src/declaration.d.ts +++ b/packages/anastasis-webui/src/declaration.d.ts @@ -17,5 +17,4 @@ declare module '*.png' { declare module 'jed' { const x: any; export = x; - } - \ No newline at end of file +} diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx index 007011326..5ba0c937d 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx @@ -244,5 +244,5 @@ export const SomePoliciesWithMethods = createExample(TestedComponent, { type: "question", instructions: "Does P equal NP?", challenge: "C5SP8" - }] +}] } as ReducerState); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx index 3447e3d61..4e46b600e 100644 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx @@ -45,7 +45,7 @@ export const WithOneExample = createExample(TestedComponent[type].screen, reduce configured: [{ challenge: 'qwe', type, - instructions: 'instr', + instructions: 'Enter 8 digits code for "Anastasis"', remove: () => null }] }); @@ -53,12 +53,12 @@ export const WithMoreExample = createExample(TestedComponent[type].screen, reduc configured: [{ challenge: 'qwe', type, - instructions: 'instr', + instructions: 'Enter 8 digits code for "Anastasis1"', remove: () => null },{ challenge: 'qwe', type, - instructions: 'instr', + instructions: 'Enter 8 digits code for "Anastasis2"', remove: () => null }] }); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx index bbffedad6..db656e630 100644 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx @@ -4,36 +4,70 @@ import { stringToBytes } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; +import { useMemo, useState } from "preact/hooks"; import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; import { AnastasisClientFrame } from "../index"; import { TextInput } from "../../../components/fields/TextInput"; import { QR } from "../../../components/QR"; +import { base32enc, computeTOTPandCheck } from "./totp"; + +export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [name, setName] = useState("anastasis"); + const [test, setTest] = useState(""); + const digits = 8 + const secretKey = useMemo(() => { + const array = new Uint8Array(32) + return window.crypto.getRandomValues(array) + }, []) + const secret32 = base32enc(secretKey); + const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` -export function AuthMethodTotpSetup({addAuthMethod, cancel, configured}: AuthMethodSetupProps): VNode { - const [name, setName] = useState(""); const addTotpAuth = (): void => addAuthMethod({ authentication_method: { type: "totp", - instructions: `Enter code for ${name}`, - challenge: encodeCrock(stringToBytes(name)), + instructions: `Enter ${digits} digits code for ${name}`, + challenge: encodeCrock(stringToBytes(totpURL)), }, }); - const errors = !name ? 'The TOTP name is missing' : undefined; + + const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10)); + + const errors = !name ? 'The TOTP name is missing' : ( + !testCodeMatches ? 'The test code doesnt match' : undefined + ); return (

    - For Time-based One-Time Password (TOTP) authentication, you need to set - a name for the TOTP secret. Then, you must scan the generated QR code + For Time-based One-Time Password (TOTP) authentication, you need to set + a name for the TOTP secret. Then, you must scan the generated QR code with your TOTP App to import the TOTP secret into your TOTP App.

    -
    +
    - +
    + +
    +

    + After scanning the code with your TOTP App, test it in the input below. +

    + + {configured.length > 0 &&
    +
    + Your TOTP numbers: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    }
    diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts b/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts new file mode 100644 index 000000000..0bc3feaf8 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import jssha from 'jssha' + +const SEARCH_RANGE = 16 +const timeStep = 30 + +export function computeTOTPandCheck(secretKey: Uint8Array, digits: number, code: number): boolean { + const now = new Date().getTime() + const epoch = Math.floor(Math.round(now / 1000.0) / timeStep); + + for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) { + const movingFactor = (epoch + ms).toString(16).padStart(16, "0"); + + const hmacSha = new jssha('SHA-1', 'HEX', { hmacKey: { value: secretKey, format: 'UINT8ARRAY' } }); + hmacSha.update(movingFactor); + const hmac_text = hmacSha.getHMAC('UINT8ARRAY'); + + const offset = (hmac_text[hmac_text.length - 1] & 0xf) + + const otp = (( + (hmac_text[offset + 0] << 24) + + (hmac_text[offset + 1] << 16) + + (hmac_text[offset + 2] << 8) + + (hmac_text[offset + 3]) + ) & 0x7fffffff) % Math.pow(10, digits) + + if (otp == code) return true + } + return false +} + +const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split('') +export function base32enc(buffer: Uint8Array): string { + let rpos = 0 + let bits = 0 + let vbit = 0 + + let result = "" + while ((rpos < buffer.length) || (vbit > 0)) { + if ((rpos < buffer.length) && (vbit < 5)) { + bits = (bits << 8) | buffer[rpos++]; + vbit += 8; + } + if (vbit < 5) { + bits <<= (5 - vbit); + vbit = 5; + } + result += encTable__[(bits >> (vbit - 5)) & 31]; + vbit -= 5; + } + return result +} + +// const array = new Uint8Array(256) +// const secretKey = window.crypto.getRandomValues(array) +// console.log(base32enc(secretKey)) -- cgit v1.2.3 From 1fd337f4fed08d7867359ec52104a6cadb76cdfc Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 2 Nov 2021 12:31:37 -0300 Subject: refactoring challenge overview to look more like policy reviewing --- .../src/pages/home/AuthenticationEditorScreen.tsx | 2 +- .../pages/home/ChallengeOverviewScreen.stories.tsx | 61 +++++++----- .../src/pages/home/ChallengeOverviewScreen.tsx | 82 +++++++++-------- .../src/pages/home/ReviewPoliciesScreen.tsx | 2 +- .../authMethod/AuthMethodEmailSetup.stories.tsx | 66 +++++++++++++ .../pages/home/authMethod/AuthMethodEmailSetup.tsx | 62 +++++++++++++ .../authMethod/AuthMethodIbanSetup.stories.tsx | 65 +++++++++++++ .../pages/home/authMethod/AuthMethodIbanSetup.tsx | 68 ++++++++++++++ .../authMethod/AuthMethodPostSetup.stories.tsx | 66 +++++++++++++ .../pages/home/authMethod/AuthMethodPostSetup.tsx | 102 +++++++++++++++++++++ .../authMethod/AuthMethodQuestionSetup.stories.tsx | 66 +++++++++++++ .../home/authMethod/AuthMethodQuestionSetup.tsx | 70 ++++++++++++++ .../home/authMethod/AuthMethodSmsSetup.stories.tsx | 66 +++++++++++++ .../pages/home/authMethod/AuthMethodSmsSetup.tsx | 63 +++++++++++++ .../authMethod/AuthMethodTotpSetup.stories.tsx | 64 +++++++++++++ .../pages/home/authMethod/AuthMethodTotpSetup.tsx | 81 ++++++++++++++++ .../authMethod/AuthMethodVideoSetup.stories.tsx | 66 +++++++++++++ .../pages/home/authMethod/AuthMethodVideoSetup.tsx | 56 +++++++++++ .../src/pages/home/authMethod/index.tsx | 68 ++++++++++++++ .../src/pages/home/authMethod/totp.ts | 56 +++++++++++ .../AuthMethodEmailSetup.stories.tsx | 66 ------------- .../home/authMethodSetup/AuthMethodEmailSetup.tsx | 62 ------------- .../AuthMethodIbanSetup.stories.tsx | 65 ------------- .../home/authMethodSetup/AuthMethodIbanSetup.tsx | 68 -------------- .../AuthMethodPostSetup.stories.tsx | 66 ------------- .../home/authMethodSetup/AuthMethodPostSetup.tsx | 102 --------------------- .../AuthMethodQuestionSetup.stories.tsx | 66 ------------- .../authMethodSetup/AuthMethodQuestionSetup.tsx | 70 -------------- .../authMethodSetup/AuthMethodSmsSetup.stories.tsx | 66 ------------- .../home/authMethodSetup/AuthMethodSmsSetup.tsx | 63 ------------- .../AuthMethodTotpSetup.stories.tsx | 64 ------------- .../home/authMethodSetup/AuthMethodTotpSetup.tsx | 81 ---------------- .../AuthMethodVideoSetup.stories.tsx | 66 ------------- .../home/authMethodSetup/AuthMethodVideoSetup.tsx | 56 ----------- .../src/pages/home/authMethodSetup/index.tsx | 68 -------------- .../src/pages/home/authMethodSetup/totp.ts | 56 ----------- 36 files changed, 1167 insertions(+), 1150 deletions(-) create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/index.tsx create mode 100644 packages/anastasis-webui/src/pages/home/authMethod/totp.ts delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index f4d2aee58..4e7819a77 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -3,7 +3,7 @@ import { AuthMethod } from "anastasis-core"; import { ComponentChildren, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useAnastasisContext } from "../../context/anastasis"; -import { authMethods, KnownAuthMethods } from "./authMethodSetup"; +import { authMethods, KnownAuthMethods } from "./authMethod"; import { AnastasisClientFrame } from "./index"; diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index 758963574..a89b5640c 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -56,17 +56,17 @@ export const SomePoliciesOneSolved = createExample(TestedComponent, { policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]], challenges: [{ cost: 'USD:1', - instructions: 'just go for it', + instructions: 'this question cost 1 USD', type: 'question', uuid: '1', }, { - cost: 'USD:1', - instructions: 'just go for it', + cost: 'USD:0', + instructions: 'answering this question is free', type: 'question', uuid: '2', }, { cost: 'USD:1', - instructions: 'just go for it', + instructions: 'this question is already answered', type: 'question', uuid: 'uuid-3', }] @@ -84,8 +84,8 @@ export const OneBadConfiguredPolicy = createExample(TestedComponent, { policies: [[{ uuid: '1' }, { uuid: '2' }]], challenges: [{ cost: 'USD:1', - instructions: 'just go for it', - type: 'sasd', + instructions: 'this policy has a missing uuid (the other auth method)', + type: 'totp', uuid: '1', }], }, @@ -101,35 +101,48 @@ export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, { { uuid: '4' }, { uuid: '5' }, { uuid: '6' }, + { uuid: '7' }, + { uuid: '8' }, ]], challenges: [{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'Does P equals NP?', type: 'question', uuid: '1', },{ cost: 'USD:1', - instructions: 'enter a text received by a sms', + instructions: 'SMS to 555-555', type: 'sms', uuid: '2', },{ cost: 'USD:1', - instructions: 'enter a text received by a email', + instructions: 'Email to qwe@asd.com', type: 'email', uuid: '3', },{ cost: 'USD:1', - instructions: 'enter a code based on a time-based one-time password', + instructions: 'Enter 8 digits code for "Anastasis"', type: 'totp', uuid: '4', - },{ - cost: 'USD:1', - instructions: 'send a wire transfer to an account', + },{// + cost: 'USD:0', + instructions: 'Wire transfer from ASDXCVQWE123123 with holder Florian', type: 'iban', uuid: '5', },{ cost: 'USD:1', - instructions: 'just go for it', + instructions: 'Join a video call', + type: 'video',//Enter 8 digits code for "Anastasis" + uuid: '7', + },{ + },{ + cost: 'USD:1', + instructions: 'Letter to address in postal code DE123123', + type: 'post',//Enter 8 digits code for "Anastasis" + uuid: '8', + },{ + cost: 'USD:1', + instructions: 'instruction for an unknown type of challenge', type: 'new-type-of-challenge', uuid: '6', }], @@ -154,52 +167,52 @@ export const OnePolicyWithAllTheChallengesInDifferentState = createExample(Teste ]], challenges: [{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "solved"', type: 'question', uuid: '1', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "hint"', type: 'question', uuid: '2', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "details"', type: 'question', uuid: '3', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "body"', type: 'question', uuid: '4', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "redirect"', type: 'question', uuid: '5', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "server-failure"', type: 'question', uuid: '6', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "truth-unknown"', type: 'question', uuid: '7', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "rate-limit-exceeded"', type: 'question', uuid: '8', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "authentication-timeout"', type: 'question', uuid: '9', },{ cost: 'USD:1', - instructions: 'answer the a question correctly', + instructions: 'in state "external-instructions"', type: 'question', uuid: '10', }], diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index cf44d5bf4..7b9b060ce 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -1,7 +1,9 @@ +/* eslint-disable @typescript-eslint/camelcase */ import { ChallengeFeedback } from "anastasis-core"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; +import { authMethods, KnownAuthMethods } from "./authMethod"; export function ChallengeOverviewScreen(): VNode { const reducer = useAnastasisContext() @@ -50,59 +52,61 @@ export function ChallengeOverviewScreen(): VNode { const errors = !atLeastThereIsOnePolicySolved ? "Solve one policy before proceeding" : undefined; return ( - {!policies.length ?

    + {!policies.length ?

    No policies found, try with another version of the secret -

    : (policies.length === 1 ?

    +

    : (policies.length === 1 ?

    One policy found for this secret. You need to solve all the challenges in order to recover your secret. -

    :

    +

    :

    We have found {policies.length} polices. You need to solve all the challenges from one policy in order to recover your secret.

    )} - {policiesWithInfo.map((row, i) => { - const tableBody = row.challenges.map(({ info, uuid }) => { + {policiesWithInfo.map((policy, policy_index) => { + const tableBody = policy.challenges.map(({ info, uuid }) => { + const isFree = !info.cost || info.cost.endsWith(':0') + const method = authMethods[info.type as KnownAuthMethods] return ( -
    - - - - - - + {info.feedback?.state === "solved" ? ( + Solved + ) : null} + + ); }) + + const policyName = policy.challenges.map(x => x.info.type).join(" + "); + const opa = !atLeastThereIsOnePolicySolved ? undefined : ( policy.isPolicySolved ? undefined : '0.6') return ( -
    - Policy #{i + 1} - {row.challenges.length === 0 &&

    - This policy doesn't have challenges +

    +

    + Policy #{policy_index + 1}: {policyName} +

    + {policy.challenges.length === 0 &&

    + This policy doesn't have challenges.

    } - {row.challenges.length === 1 &&

    - This policy just have one challenge to be solved + {policy.challenges.length === 1 &&

    + This policy just have one challenge.

    } - {row.challenges.length > 1 &&

    - This policy have {row.challenges.length} challenges + {policy.challenges.length > 1 &&

    + This policy have {policy.challenges.length} challenges.

    } -
    {info.type} - {info.instructions} - {info.feedback?.state ?? "unknown"}{info.cost} - {info.feedback?.state !== "solved" ? ( - reducer.transition("select_challenge", { uuid })}> - Solve +
    - - - - - - - - - - {tableBody} - -
    Challenge typeDescriptionStatusCost
    + {tableBody}
    ); })} diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx index 6d5220a05..673f215e2 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -2,7 +2,7 @@ import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; -import { authMethods, KnownAuthMethods } from "./authMethodSetup"; +import { authMethods, KnownAuthMethods } from "./authMethod"; export function ReviewPoliciesScreen(): VNode { const reducer = useAnastasisContext() diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx new file mode 100644 index 000000000..e178a4955 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/email', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'email' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Email to sebasjm@email.com ', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Email to sebasjm@email.com', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Email to someone@sebasjm.com', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx new file mode 100644 index 000000000..1a6be1b61 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx @@ -0,0 +1,62 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; +import { EmailInput } from "../../../components/fields/EmailInput"; + +const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + +export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { + const [email, setEmail] = useState(""); + const addEmailAuth = (): void => addAuthMethod({ + authentication_method: { + type: "email", + instructions: `Email to ${email}`, + challenge: encodeCrock(stringToBytes(email)), + }, + }); + const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined + const errors = !email ? 'Add your email' : emailError + + return ( + +

    + 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. +

    +
    + +
    + {configured.length > 0 &&
    +
    + Your emails: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx new file mode 100644 index 000000000..71f618646 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx @@ -0,0 +1,65 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/IBAN', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'iban' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', + remove: () => null + }] +}); +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Wire transfer from QWEASD123123 with holder Javier', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', + remove: () => null + }] +},); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx new file mode 100644 index 000000000..c9edbfa07 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + canonicalJson, + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { TextInput } from "../../../components/fields/TextInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [name, setName] = useState(""); + const [account, setAccount] = useState(""); + const addIbanAuth = (): void => addAuthMethod({ + authentication_method: { + type: "iban", + instructions: `Wire transfer from ${account} with holder ${name}`, + challenge: encodeCrock(stringToBytes(canonicalJson({ + name, account + }))), + }, + }); + const errors = !name ? 'Add an account name' : ( + !account ? 'Add an account IBAN number' : undefined + ) + return ( + +

    + For bank transfer authentication, you need to provide a bank + account (account holder name and IBAN). When recovering your + secret, you will be asked to pay the recovery fee via bank + transfer from the account you provided here. +

    +
    + + +
    + {configured.length > 0 &&
    +
    + Your bank accounts: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx new file mode 100644 index 000000000..0f1c17495 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/Post', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'post' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Letter to address in postal code QWE456', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Letter to address in postal code QWE456', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Letter to address in postal code ABC123', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx new file mode 100644 index 000000000..bfeaaa832 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx @@ -0,0 +1,102 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + canonicalJson, encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { TextInput } from "../../../components/fields/TextInput"; +import { AnastasisClientFrame } from ".."; + +export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [fullName, setFullName] = useState(""); + const [street, setStreet] = useState(""); + const [city, setCity] = useState(""); + const [postcode, setPostcode] = useState(""); + const [country, setCountry] = useState(""); + + const addPostAuth = () => { + const challengeJson = { + full_name: fullName, + street, + city, + postcode, + country, + }; + addAuthMethod({ + authentication_method: { + type: "post", + instructions: `Letter to address in postal code ${postcode}`, + challenge: encodeCrock(stringToBytes(canonicalJson(challengeJson))), + }, + }); + }; + + const errors = !fullName ? 'The full name is missing' : ( + !street ? 'The street is missing' : ( + !city ? 'The city is missing' : ( + !postcode ? 'The postcode is missing' : ( + !country ? 'The country is missing' : undefined + ) + ) + ) + ) + return ( + +

    + For postal letter authentication, you need to provide a postal + address. When recovering your secret, you will be asked to enter a + code that you will receive in a letter to that address. +

    +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    + + {configured.length > 0 &&
    +
    + Your postal code: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    +
    } +
    + + + + +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx new file mode 100644 index 000000000..3ba4a84ca --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/Question', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'question' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Is integer factorization polynomial? (non-quantum computer)', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Does P equal NP?', + remove: () => null + },{ + challenge: 'asd', + type, + instructions: 'Are continuous groups automatically differential groups?', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx new file mode 100644 index 000000000..eab800e35 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx @@ -0,0 +1,70 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; + +export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { + const [questionText, setQuestionText] = useState(""); + const [answerText, setAnswerText] = useState(""); + const addQuestionAuth = (): void => addAuthMethod({ + authentication_method: { + type: "question", + instructions: questionText, + challenge: encodeCrock(stringToBytes(answerText)), + }, + }); + + const errors = !questionText ? "Add your security question" : ( + !answerText ? 'Add the answer to your question' : undefined + ) + return ( + +
    +

    + 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. +

    +
    + +
    +
    + +
    + + {configured.length > 0 &&
    +
    + Your security questions: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx new file mode 100644 index 000000000..ae8297ef7 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/Sms', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'sms' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'SMS to +11-1234-2345', + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'SMS to +11-1234-2345', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'SMS to +11-5555-2345', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx new file mode 100644 index 000000000..9e85af2b2 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { Fragment, h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { NumberInput } from "../../../components/fields/NumberInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [mobileNumber, setMobileNumber] = useState(""); + const addSmsAuth = (): void => { + addAuthMethod({ + authentication_method: { + type: "sms", + instructions: `SMS to ${mobileNumber}`, + challenge: encodeCrock(stringToBytes(mobileNumber)), + }, + }); + }; + const inputRef = useRef(null); + useLayoutEffect(() => { + inputRef.current?.focus(); + }, []); + const errors = !mobileNumber ? 'Add a mobile number' : undefined + return ( + +
    +

    + 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. +

    +
    + +
    + {configured.length > 0 &&
    +
    + Your mobile numbers: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx new file mode 100644 index 000000000..4e46b600e --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx @@ -0,0 +1,64 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; + + +export default { + title: 'Pages/backup/authMethods/TOTP', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'totp' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Enter 8 digits code for "Anastasis"', + remove: () => null + }] +}); +export const WithMoreExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: 'Enter 8 digits code for "Anastasis1"', + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: 'Enter 8 digits code for "Anastasis2"', + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx new file mode 100644 index 000000000..fd0bd0224 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx @@ -0,0 +1,81 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useMemo, useState } from "preact/hooks"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; +import { TextInput } from "../../../components/fields/TextInput"; +import { QR } from "../../../components/QR"; +import { base32enc, computeTOTPandCheck } from "./totp"; + +export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { + const [name, setName] = useState("anastasis"); + const [test, setTest] = useState(""); + const digits = 8 + const secretKey = useMemo(() => { + const array = new Uint8Array(32) + return window.crypto.getRandomValues(array) + }, []) + const secret32 = base32enc(secretKey); + const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` + + const addTotpAuth = (): void => addAuthMethod({ + authentication_method: { + type: "totp", + instructions: `Enter ${digits} digits code for "${name}"`, + challenge: encodeCrock(stringToBytes(totpURL)), + }, + }); + + const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10)); + + const errors = !name ? 'The TOTP name is missing' : ( + !testCodeMatches ? 'The test code doesnt match' : undefined + ); + return ( + +

    + For Time-based One-Time Password (TOTP) authentication, you need to set + a name for the TOTP secret. Then, you must scan the generated QR code + with your TOTP App to import the TOTP secret into your TOTP App. +

    +
    + +
    +
    + +
    +

    + After scanning the code with your TOTP App, test it in the input below. +

    + + {configured.length > 0 &&
    +
    + Your TOTP numbers: +
    + {configured.map((c, i) => { + return
    +

    {c.instructions}

    +
    +
    + })} +
    } +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx new file mode 100644 index 000000000..3c4c7bf39 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx @@ -0,0 +1,66 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createExample, reducerStatesExample } from '../../../utils'; +import { authMethods as TestedComponent, KnownAuthMethods } from './index'; +import logoImage from '../../../assets/logo.jpeg' + +export default { + title: 'Pages/backup/authMethods/Video', + component: TestedComponent, + args: { + order: 5, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +const type: KnownAuthMethods = 'video' + +export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [] +}); + +export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: logoImage, + remove: () => null + }] +}); + +export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { + configured: [{ + challenge: 'qwe', + type, + instructions: logoImage, + remove: () => null + },{ + challenge: 'qwe', + type, + instructions: logoImage, + remove: () => null + }] +}); diff --git a/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx new file mode 100644 index 000000000..8be999b3f --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; +import { ImageInput } from "../../../components/fields/ImageInput"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; +import { AnastasisClientFrame } from "../index"; + +export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { + const [image, setImage] = useState(""); + const addVideoAuth = (): void => { + addAuthMethod({ + authentication_method: { + type: "video", + instructions: 'Join a video call', + challenge: encodeCrock(stringToBytes(image)), + }, + }) + }; + return ( + +

    + For video identification, you need to provide a passport-style + photograph. When recovering your secret, you will be asked to join a + video call. During that call, a human will use the photograph to + verify your identity. +

    +
    + +
    + {configured.length > 0 &&
    +
    + Your photographs: +
    + {configured.map((c, i) => { + return
    + +
    +
    + })} +
    } +
    +
    + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx new file mode 100644 index 000000000..1e1d7bc03 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -0,0 +1,68 @@ +import { h, VNode } from "preact"; +import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; + +import { AuthMethodEmailSetup as EmailScreen } from "./AuthMethodEmailSetup"; +import { AuthMethodIbanSetup as IbanScreen } from "./AuthMethodIbanSetup"; +import { AuthMethodPostSetup as PostalScreen } from "./AuthMethodPostSetup"; +import { AuthMethodQuestionSetup as QuestionScreen } from "./AuthMethodQuestionSetup"; +import { AuthMethodSmsSetup as SmsScreen } from "./AuthMethodSmsSetup"; +import { AuthMethodTotpSetup as TotpScreen } from "./AuthMethodTotpSetup"; +import { AuthMethodVideoSetup as VideScreen } from "./AuthMethodVideoSetup"; +import postalIcon from '../../../assets/icons/auth_method/postal.svg'; +import questionIcon from '../../../assets/icons/auth_method/question.svg'; +import smsIcon from '../../../assets/icons/auth_method/sms.svg'; +import videoIcon from '../../../assets/icons/auth_method/video.svg'; + +interface AuthMethodConfiguration { + icon: VNode; + label: string; + screen: (props: AuthMethodSetupProps) => VNode; +} +export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; + +type KnowMethodConfig = { + [name in KnownAuthMethods]: AuthMethodConfiguration; +}; + +export const authMethods: KnowMethodConfig = { + question: { + icon: , + label: "Question", + screen: QuestionScreen + }, + sms: { + icon: , + label: "SMS", + screen: SmsScreen + }, + email: { + icon: , + label: "Email", + screen: EmailScreen + + }, + iban: { + icon: , + label: "IBAN", + screen: IbanScreen + + }, + post: { + icon: , + label: "Physical mail", + screen: PostalScreen + + }, + totp: { + icon: , + label: "TOTP", + screen: TotpScreen + + }, + video: { + icon: , + label: "Video", + screen: VideScreen + + } +} \ No newline at end of file diff --git a/packages/anastasis-webui/src/pages/home/authMethod/totp.ts b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts new file mode 100644 index 000000000..0bc3feaf8 --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/authMethod/totp.ts @@ -0,0 +1,56 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import jssha from 'jssha' + +const SEARCH_RANGE = 16 +const timeStep = 30 + +export function computeTOTPandCheck(secretKey: Uint8Array, digits: number, code: number): boolean { + const now = new Date().getTime() + const epoch = Math.floor(Math.round(now / 1000.0) / timeStep); + + for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) { + const movingFactor = (epoch + ms).toString(16).padStart(16, "0"); + + const hmacSha = new jssha('SHA-1', 'HEX', { hmacKey: { value: secretKey, format: 'UINT8ARRAY' } }); + hmacSha.update(movingFactor); + const hmac_text = hmacSha.getHMAC('UINT8ARRAY'); + + const offset = (hmac_text[hmac_text.length - 1] & 0xf) + + const otp = (( + (hmac_text[offset + 0] << 24) + + (hmac_text[offset + 1] << 16) + + (hmac_text[offset + 2] << 8) + + (hmac_text[offset + 3]) + ) & 0x7fffffff) % Math.pow(10, digits) + + if (otp == code) return true + } + return false +} + +const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split('') +export function base32enc(buffer: Uint8Array): string { + let rpos = 0 + let bits = 0 + let vbit = 0 + + let result = "" + while ((rpos < buffer.length) || (vbit > 0)) { + if ((rpos < buffer.length) && (vbit < 5)) { + bits = (bits << 8) | buffer[rpos++]; + vbit += 8; + } + if (vbit < 5) { + bits <<= (5 - vbit); + vbit = 5; + } + result += encTable__[(bits >> (vbit - 5)) & 31]; + vbit -= 5; + } + return result +} + +// const array = new Uint8Array(256) +// const secretKey = window.crypto.getRandomValues(array) +// console.log(base32enc(secretKey)) diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx deleted file mode 100644 index e178a4955..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.stories.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; - - -export default { - title: 'Pages/backup/authMethods/email', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'email' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); - -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Email to sebasjm@email.com ', - remove: () => null - }] -}); - -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Email to sebasjm@email.com', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Email to someone@sebasjm.com', - remove: () => null - }] -}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx deleted file mode 100644 index e8cee9cb4..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodEmailSetup.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; -import { TextInput } from "../../../components/fields/TextInput"; -import { EmailInput } from "../../../components/fields/EmailInput"; - -const EMAIL_PATTERN = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - -export function AuthMethodEmailSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { - const [email, setEmail] = useState(""); - const addEmailAuth = (): void => addAuthMethod({ - authentication_method: { - type: "email", - instructions: `Email to ${email}`, - challenge: encodeCrock(stringToBytes(email)), - }, - }); - const emailError = !EMAIL_PATTERN.test(email) ? 'Email address is not valid' : undefined - const errors = !email ? 'Add your email' : emailError - - return ( - -

    - 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. -

    -
    - -
    - {configured.length > 0 &&
    -
    - Your emails: -
    - {configured.map((c, i) => { - return
    -

    {c.instructions}

    -
    -
    - })} -
    } -
    -
    - - - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx deleted file mode 100644 index 71f618646..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.stories.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; - - -export default { - title: 'Pages/backup/authMethods/IBAN', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'iban' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); - -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', - remove: () => null - }] -}); -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Javier', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Wire transfer from QWEASD123123 with holder Sebastian', - remove: () => null - }] -},); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx deleted file mode 100644 index c9edbfa07..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodIbanSetup.tsx +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - canonicalJson, - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { TextInput } from "../../../components/fields/TextInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; - -export function AuthMethodIbanSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { - const [name, setName] = useState(""); - const [account, setAccount] = useState(""); - const addIbanAuth = (): void => addAuthMethod({ - authentication_method: { - type: "iban", - instructions: `Wire transfer from ${account} with holder ${name}`, - challenge: encodeCrock(stringToBytes(canonicalJson({ - name, account - }))), - }, - }); - const errors = !name ? 'Add an account name' : ( - !account ? 'Add an account IBAN number' : undefined - ) - return ( - -

    - For bank transfer authentication, you need to provide a bank - account (account holder name and IBAN). When recovering your - secret, you will be asked to pay the recovery fee via bank - transfer from the account you provided here. -

    -
    - - -
    - {configured.length > 0 &&
    -
    - Your bank accounts: -
    - {configured.map((c, i) => { - return
    -

    {c.instructions}

    -
    -
    - })} -
    } -
    -
    - - - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx deleted file mode 100644 index 0f1c17495..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.stories.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; - - -export default { - title: 'Pages/backup/authMethods/Post', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'post' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); - -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code QWE456', - remove: () => null - }] -}); - -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code QWE456', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Letter to address in postal code ABC123', - remove: () => null - }] -}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx deleted file mode 100644 index bfeaaa832..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodPostSetup.tsx +++ /dev/null @@ -1,102 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - canonicalJson, encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { TextInput } from "../../../components/fields/TextInput"; -import { AnastasisClientFrame } from ".."; - -export function AuthMethodPostSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { - const [fullName, setFullName] = useState(""); - const [street, setStreet] = useState(""); - const [city, setCity] = useState(""); - const [postcode, setPostcode] = useState(""); - const [country, setCountry] = useState(""); - - const addPostAuth = () => { - const challengeJson = { - full_name: fullName, - street, - city, - postcode, - country, - }; - addAuthMethod({ - authentication_method: { - type: "post", - instructions: `Letter to address in postal code ${postcode}`, - challenge: encodeCrock(stringToBytes(canonicalJson(challengeJson))), - }, - }); - }; - - const errors = !fullName ? 'The full name is missing' : ( - !street ? 'The street is missing' : ( - !city ? 'The city is missing' : ( - !postcode ? 'The postcode is missing' : ( - !country ? 'The country is missing' : undefined - ) - ) - ) - ) - return ( - -

    - For postal letter authentication, you need to provide a postal - address. When recovering your secret, you will be asked to enter a - code that you will receive in a letter to that address. -

    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    - - {configured.length > 0 &&
    -
    - Your postal code: -
    - {configured.map((c, i) => { - return
    -

    {c.instructions}

    -
    -
    - })} -
    -
    } -
    - - - - -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx deleted file mode 100644 index 3ba4a84ca..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.stories.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; - - -export default { - title: 'Pages/backup/authMethods/Question', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'question' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); - -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Is integer factorization polynomial? (non-quantum computer)', - remove: () => null - }] -}); - -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Does P equal NP?', - remove: () => null - },{ - challenge: 'asd', - type, - instructions: 'Are continuous groups automatically differential groups?', - remove: () => null - }] -}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx deleted file mode 100644 index eab800e35..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodQuestionSetup.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; -import { TextInput } from "../../../components/fields/TextInput"; - -export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: AuthMethodSetupProps): VNode { - const [questionText, setQuestionText] = useState(""); - const [answerText, setAnswerText] = useState(""); - const addQuestionAuth = (): void => addAuthMethod({ - authentication_method: { - type: "question", - instructions: questionText, - challenge: encodeCrock(stringToBytes(answerText)), - }, - }); - - const errors = !questionText ? "Add your security question" : ( - !answerText ? 'Add the answer to your question' : undefined - ) - return ( - -
    -

    - 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. -

    -
    - -
    -
    - -
    - - {configured.length > 0 &&
    -
    - Your security questions: -
    - {configured.map((c, i) => { - return
    -

    {c.instructions}

    -
    -
    - })} -
    } -
    - - - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx deleted file mode 100644 index ae8297ef7..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.stories.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; - - -export default { - title: 'Pages/backup/authMethods/Sms', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'sms' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); - -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-1234-2345', - remove: () => null - }] -}); - -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-1234-2345', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'SMS to +11-5555-2345', - remove: () => null - }] -}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx deleted file mode 100644 index 9e85af2b2..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodSmsSetup.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { Fragment, h, VNode } from "preact"; -import { useLayoutEffect, useRef, useState } from "preact/hooks"; -import { NumberInput } from "../../../components/fields/NumberInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; - -export function AuthMethodSmsSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { - const [mobileNumber, setMobileNumber] = useState(""); - const addSmsAuth = (): void => { - addAuthMethod({ - authentication_method: { - type: "sms", - instructions: `SMS to ${mobileNumber}`, - challenge: encodeCrock(stringToBytes(mobileNumber)), - }, - }); - }; - const inputRef = useRef(null); - useLayoutEffect(() => { - inputRef.current?.focus(); - }, []); - const errors = !mobileNumber ? 'Add a mobile number' : undefined - return ( - -
    -

    - 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. -

    -
    - -
    - {configured.length > 0 &&
    -
    - Your mobile numbers: -
    - {configured.map((c, i) => { - return
    -

    {c.instructions}

    -
    -
    - })} -
    } -
    - - - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx deleted file mode 100644 index 4e46b600e..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; - - -export default { - title: 'Pages/backup/authMethods/TOTP', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'totp' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis"', - remove: () => null - }] -}); -export const WithMoreExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis1"', - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: 'Enter 8 digits code for "Anastasis2"', - remove: () => null - }] -}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx deleted file mode 100644 index db656e630..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useMemo, useState } from "preact/hooks"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; -import { TextInput } from "../../../components/fields/TextInput"; -import { QR } from "../../../components/QR"; -import { base32enc, computeTOTPandCheck } from "./totp"; - -export function AuthMethodTotpSetup({ addAuthMethod, cancel, configured }: AuthMethodSetupProps): VNode { - const [name, setName] = useState("anastasis"); - const [test, setTest] = useState(""); - const digits = 8 - const secretKey = useMemo(() => { - const array = new Uint8Array(32) - return window.crypto.getRandomValues(array) - }, []) - const secret32 = base32enc(secretKey); - const totpURL = `otpauth://totp/${name}?digits=${digits}&secret=${secret32}` - - const addTotpAuth = (): void => addAuthMethod({ - authentication_method: { - type: "totp", - instructions: `Enter ${digits} digits code for ${name}`, - challenge: encodeCrock(stringToBytes(totpURL)), - }, - }); - - const testCodeMatches = computeTOTPandCheck(secretKey, 8, parseInt(test, 10)); - - const errors = !name ? 'The TOTP name is missing' : ( - !testCodeMatches ? 'The test code doesnt match' : undefined - ); - return ( - -

    - For Time-based One-Time Password (TOTP) authentication, you need to set - a name for the TOTP secret. Then, you must scan the generated QR code - with your TOTP App to import the TOTP secret into your TOTP App. -

    -
    - -
    -
    - -
    -

    - After scanning the code with your TOTP App, test it in the input below. -

    - - {configured.length > 0 &&
    -
    - Your TOTP numbers: -
    - {configured.map((c, i) => { - return
    -

    {c.instructions}

    -
    -
    - })} -
    } -
    -
    - - - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx deleted file mode 100644 index 3c4c7bf39..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.stories.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 - */ - -/** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { createExample, reducerStatesExample } from '../../../utils'; -import { authMethods as TestedComponent, KnownAuthMethods } from './index'; -import logoImage from '../../../assets/logo.jpeg' - -export default { - title: 'Pages/backup/authMethods/Video', - component: TestedComponent, - args: { - order: 5, - }, - argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, - }, -}; - -const type: KnownAuthMethods = 'video' - -export const Empty = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [] -}); - -export const WithOneExample = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - }] -}); - -export const WithMoreExamples = createExample(TestedComponent[type].screen, reducerStatesExample.authEditing, { - configured: [{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - },{ - challenge: 'qwe', - type, - instructions: logoImage, - remove: () => null - }] -}); diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx deleted file mode 100644 index d292a9d24..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodVideoSetup.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { - encodeCrock, - stringToBytes -} from "@gnu-taler/taler-util"; -import { h, VNode } from "preact"; -import { useState } from "preact/hooks"; -import { ImageInput } from "../../../components/fields/ImageInput"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; -import { AnastasisClientFrame } from "../index"; - -export function AuthMethodVideoSetup({cancel, addAuthMethod, configured}: AuthMethodSetupProps): VNode { - const [image, setImage] = useState(""); - const addVideoAuth = (): void => { - addAuthMethod({ - authentication_method: { - type: "video", - instructions: image, - challenge: encodeCrock(stringToBytes(image)), - }, - }) - }; - return ( - -

    - For video identification, you need to provide a passport-style - photograph. When recovering your secret, you will be asked to join a - video call. During that call, a human will use the photograph to - verify your identity. -

    -
    - -
    - {configured.length > 0 &&
    -
    - Your photographs: -
    - {configured.map((c, i) => { - return
    - -
    -
    - })} -
    } -
    -
    - - -
    -
    -
    - ); -} diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx b/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx deleted file mode 100644 index 1e1d7bc03..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { h, VNode } from "preact"; -import { AuthMethodSetupProps } from "../AuthenticationEditorScreen"; - -import { AuthMethodEmailSetup as EmailScreen } from "./AuthMethodEmailSetup"; -import { AuthMethodIbanSetup as IbanScreen } from "./AuthMethodIbanSetup"; -import { AuthMethodPostSetup as PostalScreen } from "./AuthMethodPostSetup"; -import { AuthMethodQuestionSetup as QuestionScreen } from "./AuthMethodQuestionSetup"; -import { AuthMethodSmsSetup as SmsScreen } from "./AuthMethodSmsSetup"; -import { AuthMethodTotpSetup as TotpScreen } from "./AuthMethodTotpSetup"; -import { AuthMethodVideoSetup as VideScreen } from "./AuthMethodVideoSetup"; -import postalIcon from '../../../assets/icons/auth_method/postal.svg'; -import questionIcon from '../../../assets/icons/auth_method/question.svg'; -import smsIcon from '../../../assets/icons/auth_method/sms.svg'; -import videoIcon from '../../../assets/icons/auth_method/video.svg'; - -interface AuthMethodConfiguration { - icon: VNode; - label: string; - screen: (props: AuthMethodSetupProps) => VNode; -} -export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; - -type KnowMethodConfig = { - [name in KnownAuthMethods]: AuthMethodConfiguration; -}; - -export const authMethods: KnowMethodConfig = { - question: { - icon: , - label: "Question", - screen: QuestionScreen - }, - sms: { - icon: , - label: "SMS", - screen: SmsScreen - }, - email: { - icon: , - label: "Email", - screen: EmailScreen - - }, - iban: { - icon: , - label: "IBAN", - screen: IbanScreen - - }, - post: { - icon: , - label: "Physical mail", - screen: PostalScreen - - }, - totp: { - icon: , - label: "TOTP", - screen: TotpScreen - - }, - video: { - icon: , - label: "Video", - screen: VideScreen - - } -} \ No newline at end of file diff --git a/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts b/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts deleted file mode 100644 index 0bc3feaf8..000000000 --- a/packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import jssha from 'jssha' - -const SEARCH_RANGE = 16 -const timeStep = 30 - -export function computeTOTPandCheck(secretKey: Uint8Array, digits: number, code: number): boolean { - const now = new Date().getTime() - const epoch = Math.floor(Math.round(now / 1000.0) / timeStep); - - for (let ms = -SEARCH_RANGE; ms < SEARCH_RANGE; ms++) { - const movingFactor = (epoch + ms).toString(16).padStart(16, "0"); - - const hmacSha = new jssha('SHA-1', 'HEX', { hmacKey: { value: secretKey, format: 'UINT8ARRAY' } }); - hmacSha.update(movingFactor); - const hmac_text = hmacSha.getHMAC('UINT8ARRAY'); - - const offset = (hmac_text[hmac_text.length - 1] & 0xf) - - const otp = (( - (hmac_text[offset + 0] << 24) + - (hmac_text[offset + 1] << 16) + - (hmac_text[offset + 2] << 8) + - (hmac_text[offset + 3]) - ) & 0x7fffffff) % Math.pow(10, digits) - - if (otp == code) return true - } - return false -} - -const encTable__ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".split('') -export function base32enc(buffer: Uint8Array): string { - let rpos = 0 - let bits = 0 - let vbit = 0 - - let result = "" - while ((rpos < buffer.length) || (vbit > 0)) { - if ((rpos < buffer.length) && (vbit < 5)) { - bits = (bits << 8) | buffer[rpos++]; - vbit += 8; - } - if (vbit < 5) { - bits <<= (5 - vbit); - vbit = 5; - } - result += encTable__[(bits >> (vbit - 5)) & 31]; - vbit -= 5; - } - return result -} - -// const array = new Uint8Array(256) -// const secretKey = window.crypto.getRandomValues(array) -// console.log(base32enc(secretKey)) -- cgit v1.2.3 From 7d24d2254b49010eb0e6e6af54a0f381cfdc4b53 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 3 Nov 2021 18:26:57 +0100 Subject: anastasis-webui: make it compile again --- .../pages/home/ChallengeOverviewScreen.stories.tsx | 339 ++++++++++----------- .../src/pages/home/ChallengeOverviewScreen.tsx | 157 ++++++---- .../src/pages/home/CountrySelectionScreen.tsx | 2 +- .../src/pages/home/TruthsPayingScreen.tsx | 2 +- 4 files changed, 264 insertions(+), 236 deletions(-) (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx index a89b5640c..48115c798 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.stories.tsx @@ -16,218 +16,201 @@ */ /** -* -* @author Sebastian Javier Marchano (sebasjm) -*/ - -import { RecoveryStates, ReducerState } from 'anastasis-core'; -import { createExample, reducerStatesExample } from '../../utils'; -import { ChallengeOverviewScreen as TestedComponent } from './ChallengeOverviewScreen'; + * + * @author Sebastian Javier Marchano (sebasjm) + */ +import { RecoveryStates, ReducerState } from "anastasis-core"; +import { createExample, reducerStatesExample } from "../../utils"; +import { ChallengeOverviewScreen as TestedComponent } from "./ChallengeOverviewScreen"; export default { - title: 'Pages/recovery/ChallengeOverviewScreen', + title: "Pages/recovery/ChallengeOverviewScreen", component: TestedComponent, args: { order: 5, }, argTypes: { - onUpdate: { action: 'onUpdate' }, - onBack: { action: 'onBack' }, + onUpdate: { action: "onUpdate" }, + onBack: { action: "onBack" }, }, }; export const OneUnsolvedPolicy = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '1' }]], - challenges: [{ - cost: 'USD:1', - instructions: 'just go for it', - type: 'question', - uuid: '1', - }] + policies: [[{ uuid: "1" }]], + challenges: [ + { + cost: "USD:1", + instructions: "just go for it", + type: "question", + uuid: "1", + }, + ], }, } as ReducerState); export const SomePoliciesOneSolved = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '1' }, { uuid: '2' }], [{ uuid: 'uuid-3' }]], - challenges: [{ - cost: 'USD:1', - instructions: 'this question cost 1 USD', - type: 'question', - uuid: '1', - }, { - cost: 'USD:0', - instructions: 'answering this question is free', - type: 'question', - uuid: '2', - }, { - cost: 'USD:1', - instructions: 'this question is already answered', - type: 'question', - uuid: 'uuid-3', - }] + policies: [[{ uuid: "1" }, { uuid: "2" }], [{ uuid: "uuid-3" }]], + challenges: [ + { + cost: "USD:1", + instructions: "this question cost 1 USD", + type: "question", + uuid: "1", + }, + { + cost: "USD:0", + instructions: "answering this question is free", + type: "question", + uuid: "2", + }, + { + cost: "USD:1", + instructions: "this question is already answered", + type: "question", + uuid: "uuid-3", + }, + ], }, challenge_feedback: { - 'uuid-3': { - state: 'solved' - } + "uuid-3": { + state: "solved", + }, }, } as ReducerState); export const OneBadConfiguredPolicy = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[{ uuid: '1' }, { uuid: '2' }]], - challenges: [{ - cost: 'USD:1', - instructions: 'this policy has a missing uuid (the other auth method)', - type: 'totp', - uuid: '1', - }], + policies: [[{ uuid: "1" }, { uuid: "2" }]], + challenges: [ + { + cost: "USD:1", + instructions: "this policy has a missing uuid (the other auth method)", + type: "totp", + uuid: "1", + }, + ], }, } as ReducerState); export const OnePolicyWithAllTheChallenges = createExample(TestedComponent, { ...reducerStatesExample.challengeSelecting, recovery_information: { - policies: [[ - { uuid: '1' }, - { uuid: '2' }, - { uuid: '3' }, - { uuid: '4' }, - { uuid: '5' }, - { uuid: '6' }, - { uuid: '7' }, - { uuid: '8' }, - ]], - challenges: [{ - cost: 'USD:1', - instructions: 'Does P equals NP?', - type: 'question', - uuid: '1', - },{ - cost: 'USD:1', - instructions: 'SMS to 555-555', - type: 'sms', - uuid: '2', - },{ - cost: 'USD:1', - instructions: 'Email to qwe@asd.com', - type: 'email', - uuid: '3', - },{ - cost: 'USD:1', - instructions: 'Enter 8 digits code for "Anastasis"', - type: 'totp', - uuid: '4', - },{// - cost: 'USD:0', - instructions: 'Wire transfer from ASDXCVQWE123123 with holder Florian', - type: 'iban', - uuid: '5', - },{ - cost: 'USD:1', - instructions: 'Join a video call', - type: 'video',//Enter 8 digits code for "Anastasis" - uuid: '7', - },{ - },{ - cost: 'USD:1', - instructions: 'Letter to address in postal code DE123123', - type: 'post',//Enter 8 digits code for "Anastasis" - uuid: '8', - },{ - cost: 'USD:1', - instructions: 'instruction for an unknown type of challenge', - type: 'new-type-of-challenge', - uuid: '6', - }], + policies: [ + [ + { uuid: "1" }, + { uuid: "2" }, + { uuid: "3" }, + { uuid: "4" }, + { uuid: "5" }, + { uuid: "6" }, + { uuid: "7" }, + { uuid: "8" }, + ], + ], + challenges: [ + { + cost: "USD:1", + instructions: "Does P equals NP?", + type: "question", + uuid: "1", + }, + { + cost: "USD:1", + instructions: "SMS to 555-555", + type: "sms", + uuid: "2", + }, + { + cost: "USD:1", + instructions: "Email to qwe@asd.com", + type: "email", + uuid: "3", + }, + { + cost: "USD:1", + instructions: 'Enter 8 digits code for "Anastasis"', + type: "totp", + uuid: "4", + }, + { + // + cost: "USD:0", + instructions: "Wire transfer from ASDXCVQWE123123 with holder Florian", + type: "iban", + uuid: "5", + }, + { + cost: "USD:1", + instructions: "Join a video call", + type: "video", //Enter 8 digits code for "Anastasis" + uuid: "7", + }, + {}, + { + cost: "USD:1", + instructions: "Letter to address in postal code DE123123", + type: "post", //Enter 8 digits code for "Anastasis" + uuid: "8", + }, + { + cost: "USD:1", + instructions: "instruction for an unknown type of challenge", + type: "new-type-of-challenge", + uuid: "6", + }, + ], }, } as ReducerState); - -export const OnePolicyWithAllTheChallengesInDifferentState = createExample(TestedComponent, { - ...reducerStatesExample.challengeSelecting, - recovery_information: { - policies: [[ - { uuid: '1' }, - { uuid: '2' }, - { uuid: '3' }, - { uuid: '4' }, - { uuid: '5' }, - { uuid: '6' }, - { uuid: '7' }, - { uuid: '8' }, - { uuid: '9' }, - { uuid: '10' }, - ]], - challenges: [{ - cost: 'USD:1', - instructions: 'in state "solved"', - type: 'question', - uuid: '1', - },{ - cost: 'USD:1', - instructions: 'in state "hint"', - type: 'question', - uuid: '2', - },{ - cost: 'USD:1', - instructions: 'in state "details"', - type: 'question', - uuid: '3', - },{ - cost: 'USD:1', - instructions: 'in state "body"', - type: 'question', - uuid: '4', - },{ - cost: 'USD:1', - instructions: 'in state "redirect"', - type: 'question', - uuid: '5', - },{ - cost: 'USD:1', - instructions: 'in state "server-failure"', - type: 'question', - uuid: '6', - },{ - cost: 'USD:1', - instructions: 'in state "truth-unknown"', - type: 'question', - uuid: '7', - },{ - cost: 'USD:1', - instructions: 'in state "rate-limit-exceeded"', - type: 'question', - uuid: '8', - },{ - cost: 'USD:1', - instructions: 'in state "authentication-timeout"', - type: 'question', - uuid: '9', - },{ - cost: 'USD:1', - instructions: 'in state "external-instructions"', - type: 'question', - uuid: '10', - }], - }, - challenge_feedback: { - 1: { state: 'solved' }, - 2: { state: 'hint' }, - 3: { state: 'details' }, - 4: { state: 'body' }, - 5: { state: 'redirect' }, - 6: { state: 'server-failure' }, - 7: { state: 'truth-unknown' }, - 8: { state: 'rate-limit-exceeded' }, - 9: { state: 'authentication-timeout' }, - 10: { state: 'external-instructions' }, - } -} as ReducerState); -export const NoPolicies = createExample(TestedComponent, reducerStatesExample.challengeSelecting); +export const OnePolicyWithAllTheChallengesInDifferentState = createExample( + TestedComponent, + { + ...reducerStatesExample.challengeSelecting, + recovery_state: RecoveryStates.ChallengeSelecting, + recovery_information: { + policies: [ + [ + { uuid: "1" }, + { uuid: "2" }, + { uuid: "3" }, + { uuid: "4" }, + { uuid: "5" }, + { uuid: "6" }, + { uuid: "7" }, + { uuid: "8" }, + { uuid: "9" }, + { uuid: "10" }, + ], + ], + challenges: [ + { + cost: "USD:1", + instructions: 'in state "solved"', + type: "question", + uuid: "1", + }, + { + cost: "USD:1", + instructions: 'in state "message"', + type: "question", + uuid: "2", + }, + ], + }, + challenge_feedback: { + 1: { state: "solved" }, + 2: { state: "message", message: "Security question was not solved correctly" }, + // FIXME: add missing feedback states here! + }, + } as ReducerState, +); +export const NoPolicies = createExample( + TestedComponent, + reducerStatesExample.challengeSelecting, +); diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index 7b9b060ce..c63f19eb6 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -1,23 +1,29 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { ChallengeFeedback } from "anastasis-core"; +import { ChallengeFeedback, ChallengeFeedbackStatus } from "anastasis-core"; import { h, VNode } from "preact"; import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; import { authMethods, KnownAuthMethods } from "./authMethod"; export function ChallengeOverviewScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); if (!reducer) { - return
    no reducer in context
    + return
    no reducer in context
    ; } - if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return
    invalid state
    + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return
    invalid state
    ; } - const policies = reducer.currentReducerState.recovery_information?.policies ?? []; - const knownChallengesArray = reducer.currentReducerState.recovery_information?.challenges ?? []; - const challengeFeedback = reducer.currentReducerState?.challenge_feedback ?? {}; + const policies = + reducer.currentReducerState.recovery_information?.policies ?? []; + const knownChallengesArray = + reducer.currentReducerState.recovery_information?.challenges ?? []; + const challengeFeedback = + reducer.currentReducerState?.challenge_feedback ?? {}; const knownChallengesMap: { [uuid: string]: { @@ -32,51 +38,80 @@ export function ChallengeOverviewScreen(): VNode { type: ch.type, cost: ch.cost, instructions: ch.instructions, - feedback: challengeFeedback[ch.uuid] + feedback: challengeFeedback[ch.uuid], }; } - const policiesWithInfo = policies.map(row => { - let isPolicySolved = true - const challenges = row.map(({ uuid }) => { - const info = knownChallengesMap[uuid]; - const isChallengeSolved = info?.feedback?.state === 'solved' - isPolicySolved = isPolicySolved && isChallengeSolved - return { info, uuid, isChallengeSolved } - }).filter(ch => ch.info !== undefined) + const policiesWithInfo = policies.map((row) => { + let isPolicySolved = true; + const challenges = row + .map(({ uuid }) => { + const info = knownChallengesMap[uuid]; + const isChallengeSolved = info?.feedback?.state === "solved"; + isPolicySolved = isPolicySolved && isChallengeSolved; + return { info, uuid, isChallengeSolved }; + }) + .filter((ch) => ch.info !== undefined); - return { isPolicySolved, challenges } - }) + return { isPolicySolved, challenges }; + }); - const atLeastThereIsOnePolicySolved = policiesWithInfo.find(p => p.isPolicySolved) !== undefined + const atLeastThereIsOnePolicySolved = + policiesWithInfo.find((p) => p.isPolicySolved) !== undefined; - const errors = !atLeastThereIsOnePolicySolved ? "Solve one policy before proceeding" : undefined; + const errors = !atLeastThereIsOnePolicySolved + ? "Solve one policy before proceeding" + : undefined; return ( - {!policies.length ?

    - No policies found, try with another version of the secret -

    : (policies.length === 1 ?

    - One policy found for this secret. You need to solve all the challenges in order to recover your secret. -

    :

    - We have found {policies.length} polices. You need to solve all the challenges from one policy in order - to recover your secret. -

    )} + {!policies.length ? ( +

    + No policies found, try with another version of the secret +

    + ) : policies.length === 1 ? ( +

    + One policy found for this secret. You need to solve all the challenges + in order to recover your secret. +

    + ) : ( +

    + We have found {policies.length} polices. You need to solve all the + challenges from one policy in order to recover your secret. +

    + )} {policiesWithInfo.map((policy, policy_index) => { const tableBody = policy.challenges.map(({ info, uuid }) => { - const isFree = !info.cost || info.cost.endsWith(':0') - const method = authMethods[info.type as KnownAuthMethods] + const isFree = !info.cost || info.cost.endsWith(":0"); + const method = authMethods[info.type as KnownAuthMethods]; return ( -
    -
    - - {method?.icon} - - - {info.instructions} - +
    +
    +
    + {method?.icon} + {info.instructions} +
    + {info.feedback?.state === ChallengeFeedbackStatus.Message ? ( +
    +

    {info.feedback.message}

    +
    + ) : null}
    {method && info.feedback?.state !== "solved" ? ( - reducer.transition("select_challenge", { uuid })}> + + reducer.transition("select_challenge", { uuid }) + } + > {isFree ? "Solve" : `Pay and Solve`} ) : null} @@ -86,26 +121,36 @@ export function ChallengeOverviewScreen(): VNode {
    ); - }) - - const policyName = policy.challenges.map(x => x.info.type).join(" + "); - const opa = !atLeastThereIsOnePolicySolved ? undefined : ( policy.isPolicySolved ? undefined : '0.6') + }); + + const policyName = policy.challenges + .map((x) => x.info.type) + .join(" + "); + const opa = !atLeastThereIsOnePolicySolved + ? undefined + : policy.isPolicySolved + ? undefined + : "0.6"; return ( -
    +

    Policy #{policy_index + 1}: {policyName}

    - {policy.challenges.length === 0 &&

    - This policy doesn't have challenges. -

    } - {policy.challenges.length === 1 &&

    - This policy just have one challenge. -

    } - {policy.challenges.length > 1 &&

    - This policy have {policy.challenges.length} challenges. -

    } + {policy.challenges.length === 0 && ( +

    This policy doesn't have challenges.

    + )} + {policy.challenges.length === 1 && ( +

    This policy just have one challenge.

    + )} + {policy.challenges.length > 1 && ( +

    This policy have {policy.challenges.length} challenges.

    + )} {tableBody}
    ); diff --git a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx index 77329f4fa..b64e1a096 100644 --- a/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx @@ -16,7 +16,7 @@ export function CountrySelectionScreen(): VNode { currencies: [x.currency], }); return ( - +
    {reducer.currentReducerState.countries!.map((x: any) => (
    diff --git a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx index 098a8ba8d..0b32e0db5 100644 --- a/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/TruthsPayingScreen.tsx @@ -13,7 +13,7 @@ export function TruthsPayingScreen(): VNode { const payments = reducer.currentReducerState.payments ?? []; return (

    -- cgit v1.2.3 From fefdb0d7adcfe95be20f4ff81af316a0ca64f35a Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 3 Nov 2021 18:52:10 +0100 Subject: anastasis-webui: display some crude challenge feedback --- .../src/pages/home/ChallengeOverviewScreen.tsx | 38 +++++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index c63f19eb6..69dbce037 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -5,6 +5,38 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; import { authMethods, KnownAuthMethods } from "./authMethod"; +function FeedbackDisplay(props: { feedback?: ChallengeFeedback }) { + const { feedback } = props; + if (!feedback) { + return null; + } + switch (feedback.state) { + case ChallengeFeedbackStatus.Message: + return ( +

    +

    {feedback.message}

    +
    + ); + case ChallengeFeedbackStatus.Pending: + case ChallengeFeedbackStatus.AuthIban: + return null; + case ChallengeFeedbackStatus.RateLimitExceeded: + return
    Rate limit exceeded.
    ; + case ChallengeFeedbackStatus.Redirect: + return
    Redirect (FIXME: not supported)
    ; + case ChallengeFeedbackStatus.Unsupported: + return
    Challenge not supported by client.
    ; + case ChallengeFeedbackStatus.TruthUnknown: + return
    Truth unknown
    ; + default: + return ( +
    +
    {JSON.stringify(feedback)}
    +
    + ); + } +} + export function ChallengeOverviewScreen(): VNode { const reducer = useAnastasisContext(); @@ -98,11 +130,7 @@ export function ChallengeOverviewScreen(): VNode { {method?.icon} {info.instructions}
    - {info.feedback?.state === ChallengeFeedbackStatus.Message ? ( -
    -

    {info.feedback.message}

    -
    - ) : null} +
    {method && info.feedback?.state !== "solved" ? ( -- cgit v1.2.3 From 9fb6536fbc91adaf7a8a80860fcef5e1f80bfb3b Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 3 Nov 2021 18:56:19 +0100 Subject: anastasis-webui: show feedback in solution screen --- .../src/pages/home/ChallengeOverviewScreen.tsx | 4 +- .../anastasis-webui/src/pages/home/SolveScreen.tsx | 177 ++++++++++++++++----- 2 files changed, 135 insertions(+), 46 deletions(-) (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx index 69dbce037..7bafbe06a 100644 --- a/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ChallengeOverviewScreen.tsx @@ -5,7 +5,7 @@ import { useAnastasisContext } from "../../context/anastasis"; import { AnastasisClientFrame } from "./index"; import { authMethods, KnownAuthMethods } from "./authMethod"; -function FeedbackDisplay(props: { feedback?: ChallengeFeedback }) { +function OverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { const { feedback } = props; if (!feedback) { return null; @@ -130,7 +130,7 @@ export function ChallengeOverviewScreen(): VNode { {method?.icon} {info.instructions}
    - +
    {method && info.feedback?.state !== "solved" ? ( diff --git a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx index b0cfa9bb0..d4d9271b4 100644 --- a/packages/anastasis-webui/src/pages/home/SolveScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SolveScreen.tsx @@ -1,38 +1,89 @@ import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AnastasisClientFrame } from "."; -import { ChallengeFeedback, ChallengeInfo } from "../../../../anastasis-core/lib"; +import { + ChallengeFeedback, + ChallengeFeedbackStatus, + ChallengeInfo, +} from "../../../../anastasis-core/lib"; import { TextInput } from "../../components/fields/TextInput"; import { useAnastasisContext } from "../../context/anastasis"; +function SolveOverviewFeedbackDisplay(props: { feedback?: ChallengeFeedback }) { + const { feedback } = props; + if (!feedback) { + return null; + } + switch (feedback.state) { + case ChallengeFeedbackStatus.Message: + return ( +
    +

    {feedback.message}

    +
    + ); + case ChallengeFeedbackStatus.Pending: + case ChallengeFeedbackStatus.AuthIban: + return null; + case ChallengeFeedbackStatus.RateLimitExceeded: + return
    Rate limit exceeded.
    ; + case ChallengeFeedbackStatus.Redirect: + return
    Redirect (FIXME: not supported)
    ; + case ChallengeFeedbackStatus.Unsupported: + return
    Challenge not supported by client.
    ; + case ChallengeFeedbackStatus.TruthUnknown: + return
    Truth unknown
    ; + default: + return ( +
    +
    {JSON.stringify(feedback)}
    +
    + ); + } +} + export function SolveScreen(): VNode { - const reducer = useAnastasisContext() + const reducer = useAnastasisContext(); const [answer, setAnswer] = useState(""); if (!reducer) { - return -
    no reducer in context
    -
    + return ( + +
    no reducer in context
    +
    + ); } - if (!reducer.currentReducerState || reducer.currentReducerState.recovery_state === undefined) { - return -
    invalid state
    -
    + if ( + !reducer.currentReducerState || + reducer.currentReducerState.recovery_state === undefined + ) { + return ( + +
    invalid state
    +
    + ); } if (!reducer.currentReducerState.recovery_information) { - return -
    no recovery information found
    -
    + return ( + +
    no recovery information found
    +
    + ); } if (!reducer.currentReducerState.selected_challenge_uuid) { - return -
    invalid state
    -
    + return ( + +
    invalid state
    +
    + ); } const chArr = reducer.currentReducerState.recovery_information.challenges; - const challengeFeedback = reducer.currentReducerState.challenge_feedback ?? {}; + const challengeFeedback = + reducer.currentReducerState.challenge_feedback ?? {}; const selectedUuid = reducer.currentReducerState.selected_challenge_uuid; const challenges: { [uuid: string]: ChallengeInfo; @@ -47,31 +98,44 @@ export function SolveScreen(): VNode { email: SolveEmailEntry, post: SolvePostEntry, }; - const SolveDialog = selectedChallenge === undefined ? SolveUndefinedEntry : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry; + const SolveDialog = + selectedChallenge === undefined + ? SolveUndefinedEntry + : dialogMap[selectedChallenge.type] ?? SolveUnsupportedEntry; function onNext(): void { - reducer?.transition("solve_challenge", { answer }) + reducer?.transition("solve_challenge", { answer }); } function onCancel(): void { - reducer?.back() + reducer?.back(); } - return ( - + + + feedback={challengeFeedback[selectedUuid]} + /> -
    - - +
    + +
    ); @@ -85,38 +149,61 @@ export interface SolveEntryProps { setAnswer: (s: string) => void; } -function SolveSmsEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { - return ( -

    An sms has been sent to "{challenge.instructions}". Type the code below

    - -
    +function SolveSmsEntry({ + challenge, + answer, + setAnswer, +}: SolveEntryProps): VNode { + return ( + +

    + An sms has been sent to "{challenge.instructions}". Type the code + below +

    + +
    ); } -function SolveQuestionEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { +function SolveQuestionEntry({ + challenge, + answer, + setAnswer, +}: SolveEntryProps): VNode { return (

    Type the answer to the following question:

    -
    -        {challenge.instructions}
    -      
    +
    {challenge.instructions}
    ); } -function SolvePostEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { +function SolvePostEntry({ + challenge, + answer, + setAnswer, +}: SolveEntryProps): VNode { return ( -

    instruction for post type challenge "{challenge.instructions}"

    +

    + instruction for post type challenge "{challenge.instructions}" +

    ); } -function SolveEmailEntry({ challenge, answer, setAnswer }: SolveEntryProps): VNode { +function SolveEmailEntry({ + challenge, + answer, + setAnswer, +}: SolveEntryProps): VNode { return ( -

    An email has been sent to "{challenge.instructions}". Type the code below

    +

    + An email has been sent to "{challenge.instructions}". Type the + code below +

    ); @@ -126,7 +213,8 @@ function SolveUnsupportedEntry(props: SolveEntryProps): VNode { return (

    - The challenge selected is not supported for this UI. Please update this version or try using another policy. + The challenge selected is not supported for this UI. Please update this + version or try using another policy.

    Challenge type: {props.challenge.type} @@ -136,9 +224,10 @@ function SolveUnsupportedEntry(props: SolveEntryProps): VNode { } function SolveUndefinedEntry(props: SolveEntryProps): VNode { return ( - +

    - There is no challenge information for id "{props.id}". Try resetting the recovery session. + There is no challenge information for id "{props.id}". Try + resetting the recovery session.

    ); -- cgit v1.2.3 From a82b5a6992fda61d6eaa0bb079e284805a394777 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 3 Nov 2021 17:30:11 -0300 Subject: feedback from meeting and editing policy --- .../anastasis-webui/src/components/AsyncButton.tsx | 51 ++++++++ .../src/components/fields/DateInput.tsx | 46 ++++--- .../src/components/menu/NavigationBar.tsx | 2 +- .../src/components/menu/SideBar.tsx | 14 +-- .../src/components/picker/DatePicker.tsx | 12 +- packages/anastasis-webui/src/hooks/async.ts | 77 ++++++++++++ .../pages/home/AttributeEntryScreen.stories.tsx | 4 +- .../src/pages/home/AttributeEntryScreen.tsx | 37 ++++-- .../src/pages/home/AuthenticationEditorScreen.tsx | 4 + .../src/pages/home/BackupFinishedScreen.tsx | 7 +- .../home/ContinentSelectionScreen.stories.tsx | 18 ++- .../src/pages/home/ContinentSelectionScreen.tsx | 36 +++--- .../pages/home/CountrySelectionScreen.stories.tsx | 39 ------ .../src/pages/home/CountrySelectionScreen.tsx | 31 ----- .../src/pages/home/EditPoliciesScreen.stories.tsx | 109 +++++++++++++++++ .../src/pages/home/EditPoliciesScreen.tsx | 133 +++++++++++++++++++++ .../pages/home/ReviewPoliciesScreen.stories.tsx | 8 +- .../src/pages/home/ReviewPoliciesScreen.tsx | 57 +++++++-- .../anastasis-webui/src/pages/home/StartScreen.tsx | 42 +++---- .../home/authMethod/AuthMethodQuestionSetup.tsx | 15 +-- packages/anastasis-webui/src/pages/home/index.tsx | 33 ++--- packages/anastasis-webui/src/scss/main.scss | 2 +- packages/anastasis-webui/src/utils/index.tsx | 97 ++++++++++++--- 23 files changed, 655 insertions(+), 219 deletions(-) create mode 100644 packages/anastasis-webui/src/components/AsyncButton.tsx create mode 100644 packages/anastasis-webui/src/hooks/async.ts delete mode 100644 packages/anastasis-webui/src/pages/home/CountrySelectionScreen.stories.tsx delete mode 100644 packages/anastasis-webui/src/pages/home/CountrySelectionScreen.tsx create mode 100644 packages/anastasis-webui/src/pages/home/EditPoliciesScreen.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/EditPoliciesScreen.tsx (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx new file mode 100644 index 000000000..5602715e4 --- /dev/null +++ b/packages/anastasis-webui/src/components/AsyncButton.tsx @@ -0,0 +1,51 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ComponentChildren, h, VNode } from "preact"; +// import { LoadingModal } from "../modal"; +import { useAsync } from "../hooks/async"; +// import { Translate } from "../../i18n"; + +type Props = { + children: ComponentChildren; + disabled: boolean; + onClick?: () => Promise; + [rest: string]: any; +}; + +export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VNode { + const { isLoading, request } = useAsync(onClick); + + // if (isSlow) { + // return ; + // } + console.log(isLoading) + if (isLoading) { + + return ; + } + + return + + ; +} diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index 69a05fcf3..c406b85d1 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -1,4 +1,4 @@ -import { format } from "date-fns"; +import { format, isAfter, parse, sub, subYears } from "date-fns"; import { h, VNode } from "preact"; import { useLayoutEffect, useRef, useState } from "preact/hooks"; import { DatePicker } from "../picker/DatePicker"; @@ -19,16 +19,14 @@ export function DateInput(props: DateInputProps): VNode { inputRef.current?.focus(); } }, [props.grabFocus]); - const [opened, setOpened2] = useState(false) - function setOpened(v: boolean): void { - console.log('dale', v) - setOpened2(v) - } + const [opened, setOpened] = useState(false) const value = props.bind[0] || ""; const [dirty, setDirty] = useState(false) const showError = dirty && props.error + const calendar = subYears(new Date(), 30) + return
    -
    - { setOpened(true) } } - value={value} - ref={inputRef} /> - - - - +
    +
    +

    + { + const text = e.currentTarget.value + setDirty(true) + props.bind[1](text); + }} + ref={inputRef} /> +

    +

    + { setOpened(true) }}> + + +

    +
    +

    Using the format yyyy-mm-dd

    {showError &&

    {props.error}

    } setOpened(false)} dateReceiver={(d) => { setDirty(true) - const v = format(d, 'yyyy/MM/dd') + const v = format(d, 'yyyy-MM-dd') props.bind[1](v); }} /> diff --git a/packages/anastasis-webui/src/components/menu/NavigationBar.tsx b/packages/anastasis-webui/src/components/menu/NavigationBar.tsx index e1bb4c7c0..935951ab9 100644 --- a/packages/anastasis-webui/src/components/menu/NavigationBar.tsx +++ b/packages/anastasis-webui/src/components/menu/NavigationBar.tsx @@ -49,7 +49,7 @@ export function NavigationBar({ onMobileMenu, title }: Props): VNode {
    diff --git a/packages/anastasis-webui/src/components/menu/SideBar.tsx b/packages/anastasis-webui/src/components/menu/SideBar.tsx index 35720e0f1..72655662f 100644 --- a/packages/anastasis-webui/src/components/menu/SideBar.tsx +++ b/packages/anastasis-webui/src/components/menu/SideBar.tsx @@ -39,9 +39,9 @@ export function Sidebar({ mobile }: Props): VNode { return (
  • -
  • + {/*
  • Payment (optional)
    -
  • + */}
  • @@ -116,7 +116,7 @@ export function Sidebar({ mobile }: Props): VNode {
  • - Location & Currency + Location
  • diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx index 5b33fa8be..a94b3708e 100644 --- a/packages/anastasis-webui/src/components/picker/DatePicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx @@ -24,6 +24,7 @@ import { h, Component } from "preact"; interface Props { closeFunction?: () => void; dateReceiver?: (d: Date) => void; + initialDate?: Date; years?: Array; opened?: boolean; } @@ -213,8 +214,8 @@ export class DatePicker extends Component { // } } - constructor() { - super(); + constructor(props) { + super(props); this.closeDatePicker = this.closeDatePicker.bind(this); this.dayClicked = this.dayClicked.bind(this); @@ -226,11 +227,12 @@ export class DatePicker extends Component { this.toggleYearSelector = this.toggleYearSelector.bind(this); this.displaySelectedMonth = this.displaySelectedMonth.bind(this); + const initial = props.initialDate || now; this.state = { - currentDate: now, - displayedMonth: now.getMonth(), - displayedYear: now.getFullYear(), + currentDate: initial, + displayedMonth: initial.getMonth(), + displayedYear: initial.getFullYear(), selectYearMode: false } } diff --git a/packages/anastasis-webui/src/hooks/async.ts b/packages/anastasis-webui/src/hooks/async.ts new file mode 100644 index 000000000..f142a5dc5 --- /dev/null +++ b/packages/anastasis-webui/src/hooks/async.ts @@ -0,0 +1,77 @@ +/* + 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ +import { useState } from "preact/hooks"; +// import { cancelPendingRequest } from "./backend"; + +export interface Options { + slowTolerance: number; +} + +export interface AsyncOperationApi { + request: (...a: any) => void; + cancel: () => void; + data: T | undefined; + isSlow: boolean; + isLoading: boolean; + error: string | undefined; +} + +export function useAsync(fn?: (...args: any) => Promise, { slowTolerance: tooLong }: Options = { slowTolerance: 1000 }): AsyncOperationApi { + const [data, setData] = useState(undefined); + const [isLoading, setLoading] = useState(false); + const [error, setError] = useState(undefined); + const [isSlow, setSlow] = useState(false) + + const request = async (...args: any) => { + if (!fn) return; + setLoading(true); + console.log("loading true") + const handler = setTimeout(() => { + setSlow(true) + }, tooLong) + + try { + const result = await fn(...args); + console.log(result) + setData(result); + } catch (error) { + setError(error); + } + setLoading(false); + setSlow(false) + clearTimeout(handler) + }; + + function cancel() { + // cancelPendingRequest() + setLoading(false); + setSlow(false) + } + + return { + request, + cancel, + data, + isSlow, + isLoading, + error + }; +} 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, })} > -
    -
    +
    +
    {fieldList}
    -
    +

    This personal information will help to locate your secret.

    -

    This stay private

    -

    The information you have entered here: -

    +

    This stays private

    +

    The information you have entered here:

    • @@ -111,15 +111,17 @@ function AttributeEntryField(props: AttributeEntryFieldProps): VNode { bind={[props.value, props.setValue]} /> } - +
      + This stays private - This stay private - +
    ); } +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 {
    When recovering your wallet, you will be asked to verify your identity via the methods you configure here. + + Explain the exclamation marks + + Explain how to add providers
    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 {

    } {details &&
    -

    The backup is stored by the following providers:

    +

    The backup is stored by the following providers:

    {Object.keys(details).map((x, i) => { const sd = details[x]; return ( @@ -31,11 +31,14 @@ export function BackupFinishedScreen(): VNode { {x}

    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'}

    ); })}
    } +
    + +
    ); } 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 ( - -
    -
    + + +
    +
    -
    -
    - selectContinent(e.currentTarget.value)} value={theContinent} > {continentList.map(prov => (
    - {!step1 && - reducer.back()}> - X - - }
    -
    -
    +
    +
    -
    } +
    } */}
    -
    +

    - 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.

    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 - */ - -/** -* -* @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
    no reducer in context
    - } - if (!reducer.currentReducerState || !("countries" in reducer.currentReducerState)) { - return
    invalid state
    - } - const sel = (x: any): void => reducer.transition("select_country", { - country_code: x.code, - currencies: [x.currency], - }); - return ( - -
    - {reducer.currentReducerState.countries!.map((x: any) => ( -
    - -
    - ))} -
    -
    - ); -} 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 + */ + +/** +* +* @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>([]) + + const reducer = useAnastasisContext() + if (!reducer) { + return
    no reducer in context
    + } + if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { + return
    invalid state
    + } + + 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 +
    + {!policy ?

    + Creating a new policy #{policy_index} +

    :

    + Editing policy #{policy_index} +

    } + {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 ( +
    + + {authMethods[type]?.icon} + + + {method.instructions} + + + + + + +
    + ); + })} +
    + + + + + +
    +
    +
    +} 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() const reducer = useAnastasisContext() if (!reducer) { return
    no reducer in context
    @@ -12,20 +16,44 @@ export function ReviewPoliciesScreen(): VNode { if (!reducer.currentReducerState || reducer.currentReducerState.backup_state === undefined) { return
    invalid state
    } + const configuredAuthMethods = reducer.currentReducerState.authentication_methods ?? []; const policies = reducer.currentReducerState.policies ?? []; + if (editingPolicy !== undefined) { + return ( + 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 ( {policies.length > 0 &&

    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. -

    } +

    } {policies.length < 1 &&

    No policies had been created. Go back and add more authentication methods. -

    } +

    } +
    setEditingPolicy(policies.length + 1)}> + +
    {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 {

    } {methods.map((m, i) => { return ( -

    - - {authMethods[m.type as KnownAuthMethods]?.icon} - - - {m.instructions} recovery provided by {m.provider} - -

    +

    + + {authMethods[m.type as KnownAuthMethods]?.icon} + + + {m.instructions} recovery provided by {m.provider} + +

    ); })}
    -
    +
    + + +
    ); })} 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 ( -
    -
    -
    -
    -
    +
    +
    +
    -
    - +
    + - + - -
    - -
    -
    +
    -
    + +
    +
    ); 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

    - 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 />

    +
    + + + + +
    + {configured.length > 0 &&
    Your security questions: @@ -58,12 +65,6 @@ export function AuthMethodQuestionSetup({ cancel, addAuthMethod, configured }: A
    })}
    } -
    - - - - -
    ); 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

    Fatal: Reducer must be in context.

    ; } - const next = (): void => { - if (props.onNext) { - props.onNext(); - } else { - reducer.transition("next", {}); - } + const next = async (): Promise => { + return new Promise((res, rej) => { + try { + if (props.onNext) { + props.onNext(); + } else { + reducer.transition("next", {}); + } + res() + } catch { + rej() + } + }) }; const handleKeyPress = ( e: h.JSX.TargetedKeyboardEvent, @@ -111,20 +118,18 @@ export function AnastasisClientFrame(props: AnastasisClientFrameProps): VNode { return ( -
    -
    handleKeyPress(e)}> -

    {props.title}

    +
    handleKeyPress(e)}> +

    {props.title}

    +
    {props.children} {!props.hideNav ? (
    - - - + Next
    ) : null} -
    +
    ); diff --git a/packages/anastasis-webui/src/scss/main.scss b/packages/anastasis-webui/src/scss/main.scss index 1e0d3fded..b5335073f 100644 --- a/packages/anastasis-webui/src/scss/main.scss +++ b/packages/anastasis-webui/src/scss/main.scss @@ -195,7 +195,7 @@ div[data-tooltip]::before { padding: 1em 1em; min-height: 100%; width: 100%; - max-width: 40em; + // max-width: 40em; } // .home div { diff --git a/packages/anastasis-webui/src/utils/index.tsx b/packages/anastasis-webui/src/utils/index.tsx index 48ac47544..244be8af8 100644 --- a/packages/anastasis-webui/src/utils/index.tsx +++ b/packages/anastasis-webui/src/utils/index.tsx @@ -86,10 +86,10 @@ const base = { { type: "question", usage_fee: "COL:0" - },{ + }, { type: "sms", usage_fee: "COL:0" - },{ + }, { type: "email", usage_fee: "COL:0" }, @@ -98,6 +98,48 @@ const base = { storage_limit_in_megabytes: 16, truth_upload_fee: "COL:0" }, + "https://kudos.demo.anastasis.lu/": { + http_status: 200, + annual_fee: "COL:0", + business_name: "ana", + currency: "COL", + liability_limit: "COL:10", + methods: [ + { + type: "question", + usage_fee: "COL:0" + }, { + type: "email", + usage_fee: "COL:0" + }, + ], + salt: "WBMDD76BR1E90YQ5AHBMKPH7GW", + storage_limit_in_megabytes: 16, + truth_upload_fee: "COL:0" + }, + "https://anastasis.demo.taler.net/": { + http_status: 200, + annual_fee: "COL:0", + business_name: "ana", + currency: "COL", + liability_limit: "COL:10", + methods: [ + { + type: "question", + usage_fee: "COL:0" + }, { + type: "sms", + usage_fee: "COL:0" + }, { + type: "totp", + usage_fee: "COL:0" + }, + ], + salt: "WBMDD76BR1E90YQ5AHBMKPH7GW", + storage_limit_in_megabytes: 16, + truth_upload_fee: "COL:0" + }, + "http://localhost:8087/": { code: 8414, hint: "request to provider failed" @@ -118,55 +160,72 @@ const base = { export const reducerStatesExample = { initial: undefined, - recoverySelectCountry: {...base, + recoverySelectCountry: { + ...base, recovery_state: RecoveryStates.CountrySelecting } as ReducerState, - recoverySelectContinent: {...base, + recoverySelectContinent: { + ...base, recovery_state: RecoveryStates.ContinentSelecting, } as ReducerState, - secretSelection: {...base, + secretSelection: { + ...base, recovery_state: RecoveryStates.SecretSelecting, } as ReducerState, - recoveryFinished: {...base, + recoveryFinished: { + ...base, recovery_state: RecoveryStates.RecoveryFinished, } as ReducerState, - challengeSelecting: {...base, + challengeSelecting: { + ...base, recovery_state: RecoveryStates.ChallengeSelecting, } as ReducerState, - challengeSolving: {...base, + challengeSolving: { + ...base, recovery_state: RecoveryStates.ChallengeSolving, } as ReducerState, - challengePaying: {...base, + challengePaying: { + ...base, recovery_state: RecoveryStates.ChallengePaying, } as ReducerState, - recoveryAttributeEditing: {...base, + recoveryAttributeEditing: { + ...base, recovery_state: RecoveryStates.UserAttributesCollecting } as ReducerState, - backupSelectCountry: {...base, + backupSelectCountry: { + ...base, backup_state: BackupStates.CountrySelecting } as ReducerState, - backupSelectContinent: {...base, + backupSelectContinent: { + ...base, backup_state: BackupStates.ContinentSelecting, } as ReducerState, - secretEdition: {...base, + secretEdition: { + ...base, backup_state: BackupStates.SecretEditing, } as ReducerState, - policyReview: {...base, + policyReview: { + ...base, backup_state: BackupStates.PoliciesReviewing, } as ReducerState, - policyPay: {...base, + policyPay: { + ...base, backup_state: BackupStates.PoliciesPaying, } as ReducerState, - backupFinished: {...base, + backupFinished: { + ...base, backup_state: BackupStates.BackupFinished, } as ReducerState, - authEditing: {...base, + authEditing: { + ...base, backup_state: BackupStates.AuthenticationsEditing } as ReducerState, - backupAttributeEditing: {...base, + backupAttributeEditing: { + ...base, backup_state: BackupStates.UserAttributesCollecting } as ReducerState, - truthsPaying: {...base, + truthsPaying: { + ...base, backup_state: BackupStates.TruthsPaying } as ReducerState, -- cgit v1.2.3 From 4ac0b23793417536281f7d3c67c4c954da795f88 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 4 Nov 2021 12:37:27 -0300 Subject: testing provider screen --- .../pages/home/AddingProviderScreen.stories.tsx | 50 ++++++++++ .../src/pages/home/AddingProviderScreen.tsx | 101 +++++++++++++++++++++ .../src/pages/home/AuthenticationEditorScreen.tsx | 89 ++++++++++-------- .../src/pages/home/ReviewPoliciesScreen.tsx | 5 +- .../src/pages/home/authMethod/index.tsx | 5 +- 5 files changed, 209 insertions(+), 41 deletions(-) create mode 100644 packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx create mode 100644 packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx new file mode 100644 index 000000000..43807fefe --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.stories.tsx @@ -0,0 +1,50 @@ +/* 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 + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { ReducerState } from 'anastasis-core'; +import { createExample, reducerStatesExample } from '../../utils'; +import { AddingProviderScreen as TestedComponent } from './AddingProviderScreen'; + + +export default { + title: 'Pages/backup/AddingProviderScreen', + component: TestedComponent, + args: { + order: 4, + }, + argTypes: { + onUpdate: { action: 'onUpdate' }, + onBack: { action: 'onBack' }, + }, +}; + +export const NewProvider = createExample(TestedComponent, { + ...reducerStatesExample.authEditing, +} as ReducerState); + +export const NewSMSProvider = createExample(TestedComponent, { + ...reducerStatesExample.authEditing, +} as ReducerState, { providerType: 'sms'}); + +export const NewIBANProvider = createExample(TestedComponent, { + ...reducerStatesExample.authEditing, +} as ReducerState, { providerType: 'iban' }); diff --git a/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx new file mode 100644 index 000000000..9c83da49e --- /dev/null +++ b/packages/anastasis-webui/src/pages/home/AddingProviderScreen.tsx @@ -0,0 +1,101 @@ +/* eslint-disable @typescript-eslint/camelcase */ +import { + encodeCrock, + stringToBytes +} from "@gnu-taler/taler-util"; +import { h, VNode } from "preact"; +import { useLayoutEffect, useRef, useState } from "preact/hooks"; +import { TextInput } from "../../components/fields/TextInput"; +import { authMethods, KnownAuthMethods } from "./authMethod"; +import { AnastasisClientFrame } from "./index"; + +interface Props { + providerType?: KnownAuthMethods; + cancel: () => void; +} +export function AddingProviderScreen({ providerType, cancel }: Props): VNode { + const [providerURL, setProviderURL] = useState(""); + const [error, setError] = useState() + const providerLabel = providerType ? authMethods[providerType].label : undefined + + function testProvider(): void { + setError(undefined) + + fetch(`${providerURL}/config`) + .then(r => r.json().catch(d => ({}))) + .then(r => { + if (!("methods" in r) || !Array.isArray(r.methods)) { + setError("This provider doesn't have authentication method. Check the provider URL") + return; + } + if (!providerLabel) { + setError("") + return + } + let found = false + for (let i = 0; i < r.methods.length && !found; i++) { + found = r.methods[i].type !== providerType + } + if (!found) { + setError(`This provider does not support authentication method ${providerLabel}`) + } + }) + .catch(e => { + setError(`There was an error testing this provider, try another one. ${e.message}`) + }) + + } + function addProvider(): void { + // addAuthMethod({ + // authentication_method: { + // type: "sms", + // instructions: `SMS to ${providerURL}`, + // challenge: encodeCrock(stringToBytes(providerURL)), + // }, + // }); + } + const inputRef = useRef(null); + useLayoutEffect(() => { + inputRef.current?.focus(); + }, []); + + let errors = !providerURL ? 'Add provider URL' : undefined + try { + new URL(providerURL) + } catch { + errors = 'Check the URL' + } + if (!!error && !errors) { + errors = error + } + + return ( + +
    +

    + Add a provider url {errors} +

    +
    + +
    + {!!error &&

    {error}

    } + {error === "" &&

    This provider worked!

    } +
    + +
    +
    + + + + +
    +
    +
    + ); +} diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index ab482044f..b95d3f1e3 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/camelcase */ import { AuthMethod } from "anastasis-core"; -import { ComponentChildren, h, VNode } from "preact"; +import { ComponentChildren, Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; +import { TextInput } from "../../components/fields/TextInput"; import { useAnastasisContext } from "../../context/anastasis"; import { authMethods, KnownAuthMethods } from "./authMethod"; import { AnastasisClientFrame } from "./index"; @@ -13,6 +14,7 @@ const getKeys = Object.keys as (obj: T) => Array export function AuthenticationEditorScreen(): VNode { const [noProvidersAck, setNoProvidersAck] = useState(false) const [selectedMethod, setSelectedMethod] = useState(undefined); + const [addingProvider, setAddingProvider] = useState(undefined) const reducer = useAnastasisContext() if (!reducer) { @@ -62,36 +64,58 @@ export function AuthenticationEditorScreen(): VNode { }; const AuthSetup = authMethods[selectedMethod].screen ?? AuthMethodNotImplemented; - return ( + return ( + + {!authAvailableSet.has(selectedMethod) && { + null + }} + > + We have found no trusted cloud providers for your recovery secret. You can add a provider manually. + To add a provider you must know the provider URL (e.g. https://provider.com) +

    + More about cloud providers +

    +
    } + +
    ); } + + if (addingProvider !== undefined) { + return
    + } + function MethodButton(props: { method: KnownAuthMethods }): VNode { + if (authMethods[props.method].skip) return
    + return (
    -

    - ); - }) - )} */}
    -
    - When recovering your wallet, you will be asked to verify your identity via the methods you configure here. - - Explain the exclamation marks - - Explain how to add providers +
    +

    + When recovering your wallet, you will be asked to verify your identity via the methods you configure here. + The list of authentication method is defined by the backup provider list. +

    +

    + +

    + {authAvailableSet.size > 0 &&

    + We couldn't find provider for some of the authentication methods. +

    }
    diff --git a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx index b8beb7b47..9441022b8 100644 --- a/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.tsx @@ -1,5 +1,4 @@ /* 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"; @@ -51,8 +50,8 @@ export function ReviewPoliciesScreen(): VNode { {policies.length < 1 &&

    No policies had been created. Go back and add more authentication methods.

    } -
    setEditingPolicy(policies.length + 1)}> - +
    +
    {policies.map((p, policy_index) => { const methods = p.methods diff --git a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx index 1e1d7bc03..7b0cce883 100644 --- a/packages/anastasis-webui/src/pages/home/authMethod/index.tsx +++ b/packages/anastasis-webui/src/pages/home/authMethod/index.tsx @@ -17,6 +17,7 @@ interface AuthMethodConfiguration { icon: VNode; label: string; screen: (props: AuthMethodSetupProps) => VNode; + skip?: boolean; } export type KnownAuthMethods = "sms" | "email" | "post" | "question" | "video" | "totp" | "iban"; @@ -62,7 +63,7 @@ export const authMethods: KnowMethodConfig = { video: { icon: , label: "Video", - screen: VideScreen - + screen: VideScreen, + skip: true, } } \ No newline at end of file -- cgit v1.2.3 From 4ebeb00243598f7a0caf79ef405134b14f68400e Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 4 Nov 2021 17:43:24 +0100 Subject: anastasis-webui: return promises in anastasis reducer hook --- .../src/hooks/use-anastasis-reducer.ts | 66 ++++++++++++---------- 1 file changed, 36 insertions(+), 30 deletions(-) (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts index 72594749d..1ef28a168 100644 --- a/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts +++ b/packages/anastasis-webui/src/hooks/use-anastasis-reducer.ts @@ -1,5 +1,12 @@ import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { BackupStates, getBackupStartState, getRecoveryStartState, RecoveryStates, reduceAction, ReducerState } from "anastasis-core"; +import { + BackupStates, + getBackupStartState, + getRecoveryStartState, + RecoveryStates, + reduceAction, + ReducerState, +} from "anastasis-core"; import { useState } from "preact/hooks"; const reducerBaseUrl = "http://localhost:5000/"; @@ -98,13 +105,15 @@ export interface AnastasisReducerApi { startBackup: () => void; startRecover: () => void; reset: () => void; - back: () => void; - transition(action: string, args: any): void; + back: () => Promise; + transition(action: string, args: any): Promise; /** * Run multiple reducer steps in a transaction without * affecting the UI-visible transition state in-between. */ - runTransaction(f: (h: ReducerTransactionHandle) => Promise): void; + runTransaction( + f: (h: ReducerTransactionHandle) => Promise, + ): Promise; } function storageGet(key: string): string | null { @@ -222,9 +231,9 @@ export function useAnastasisReducer(): AnastasisReducerApi { } }, transition(action: string, args: any) { - doTransition(action, args); + return doTransition(action, args); }, - back() { + async back() { const reducerState = anastasisState.reducerState; if (!reducerState) { return; @@ -239,7 +248,7 @@ export function useAnastasisReducer(): AnastasisReducerApi { reducerState: undefined, }); } else { - doTransition("back", {}); + await doTransition("back", {}); } }, dismissError() { @@ -252,30 +261,27 @@ export function useAnastasisReducer(): AnastasisReducerApi { reducerState: undefined, }); }, - runTransaction(f) { - async function run() { - const txHandle = new ReducerTxImpl(anastasisState.reducerState!); - try { - await f(txHandle); - } catch (e) { - console.log("exception during reducer transaction", e); - } - const s = txHandle.transactionState; - console.log("transaction finished, new state", s); - if (s.code !== undefined) { - setAnastasisState({ - ...anastasisState, - currentError: txHandle.transactionState, - }); - } else { - setAnastasisState({ - ...anastasisState, - reducerState: txHandle.transactionState, - currentError: undefined, - }); - } + async runTransaction(f) { + const txHandle = new ReducerTxImpl(anastasisState.reducerState!); + try { + await f(txHandle); + } catch (e) { + console.log("exception during reducer transaction", e); + } + const s = txHandle.transactionState; + console.log("transaction finished, new state", s); + if (s.code !== undefined) { + setAnastasisState({ + ...anastasisState, + currentError: txHandle.transactionState, + }); + } else { + setAnastasisState({ + ...anastasisState, + reducerState: txHandle.transactionState, + currentError: undefined, + }); } - run(); }, }; } -- cgit v1.2.3 From ae0a35df2b2934c517954d2a73af4cc6e1734e30 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 4 Nov 2021 15:17:57 -0300 Subject: async, onInput, and some fixes --- .../anastasis-webui/src/components/AsyncButton.tsx | 4 +- .../src/components/fields/DateInput.tsx | 2 +- .../src/components/fields/EmailInput.tsx | 2 +- .../src/components/fields/NumberInput.tsx | 2 +- .../src/components/fields/TextInput.tsx | 2 +- .../src/components/picker/DatePicker.tsx | 2 +- packages/anastasis-webui/src/hooks/async.ts | 4 +- .../src/pages/home/AttributeEntryScreen.tsx | 4 +- .../src/pages/home/AuthenticationEditorScreen.tsx | 4 +- .../src/pages/home/ChallengeOverviewScreen.tsx | 1 - .../src/pages/home/ContinentSelectionScreen.tsx | 4 +- .../src/pages/home/RecoveryFinishedScreen.tsx | 3 + .../src/pages/home/SecretEditorScreen.tsx | 31 ++++++---- .../src/pages/home/SecretSelectionScreen.tsx | 71 ++++++++++++---------- .../anastasis-webui/src/pages/home/SolveScreen.tsx | 3 + packages/anastasis-webui/src/pages/home/index.tsx | 6 +- packages/anastasis-webui/src/utils/index.tsx | 8 +-- 17 files changed, 83 insertions(+), 70 deletions(-) (limited to 'packages/anastasis-webui/src') diff --git a/packages/anastasis-webui/src/components/AsyncButton.tsx b/packages/anastasis-webui/src/components/AsyncButton.tsx index 5602715e4..af85016e8 100644 --- a/packages/anastasis-webui/src/components/AsyncButton.tsx +++ b/packages/anastasis-webui/src/components/AsyncButton.tsx @@ -37,9 +37,7 @@ export function AsyncButton({ onClick, disabled, children, ...rest }: Props): VN // if (isSlow) { // return ; // } - console.log(isLoading) - if (isLoading) { - + if (isLoading) { return ; } diff --git a/packages/anastasis-webui/src/components/fields/DateInput.tsx b/packages/anastasis-webui/src/components/fields/DateInput.tsx index c406b85d1..3148c953f 100644 --- a/packages/anastasis-webui/src/components/fields/DateInput.tsx +++ b/packages/anastasis-webui/src/components/fields/DateInput.tsx @@ -41,7 +41,7 @@ export function DateInput(props: DateInputProps): VNode { type="text" class={showError ? 'input is-danger' : 'input'} value={value} - onChange={(e) => { + onInput={(e) => { const text = e.currentTarget.value setDirty(true) props.bind[1](text); diff --git a/packages/anastasis-webui/src/components/fields/EmailInput.tsx b/packages/anastasis-webui/src/components/fields/EmailInput.tsx index e0fca0f46..e21418fea 100644 --- a/packages/anastasis-webui/src/components/fields/EmailInput.tsx +++ b/packages/anastasis-webui/src/components/fields/EmailInput.tsx @@ -34,7 +34,7 @@ export function EmailInput(props: TextInputProps): VNode { placeholder={props.placeholder} type="email" class={showError ? 'input is-danger' : 'input'} - onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} style={{ display: "block" }} />
    diff --git a/packages/anastasis-webui/src/components/fields/NumberInput.tsx b/packages/anastasis-webui/src/components/fields/NumberInput.tsx index 2b6cdcd2c..2afb242b8 100644 --- a/packages/anastasis-webui/src/components/fields/NumberInput.tsx +++ b/packages/anastasis-webui/src/components/fields/NumberInput.tsx @@ -33,7 +33,7 @@ export function NumberInput(props: TextInputProps): VNode { type="number" placeholder={props.placeholder} class={showError ? 'input is-danger' : 'input'} - onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} style={{ display: "block" }} />
    diff --git a/packages/anastasis-webui/src/components/fields/TextInput.tsx b/packages/anastasis-webui/src/components/fields/TextInput.tsx index 4bb785cd3..c093689c5 100644 --- a/packages/anastasis-webui/src/components/fields/TextInput.tsx +++ b/packages/anastasis-webui/src/components/fields/TextInput.tsx @@ -32,7 +32,7 @@ export function TextInput(props: TextInputProps): VNode { value={value} placeholder={props.placeholder} class={showError ? 'input is-danger' : 'input'} - onChange={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} + onInput={(e) => {setDirty(true); props.bind[1]((e.target as HTMLInputElement).value)}} ref={inputRef} style={{ display: "block" }} />
    diff --git a/packages/anastasis-webui/src/components/picker/DatePicker.tsx b/packages/anastasis-webui/src/components/picker/DatePicker.tsx index a94b3708e..eb5d8145d 100644 --- a/packages/anastasis-webui/src/components/picker/DatePicker.tsx +++ b/packages/anastasis-webui/src/components/picker/DatePicker.tsx @@ -214,7 +214,7 @@ export class DatePicker extends Component { // } } - constructor(props) { + constructor(props: any) { super(props); this.closeDatePicker = this.closeDatePicker.bind(this); diff --git a/packages/anastasis-webui/src/hooks/async.ts b/packages/anastasis-webui/src/hooks/async.ts index f142a5dc5..ea3ff6acf 100644 --- a/packages/anastasis-webui/src/hooks/async.ts +++ b/packages/anastasis-webui/src/hooks/async.ts @@ -43,14 +43,14 @@ export function useAsync(fn?: (...args: any) => Promise, { slowTolerance: const request = async (...args: any) => { if (!fn) return; setLoading(true); - console.log("loading true") const handler = setTimeout(() => { setSlow(true) }, tooLong) try { + console.log("calling async", args) const result = await fn(...args); - console.log(result) + console.log("async back", result) setData(result); } catch (error) { setError(error); diff --git a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx index 52046b216..f86994c97 100644 --- a/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AttributeEntryScreen.tsx @@ -48,10 +48,10 @@ export function AttributeEntryScreen(): VNode { })} >
    -
    +
    {fieldList}
    -
    +

    This personal information will help to locate your secret.

    This stays private

    The information you have entered here:

    diff --git a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx index b95d3f1e3..93ca81194 100644 --- a/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/AuthenticationEditorScreen.tsx @@ -135,7 +135,7 @@ export function AuthenticationEditorScreen(): VNode { return (
    -