summaryrefslogtreecommitdiff
path: root/packages/anastasis-webui
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-11-02 10:12:52 -0300
committerSebastian <sebasjm@gmail.com>2021-11-02 10:13:08 -0300
commita4cdc02e5017ba587c169cb28a7e7927fc64c7cf (patch)
treead9815e595a62524f7c29ce477877ea415125997 /packages/anastasis-webui
parent88d142d2098ad87613222e9a0c6df478a78f6528 (diff)
downloadwallet-core-a4cdc02e5017ba587c169cb28a7e7927fc64c7cf.tar.gz
wallet-core-a4cdc02e5017ba587c169cb28a7e7927fc64c7cf.tar.bz2
wallet-core-a4cdc02e5017ba587c169cb28a7e7927fc64c7cf.zip
totp qr code
Diffstat (limited to 'packages/anastasis-webui')
-rw-r--r--packages/anastasis-webui/package.json1
-rw-r--r--packages/anastasis-webui/src/declaration.d.ts3
-rw-r--r--packages/anastasis-webui/src/pages/home/ReviewPoliciesScreen.stories.tsx2
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.stories.tsx6
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethodSetup/AuthMethodTotpSetup.tsx54
-rw-r--r--packages/anastasis-webui/src/pages/home/authMethodSetup/totp.ts56
6 files changed, 106 insertions, 16 deletions
diff --git a/packages/anastasis-webui/package.json b/packages/anastasis-webui/package.json
index 2f2577a92..b032c563c 100644
--- a/packages/anastasis-webui/package.json
+++ b/packages/anastasis-webui/package.json
@@ -53,6 +53,7 @@
"eslint-config-preact": "^1.1.1",
"jest": "^26.2.2",
"jest-preset-preact": "^4.0.2",
+ "jssha": "^3.2.0",
"preact-cli": "^3.2.2",
"sass": "^1.32.13",
"sass-loader": "^10.1.1",
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 (
<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
+ 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>
+ <div class="block">
<TextInput
label="TOTP Name"
grabFocus
bind={[name, setName]} />
</div>
- <QR text={`sometext ${name}`} />
+ <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>
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))