print and setup totp
This commit is contained in:
parent
ae1aee1358
commit
b874f9a0c5
@ -25,6 +25,7 @@ interface Props<T> extends InputProps<T> {
|
|||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
expand?: boolean;
|
expand?: boolean;
|
||||||
values: string[];
|
values: string[];
|
||||||
|
convert?: (v: string) => any;
|
||||||
toStr?: (v?: any) => string;
|
toStr?: (v?: any) => string;
|
||||||
fromStr?: (s: string) => any;
|
fromStr?: (s: string) => any;
|
||||||
}
|
}
|
||||||
@ -41,6 +42,7 @@ export function InputSelector<T>({
|
|||||||
label,
|
label,
|
||||||
help,
|
help,
|
||||||
values,
|
values,
|
||||||
|
convert,
|
||||||
toStr = defaultToString,
|
toStr = defaultToString,
|
||||||
}: Props<keyof T>): VNode {
|
}: Props<keyof T>): VNode {
|
||||||
const { error, value, onChange } = useField<T>(name);
|
const { error, value, onChange } = useField<T>(name);
|
||||||
@ -66,7 +68,10 @@ export function InputSelector<T>({
|
|||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
readonly={readonly}
|
readonly={readonly}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
onChange(e.currentTarget.value as any);
|
const v = convert
|
||||||
|
? convert(e.currentTarget.value)
|
||||||
|
: e.currentTarget.value;
|
||||||
|
onChange(v);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{placeholder && <option>{placeholder}</option>}
|
{placeholder && <option>{placeholder}</option>}
|
||||||
|
@ -1287,6 +1287,9 @@ export namespace MerchantBackend {
|
|||||||
// This parameter is optional.
|
// This parameter is optional.
|
||||||
pos_key?: string;
|
pos_key?: string;
|
||||||
|
|
||||||
|
// Algorithm for computing the POS confirmation, 0 for none.
|
||||||
|
pos_algorithm?: number;
|
||||||
|
|
||||||
// Additional information in a separate template.
|
// Additional information in a separate template.
|
||||||
template_contract: TemplateContractDetails;
|
template_contract: TemplateContractDetails;
|
||||||
}
|
}
|
||||||
@ -1313,6 +1316,9 @@ export namespace MerchantBackend {
|
|||||||
// This parameter is optional.
|
// This parameter is optional.
|
||||||
pos_key?: string;
|
pos_key?: string;
|
||||||
|
|
||||||
|
// Algorithm for computing the POS confirmation, 0 for none.
|
||||||
|
pos_algorithm?: Integer;
|
||||||
|
|
||||||
// Additional information in a separate template.
|
// Additional information in a separate template.
|
||||||
template_contract: TemplateContractDetails;
|
template_contract: TemplateContractDetails;
|
||||||
}
|
}
|
||||||
@ -1338,6 +1344,9 @@ export namespace MerchantBackend {
|
|||||||
// This parameter is optional.
|
// This parameter is optional.
|
||||||
pos_key?: string;
|
pos_key?: string;
|
||||||
|
|
||||||
|
// Algorithm for computing the POS confirmation, 0 for none.
|
||||||
|
pos_algorithm?: Integer;
|
||||||
|
|
||||||
// Additional information in a separate template.
|
// Additional information in a separate template.
|
||||||
template_contract: TemplateContractDetails;
|
template_contract: TemplateContractDetails;
|
||||||
}
|
}
|
||||||
|
@ -244,7 +244,11 @@ export function useTemplateDetails(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (isValidating) return { loading: true, data: data?.data };
|
if (isValidating) return { loading: true, data: data?.data };
|
||||||
if (data) return data;
|
if (data) {
|
||||||
|
const d = structuredClone(data);
|
||||||
|
d.data.pos_algorithm = 1;
|
||||||
|
return d;
|
||||||
|
}
|
||||||
if (error) return error.info;
|
if (error) return error.info;
|
||||||
return { loading: true };
|
return { loading: true };
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,11 @@ import { Input } from "../../../../components/form/Input.js";
|
|||||||
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
||||||
import { InputDuration } from "../../../../components/form/InputDuration.js";
|
import { InputDuration } from "../../../../components/form/InputDuration.js";
|
||||||
import { InputNumber } from "../../../../components/form/InputNumber.js";
|
import { InputNumber } from "../../../../components/form/InputNumber.js";
|
||||||
|
import { InputSelector } from "../../../../components/form/InputSelector.js";
|
||||||
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
||||||
import { useBackendContext } from "../../../../context/backend.js";
|
import { useBackendContext } from "../../../../context/backend.js";
|
||||||
import { MerchantBackend } from "../../../../declaration.js";
|
import { MerchantBackend } from "../../../../declaration.js";
|
||||||
|
import { randomBase32Key } from "../../../../utils/crypto.js";
|
||||||
import { undefinedIfEmpty } from "../../../../utils/table.js";
|
import { undefinedIfEmpty } from "../../../../utils/table.js";
|
||||||
|
|
||||||
type Entity = MerchantBackend.Template.TemplateAddDetails;
|
type Entity = MerchantBackend.Template.TemplateAddDetails;
|
||||||
@ -43,6 +45,13 @@ interface Props {
|
|||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const algorithms = ["0", "1", "2"];
|
||||||
|
const algorithmsNames = [
|
||||||
|
"off",
|
||||||
|
"30s 8d TOTP-SHA1 without amount",
|
||||||
|
"30s 8d eTOTP-SHA1 with amount",
|
||||||
|
];
|
||||||
|
|
||||||
export function CreatePage({ onCreate, onBack }: Props): VNode {
|
export function CreatePage({ onCreate, onBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const backend = useBackendContext();
|
const backend = useBackendContext();
|
||||||
@ -104,7 +113,6 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
label={i18n.str`Identifier`}
|
label={i18n.str`Identifier`}
|
||||||
tooltip={i18n.str`Name of the template in URLs.`}
|
tooltip={i18n.str`Name of the template in URLs.`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input<Entity>
|
<Input<Entity>
|
||||||
name="template_description"
|
name="template_description"
|
||||||
label={i18n.str`Description`}
|
label={i18n.str`Description`}
|
||||||
@ -134,12 +142,35 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
|
|||||||
help=""
|
help=""
|
||||||
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
|
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
|
||||||
/>
|
/>
|
||||||
<Input<Entity>
|
<InputSelector<Entity>
|
||||||
name="pos_key"
|
name="pos_algorithm"
|
||||||
label={i18n.str`Point-of-sale key`}
|
label={i18n.str`Veritifaction algorithm`}
|
||||||
help=""
|
tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}
|
||||||
tooltip={i18n.str`Useful to validate the purchase`}
|
values={algorithms}
|
||||||
|
toStr={(v) => algorithmsNames[v]}
|
||||||
|
convert={(v) => Number(v)}
|
||||||
/>
|
/>
|
||||||
|
{state.pos_algorithm && state.pos_algorithm > 0 ? (
|
||||||
|
<Input<Entity>
|
||||||
|
name="pos_key"
|
||||||
|
label={i18n.str`Point-of-sale key`}
|
||||||
|
help=""
|
||||||
|
tooltip={i18n.str`Useful to validate the purchase`}
|
||||||
|
side={
|
||||||
|
<span data-tooltip={i18n.str`generate random secret key`}>
|
||||||
|
<button
|
||||||
|
class="button is-info mr-3"
|
||||||
|
onClick={(e) => {
|
||||||
|
const pos_key = randomBase32Key();
|
||||||
|
setState((s) => ({ ...s, pos_key }));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i18n.Translate>random</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
<div class="buttons is-right mt-5">
|
<div class="buttons is-right mt-5">
|
||||||
|
@ -31,8 +31,10 @@ import {
|
|||||||
} from "../../../../components/form/FormProvider.js";
|
} from "../../../../components/form/FormProvider.js";
|
||||||
import { Input } from "../../../../components/form/Input.js";
|
import { Input } from "../../../../components/form/Input.js";
|
||||||
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
||||||
|
import { ConfirmModal } from "../../../../components/modal/index.js";
|
||||||
import { useBackendContext } from "../../../../context/backend.js";
|
import { useBackendContext } from "../../../../context/backend.js";
|
||||||
import { useConfigContext } from "../../../../context/config.js";
|
import { useConfigContext } from "../../../../context/config.js";
|
||||||
|
import { useInstanceContext } from "../../../../context/instance.js";
|
||||||
import { MerchantBackend } from "../../../../declaration.js";
|
import { MerchantBackend } from "../../../../declaration.js";
|
||||||
|
|
||||||
type Entity = MerchantBackend.Template.UsingTemplateDetails;
|
type Entity = MerchantBackend.Template.UsingTemplateDetails;
|
||||||
@ -46,7 +48,9 @@ interface Props {
|
|||||||
export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const { url: backendUrl } = useBackendContext();
|
const { url: backendUrl } = useBackendContext();
|
||||||
|
const { id: instanceId } = useInstanceContext();
|
||||||
const config = useConfigContext();
|
const config = useConfigContext();
|
||||||
|
const [setupTOTP, setSetupTOTP] = useState(false);
|
||||||
|
|
||||||
const [state, setState] = useState<Partial<Entity>>({
|
const [state, setState] = useState<Partial<Entity>>({
|
||||||
amount: template.template_contract.amount,
|
amount: template.template_contract.amount,
|
||||||
@ -82,8 +86,33 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
|||||||
|
|
||||||
const payTemplateUri = `${talerProto}//pay-template/${merchantURL.hostname}/${templateId}${paramsStr}`;
|
const payTemplateUri = `${talerProto}//pay-template/${merchantURL.hostname}/${templateId}${paramsStr}`;
|
||||||
|
|
||||||
|
const issuer = encodeURIComponent(
|
||||||
|
`${new URL(backendUrl).hostname}/${instanceId}`,
|
||||||
|
);
|
||||||
|
const oauthUri = !template.pos_algorithm
|
||||||
|
? undefined
|
||||||
|
: template.pos_algorithm === 1
|
||||||
|
? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
|
||||||
|
: template.pos_algorithm === 2
|
||||||
|
? `otpauth://totp/${issuer}:${templateId}?secret=${template.pos_key}&issuer=${issuer}&algorithm=SHA1&digits=8&period=30`
|
||||||
|
: undefined;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
{oauthUri && (
|
||||||
|
<ConfirmModal
|
||||||
|
description="Setup TOTP"
|
||||||
|
active={setupTOTP}
|
||||||
|
onConfirm={() => {
|
||||||
|
setSetupTOTP(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<p>Scan this qr code with your TOTP device</p>
|
||||||
|
<QR text={oauthUri} />
|
||||||
|
<pre style={{ textAlign: "center" }}>
|
||||||
|
<a href={oauthUri}>{oauthUri}</a>
|
||||||
|
</pre>
|
||||||
|
</ConfirmModal>
|
||||||
|
)}
|
||||||
<section class="section is-main-section">
|
<section class="section is-main-section">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" />
|
<div class="column" />
|
||||||
@ -114,20 +143,48 @@ export function QrPage({ template, id: templateId, onBack }: Props): VNode {
|
|||||||
<i18n.Translate>Cancel</i18n.Translate>
|
<i18n.Translate>Cancel</i18n.Translate>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button class="button is-info" onClick={onBack}>
|
<button
|
||||||
|
class="button is-info"
|
||||||
|
onClick={() => saveAsPDF(templateId)}
|
||||||
|
>
|
||||||
<i18n.Translate>Print</i18n.Translate>
|
<i18n.Translate>Print</i18n.Translate>
|
||||||
</button>
|
</button>
|
||||||
|
{oauthUri && (
|
||||||
|
<button
|
||||||
|
class="button is-info"
|
||||||
|
onClick={() => setSetupTOTP(true)}
|
||||||
|
>
|
||||||
|
<i18n.Translate>Setup TOTP</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column" />
|
<div class="column" />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section id="printThis">
|
||||||
<pre>
|
<QR text={payTemplateUri} />
|
||||||
|
<pre style={{ textAlign: "center" }}>
|
||||||
<a href={payTemplateUri}>{payTemplateUri}</a>
|
<a href={payTemplateUri}>{payTemplateUri}</a>
|
||||||
</pre>
|
</pre>
|
||||||
<QR text={payTemplateUri} />
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function saveAsPDF(name: string): void {
|
||||||
|
const printWindow = window.open("", "", "height=400,width=800");
|
||||||
|
if (!printWindow) return;
|
||||||
|
const divContents = document.getElementById("printThis");
|
||||||
|
if (!divContents) return;
|
||||||
|
printWindow.document.write(
|
||||||
|
`<html><head><title>Order template for ${name}</title><style>`,
|
||||||
|
);
|
||||||
|
printWindow.document.write("</style></head><body> </body></html>");
|
||||||
|
printWindow.document.close();
|
||||||
|
printWindow.document.body.appendChild(divContents.cloneNode(true));
|
||||||
|
printWindow.addEventListener("load", () => {
|
||||||
|
printWindow.print();
|
||||||
|
printWindow.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -51,10 +51,8 @@ export default function TemplateQrPage({
|
|||||||
onNotFound,
|
onNotFound,
|
||||||
onUnauthorized,
|
onUnauthorized,
|
||||||
}: Props): VNode {
|
}: Props): VNode {
|
||||||
const { createOrderFromTemplate } = useTemplateAPI();
|
|
||||||
const result = useTemplateDetails(tid);
|
const result = useTemplateDetails(tid);
|
||||||
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
const [notif, setNotif] = useState<Notification | undefined>(undefined);
|
||||||
const { i18n } = useTranslationContext();
|
|
||||||
|
|
||||||
if (result.clientError && result.isUnauthorized) return onUnauthorized();
|
if (result.clientError && result.isUnauthorized) return onUnauthorized();
|
||||||
if (result.clientError && result.isNotfound) return onNotFound();
|
if (result.clientError && result.isNotfound) return onNotFound();
|
||||||
|
@ -31,9 +31,11 @@ import { Input } from "../../../../components/form/Input.js";
|
|||||||
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
import { InputCurrency } from "../../../../components/form/InputCurrency.js";
|
||||||
import { InputDuration } from "../../../../components/form/InputDuration.js";
|
import { InputDuration } from "../../../../components/form/InputDuration.js";
|
||||||
import { InputNumber } from "../../../../components/form/InputNumber.js";
|
import { InputNumber } from "../../../../components/form/InputNumber.js";
|
||||||
|
import { InputSelector } from "../../../../components/form/InputSelector.js";
|
||||||
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
import { InputWithAddon } from "../../../../components/form/InputWithAddon.js";
|
||||||
import { useBackendContext } from "../../../../context/backend.js";
|
import { useBackendContext } from "../../../../context/backend.js";
|
||||||
import { MerchantBackend, WithId } from "../../../../declaration.js";
|
import { MerchantBackend, WithId } from "../../../../declaration.js";
|
||||||
|
import { randomBase32Key } from "../../../../utils/crypto.js";
|
||||||
import { undefinedIfEmpty } from "../../../../utils/table.js";
|
import { undefinedIfEmpty } from "../../../../utils/table.js";
|
||||||
|
|
||||||
type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
|
type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId;
|
||||||
@ -44,6 +46,13 @@ interface Props {
|
|||||||
template: Entity;
|
template: Entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const algorithms = ["0", "1", "2"];
|
||||||
|
const algorithmsNames = [
|
||||||
|
"off",
|
||||||
|
"30s 8d TOTP-SHA1 without amount",
|
||||||
|
"30s 8d eTOTP-SHA1 with amount",
|
||||||
|
];
|
||||||
|
|
||||||
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
const backend = useBackendContext();
|
const backend = useBackendContext();
|
||||||
@ -143,12 +152,35 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode {
|
|||||||
help=""
|
help=""
|
||||||
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
|
tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`}
|
||||||
/>
|
/>
|
||||||
<Input<Entity>
|
<InputSelector<Entity>
|
||||||
name="pos_key"
|
name="pos_algorithm"
|
||||||
label={i18n.str`Point-of-sale key`}
|
label={i18n.str`Veritifaction algorithm`}
|
||||||
help=""
|
tooltip={i18n.str`Algorithm to use to verify transaction in offline mode`}
|
||||||
tooltip={i18n.str`Useful to validate the purchase`}
|
values={algorithms}
|
||||||
|
toStr={(v) => algorithmsNames[v]}
|
||||||
|
convert={(v) => Number(v)}
|
||||||
/>
|
/>
|
||||||
|
{state.pos_algorithm && state.pos_algorithm > 0 ? (
|
||||||
|
<Input<Entity>
|
||||||
|
name="pos_key"
|
||||||
|
label={i18n.str`Point-of-sale key`}
|
||||||
|
help=""
|
||||||
|
tooltip={i18n.str`Useful to validate the purchase`}
|
||||||
|
side={
|
||||||
|
<span data-tooltip={i18n.str`generate random secret key`}>
|
||||||
|
<button
|
||||||
|
class="button is-info mr-3"
|
||||||
|
onClick={(e) => {
|
||||||
|
const pos_key = randomBase32Key();
|
||||||
|
setState((s) => ({ ...s, pos_key }));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i18n.Translate>random</i18n.Translate>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : undefined}
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|
||||||
<div class="buttons is-right mt-5">
|
<div class="buttons is-right mt-5">
|
||||||
|
@ -34,12 +34,13 @@ import { MerchantBackend } from "../../../../declaration.js";
|
|||||||
type Entity = MerchantBackend.Template.UsingTemplateDetails;
|
type Entity = MerchantBackend.Template.UsingTemplateDetails;
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
id: string;
|
||||||
template: MerchantBackend.Template.TemplateDetails;
|
template: MerchantBackend.Template.TemplateDetails;
|
||||||
onCreateOrder: (d: Entity) => Promise<void>;
|
onCreateOrder: (d: Entity) => Promise<void>;
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UsePage({ template, onCreateOrder, onBack }: Props): VNode {
|
export function UsePage({ id, template, onCreateOrder, onBack }: Props): VNode {
|
||||||
const { i18n } = useTranslationContext();
|
const { i18n } = useTranslationContext();
|
||||||
|
|
||||||
const [state, setState] = useState<Partial<Entity>>({
|
const [state, setState] = useState<Partial<Entity>>({
|
||||||
@ -75,6 +76,22 @@ export function UsePage({ template, onCreateOrder, onBack }: Props): VNode {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<section class="section">
|
||||||
|
<section class="hero is-hero-bar">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="level">
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<span class="is-size-4">
|
||||||
|
<i18n.Translate>New order for template</i18n.Translate>:{" "}
|
||||||
|
<b>{id}</b>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
<section class="section is-main-section">
|
<section class="section is-main-section">
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column" />
|
<div class="column" />
|
||||||
|
@ -68,6 +68,7 @@ export default function TemplateUsePage({
|
|||||||
<NotificationCard notification={notif} />
|
<NotificationCard notification={notif} />
|
||||||
<UsePage
|
<UsePage
|
||||||
template={result.data}
|
template={result.data}
|
||||||
|
id={tid}
|
||||||
onBack={onBack}
|
onBack={onBack}
|
||||||
onCreateOrder={(
|
onCreateOrder={(
|
||||||
request: MerchantBackend.Template.UsingTemplateDetails,
|
request: MerchantBackend.Template.UsingTemplateDetails,
|
||||||
|
53
packages/merchant-backoffice-ui/src/utils/crypto.ts
Normal file
53
packages/merchant-backoffice-ui/src/utils/crypto.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
const encTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
// base32 RFC 3548
|
||||||
|
function encodeBase32(data: ArrayBuffer) {
|
||||||
|
const dataBytes = new Uint8Array(data);
|
||||||
|
let sb = "";
|
||||||
|
const size = data.byteLength;
|
||||||
|
let bitBuf = 0;
|
||||||
|
let numBits = 0;
|
||||||
|
let pos = 0;
|
||||||
|
while (pos < size || numBits > 0) {
|
||||||
|
if (pos < size && numBits < 5) {
|
||||||
|
const d = dataBytes[pos++];
|
||||||
|
bitBuf = (bitBuf << 8) | d;
|
||||||
|
numBits += 8;
|
||||||
|
}
|
||||||
|
if (numBits < 5) {
|
||||||
|
// zero-padding
|
||||||
|
bitBuf = bitBuf << (5 - numBits);
|
||||||
|
numBits = 5;
|
||||||
|
}
|
||||||
|
const v = (bitBuf >>> (numBits - 5)) & 31;
|
||||||
|
sb += encTable[v];
|
||||||
|
numBits -= 5;
|
||||||
|
}
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function randomBase32Key(): string {
|
||||||
|
var buf = new Uint8Array(20);
|
||||||
|
window.crypto.getRandomValues(buf);
|
||||||
|
return encodeBase32(buf);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user