aboutsummaryrefslogtreecommitdiff
path: root/packages/anastasis-webui/src/pages/home/authMethod
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-11-02 12:31:37 -0300
committerSebastian <sebasjm@gmail.com>2021-11-02 12:37:47 -0300
commit1fd337f4fed08d7867359ec52104a6cadb76cdfc (patch)
treecc12d85395fa829584a14b0b4ca6e3ac1d0b310e /packages/anastasis-webui/src/pages/home/authMethod
parentaa78c1105e7b6b74d6185cc33daa42f93ccbea58 (diff)
downloadwallet-core-1fd337f4fed08d7867359ec52104a6cadb76cdfc.tar.xz
refactoring challenge overview to look more like policy reviewing
Diffstat (limited to 'packages/anastasis-webui/src/pages/home/authMethod')
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.stories.tsx66
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodEmailSetup.tsx62
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.stories.tsx65
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodIbanSetup.tsx68
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.stories.tsx66
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodPostSetup.tsx102
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.stories.tsx66
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodQuestionSetup.tsx70
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.stories.tsx66
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodSmsSetup.tsx63
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.stories.tsx64
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodTotpSetup.tsx81
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.stories.tsx66
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/AuthMethodVideoSetup.tsx56
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/index.tsx68
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethod/totp.ts56
16 files changed, 1085 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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 (
+ <AnastasisClientFrame hideNav title="Add email authentication">
+ <p>
+ 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.
+ </p>
+ <div>
+ <EmailInput
+ label="Email address"
+ error={emailError}
+ placeholder="email@domain.com"
+ bind={[email, setEmail]} />
+ </div>
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your emails:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
+ <div><button class="button is-danger" onClick={c.remove} >Delete</button></div>
+ </div>
+ })}
+ </div></section>}
+ <div>
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addEmailAuth}>Add</button>
+ </span>
+ </div>
+ </div>
+ </AnastasisClientFrame>
+ );
+}
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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 (
+ <AnastasisClientFrame hideNav title="Add bank transfer authentication">
+ <p>
+ 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.
+ </p>
+ <div>
+ <TextInput
+ label="Bank account holder name"
+ grabFocus
+ placeholder="John Smith"
+ bind={[name, setName]} />
+ <TextInput
+ label="IBAN"
+ placeholder="DE91100000000123456789"
+ bind={[account, setAccount]} />
+ </div>
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your bank accounts:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
+ <div><button class="button is-danger" onClick={c.remove} >Delete</button></div>
+ </div>
+ })}
+ </div></section>}
+ <div>
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addIbanAuth}>Add</button>
+ </span>
+ </div>
+ </div>
+ </AnastasisClientFrame>
+ );
+}
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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 (
+ <AnastasisClientFrame hideNav title="Add postal authentication">
+ <p>
+ 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.
+ </p>
+ <div>
+ <TextInput
+ grabFocus
+ label="Full Name"
+ bind={[fullName, setFullName]}
+ />
+ </div>
+ <div>
+ <TextInput
+ label="Street"
+ bind={[street, setStreet]}
+ />
+ </div>
+ <div>
+ <TextInput
+ label="City" bind={[city, setCity]}
+ />
+ </div>
+ <div>
+ <TextInput
+ label="Postal Code" bind={[postcode, setPostcode]}
+ />
+ </div>
+ <div>
+ <TextInput
+ label="Country"
+ bind={[country, setCountry]}
+ />
+ </div>
+
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your postal code:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
+ <div><button class="button is-danger" onClick={c.remove} >Delete</button></div>
+ </div>
+ })}
+ </div>
+ </section>}
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addPostAuth}>Add</button>
+ </span>
+ </div>
+ </AnastasisClientFrame>
+ );
+}
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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 (
+ <AnastasisClientFrame hideNav title="Add Security Question">
+ <div>
+ <p>
+ 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.
+ </p>
+ <div>
+ <TextInput
+ label="Security question"
+ grabFocus
+ placeholder="Your question"
+ bind={[questionText, setQuestionText]} />
+ </div>
+ <div>
+ <TextInput
+ label="Answer"
+ placeholder="Your answer"
+ bind={[answerText, setAnswerText]}
+ />
+ </div>
+
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your security questions:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <p style={{ marginBottom: 'auto', marginTop: 'auto' }}>{c.instructions}</p>
+ <div><button class="button is-danger" onClick={c.remove} >Delete</button></div>
+ </div>
+ })}
+ </div></section>}
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addQuestionAuth}>Add</button>
+ </span>
+ </div>
+ </div>
+ </AnastasisClientFrame >
+ );
+}
diff --git a/packages/anastasis-webui/src/pages/home/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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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<HTMLInputElement>(null);
+ useLayoutEffect(() => {
+ inputRef.current?.focus();
+ }, []);
+ const errors = !mobileNumber ? 'Add a mobile number' : undefined
+ return (
+ <AnastasisClientFrame hideNav title="Add SMS authentication">
+ <div>
+ <p>
+ 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.
+ </p>
+ <div class="container">
+ <NumberInput
+ label="Mobile number"
+ placeholder="Your mobile number"
+ grabFocus
+ bind={[mobileNumber, setMobileNumber]} />
+ </div>
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your mobile numbers:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p>
+ <div><button class="button is-danger" onClick={c.remove}>Delete</button></div>
+ </div>
+ })}
+ </div></section>}
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addSmsAuth}>Add</button>
+ </span>
+ </div>
+ </div>
+ </AnastasisClientFrame>
+ );
+}
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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 (
+ <AnastasisClientFrame hideNav title="Add TOTP authentication">
+ <p>
+ 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.
+ </p>
+ <div class="block">
+ <TextInput
+ label="TOTP Name"
+ grabFocus
+ bind={[name, setName]} />
+ </div>
+ <div style={{ height: 300 }}>
+ <QR text={totpURL} />
+ </div>
+ <p>
+ After scanning the code with your TOTP App, test it in the input below.
+ </p>
+ <TextInput
+ label="Test code"
+ bind={[test, setTest]} />
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your TOTP numbers:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <p style={{ marginTop: 'auto', marginBottom: 'auto' }}>{c.instructions}</p>
+ <div><button class="button is-danger" onClick={c.remove}>Delete</button></div>
+ </div>
+ })}
+ </div></section>}
+ <div>
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <span data-tooltip={errors}>
+ <button class="button is-info" disabled={errors !== undefined} onClick={addTotpAuth}>Add</button>
+ </span>
+ </div>
+ </div>
+ </AnastasisClientFrame>
+ );
+}
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 <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @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 (
+ <AnastasisClientFrame hideNav title="Add video authentication">
+ <p>
+ 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.
+ </p>
+ <div style={{textAlign:'center'}}>
+ <ImageInput
+ label="Choose photograph"
+ grabFocus
+ bind={[image, setImage]} />
+ </div>
+ {configured.length > 0 && <section class="section">
+ <div class="block">
+ Your photographs:
+ </div><div class="block">
+ {configured.map((c, i) => {
+ return <div key={i} class="box" style={{ display: 'flex', justifyContent: 'space-between' }}>
+ <img style={{ marginTop: 'auto', marginBottom: 'auto', width: 100, height:100, border: 'solid 1px black' }} src={c.instructions} />
+ <div style={{marginTop: 'auto', marginBottom: 'auto'}}><button class="button is-danger" onClick={c.remove}>Delete</button></div>
+ </div>
+ })}
+ </div></section>}
+ <div>
+ <div style={{ marginTop: '2em', display: 'flex', justifyContent: 'space-between' }}>
+ <button class="button" onClick={cancel}>Cancel</button>
+ <button class="button is-info" onClick={addVideoAuth}>Add</button>
+ </div>
+ </div>
+ </AnastasisClientFrame>
+ );
+}
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: <img src={questionIcon} />,
+ label: "Question",
+ screen: QuestionScreen
+ },
+ sms: {
+ icon: <img src={smsIcon} />,
+ label: "SMS",
+ screen: SmsScreen
+ },
+ email: {
+ icon: <i class="mdi mdi-email" />,
+ label: "Email",
+ screen: EmailScreen
+
+ },
+ iban: {
+ icon: <i class="mdi mdi-bank" />,
+ label: "IBAN",
+ screen: IbanScreen
+
+ },
+ post: {
+ icon: <img src={postalIcon} />,
+ label: "Physical mail",
+ screen: PostalScreen
+
+ },
+ totp: {
+ icon: <i class="mdi mdi-devices" />,
+ label: "TOTP",
+ screen: TotpScreen
+
+ },
+ video: {
+ icon: <img src={videoIcon} />,
+ 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))