summaryrefslogtreecommitdiff
path: root/packages/auditor-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/auditor-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx')
-rw-r--r--packages/auditor-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx179
1 files changed, 179 insertions, 0 deletions
diff --git a/packages/auditor-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx b/packages/auditor-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
new file mode 100644
index 000000000..ffeaa064a
--- /dev/null
+++ b/packages/auditor-backoffice-ui/src/paths/instance/otp_devices/create/CreatePage.tsx
@@ -0,0 +1,179 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021-2023 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 { isRfc3548Base32Charset, randomRfc3548Base32Key } from "@gnu-taler/taler-util";
+import { useTranslationContext } from "@gnu-taler/web-util/browser";
+import { Fragment, h, VNode } from "preact";
+import { useState } from "preact/hooks";
+import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
+import {
+ FormErrors,
+ FormProvider,
+} from "../../../../components/form/FormProvider.js";
+import { Input } from "../../../../components/form/Input.js";
+import { InputSelector } from "../../../../components/form/InputSelector.js";
+import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
+import { useBackendContext } from "../../../../context/backend.js";
+import { MerchantBackend } from "../../../../declaration.js";
+
+type Entity = MerchantBackend.OTP.OtpDeviceAddDetails;
+
+interface Props {
+ onCreate: (d: Entity) => Promise<void>;
+ onBack?: () => void;
+}
+
+const algorithms = [0, 1, 2];
+const algorithmsNames = ["off", "30s 8d TOTP-SHA1", "30s 8d eTOTP-SHA1"];
+
+export function CreatePage({ onCreate, onBack }: Props): VNode {
+ const { i18n } = useTranslationContext();
+ const backend = useBackendContext();
+
+ const [state, setState] = useState<Partial<Entity>>({});
+
+ const [showKey, setShowKey] = useState(false);
+
+ const errors: FormErrors<Entity> = {
+ otp_device_id: !state.otp_device_id
+ ? i18n.str`required`
+ : !/[a-zA-Z0-9]*/.test(state.otp_device_id)
+ ? i18n.str`no valid. only characters and numbers`
+ : undefined,
+ otp_algorithm: !state.otp_algorithm ? i18n.str`required` : undefined,
+ otp_key: !state.otp_key
+ ? i18n.str`required`
+ : !isRfc3548Base32Charset(state.otp_key)
+ ? i18n.str`just letters and numbers from 2 to 7`
+ : state.otp_key.length !== 32
+ ? i18n.str`size of the key should be 32`
+ : undefined,
+ otp_device_description: !state.otp_device_description
+ ? i18n.str`required`
+ : !/[a-zA-Z0-9]*/.test(state.otp_device_description)
+ ? i18n.str`no valid. only characters and numbers`
+ : undefined,
+ };
+
+ const hasErrors = Object.keys(errors).some(
+ (k) => (errors as any)[k] !== undefined,
+ );
+
+ const submitForm = () => {
+ if (hasErrors) return Promise.reject();
+ return onCreate(state as any);
+ };
+
+ return (
+ <div>
+ <section class="section is-main-section">
+ <div class="columns">
+ <div class="column" />
+ <div class="column is-four-fifths">
+ <FormProvider
+ object={state}
+ valueHandler={setState}
+ errors={errors}
+ >
+ <Input<Entity>
+ name="otp_device_id"
+ label={i18n.str`ID`}
+ tooltip={i18n.str`Internal id on the system`}
+ />
+ <Input<Entity>
+ name="otp_device_description"
+ label={i18n.str`Descripiton`}
+ tooltip={i18n.str`Useful to identify the device physically`}
+ />
+ <InputSelector<Entity>
+ name="otp_algorithm"
+ label={i18n.str`Verification algorithm`}
+ tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}
+ values={algorithms}
+ toStr={(v) => algorithmsNames[v]}
+ fromStr={(v) => Number(v)}
+ />
+ {state.otp_algorithm && state.otp_algorithm > 0 ? (
+ <Fragment>
+ <InputWithAddon<Entity>
+ expand
+ name="otp_key"
+ label={i18n.str`Device key`}
+ inputType={showKey ? "text" : "password"}
+ help="Be sure to be very hard to guess or use the random generator"
+ tooltip={i18n.str`Your device need to have exactly the same value`}
+ fromStr={(v) => v.toUpperCase()}
+ addonAfterAction={() => {
+ setShowKey(!showKey);
+ }}
+ addonAfter={
+ <span class="icon">
+ {showKey ? (
+ <i class="mdi mdi-eye" />
+ ) : (
+ <i class="mdi mdi-eye-off" />
+ )}
+ </span>
+ }
+ side={
+ <button
+ data-tooltip={i18n.str`generate random secret key`}
+ class="button is-info mr-3"
+ onClick={(e) => {
+ setState((s) => ({
+ ...s,
+ otp_key: randomRfc3548Base32Key(),
+ }));
+ }}
+ >
+ <i18n.Translate>random</i18n.Translate>
+ </button>
+ }
+ />
+ </Fragment>
+ ) : undefined}
+ </FormProvider>
+
+ <div class="buttons is-right mt-5">
+ {onBack && (
+ <button class="button" onClick={onBack}>
+ <i18n.Translate>Cancel</i18n.Translate>
+ </button>
+ )}
+ <AsyncButton
+ disabled={hasErrors}
+ data-tooltip={
+ hasErrors
+ ? i18n.str`Need to complete marked fields`
+ : "confirm operation"
+ }
+ onClick={submitForm}
+ >
+ <i18n.Translate>Confirm</i18n.Translate>
+ </AsyncButton>
+ </div>
+ </div>
+ <div class="column" />
+ </div>
+ </section>
+ </div>
+ );
+}