test login with an endpoint and cleaner calculation
This commit is contained in:
parent
0700bbe9d1
commit
0bf92a44df
@ -16,6 +16,7 @@
|
||||
|
||||
import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
|
||||
import {
|
||||
ErrorType,
|
||||
RequestError,
|
||||
useLocalStorage,
|
||||
} from "@gnu-taler/web-util/lib/index.browser";
|
||||
@ -192,6 +193,33 @@ export function usePublicBackend(): useBackendType {
|
||||
};
|
||||
}
|
||||
|
||||
export function useCredentialsChecker() {
|
||||
const { request } = useApiContext();
|
||||
const baseUrl = getInitialBackendBaseURL();
|
||||
//check against account details endpoint
|
||||
//while sandbox backend doesn't have a login endpoint
|
||||
return async function testLogin(
|
||||
username: string,
|
||||
password: string,
|
||||
): Promise<{
|
||||
valid: boolean;
|
||||
cause?: ErrorType;
|
||||
}> {
|
||||
try {
|
||||
await request(baseUrl, `access-api/accounts/${username}/`, {
|
||||
basicAuth: { username, password },
|
||||
preventCache: true,
|
||||
});
|
||||
return { valid: true };
|
||||
} catch (error) {
|
||||
if (error instanceof RequestError) {
|
||||
return { valid: false, cause: error.cause.type };
|
||||
}
|
||||
return { valid: false, cause: ErrorType.UNEXPECTED };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function useAuthenticatedBackend(): useBackendType {
|
||||
const { state } = useBackendContext();
|
||||
const { request: requestHandler } = useApiContext();
|
||||
|
@ -288,9 +288,10 @@ export function useRatiosAndFeeConfig(): HttpResponse<
|
||||
HttpResponseOk<SandboxBackend.Circuit.Config>,
|
||||
RequestError<SandboxBackend.SandboxError>
|
||||
>([`circuit-api/config`], fetcher, {
|
||||
refreshInterval: 1000,
|
||||
refreshInterval: 60 * 1000,
|
||||
refreshWhenHidden: false,
|
||||
revalidateOnFocus: false,
|
||||
revalidateIfStale: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshWhenOffline: false,
|
||||
errorRetryCount: 0,
|
||||
|
@ -36,9 +36,9 @@ import {
|
||||
} from "../context/pageState.js";
|
||||
import { useAccountDetails } from "../hooks/access.js";
|
||||
import {
|
||||
useAdminAccountAPI,
|
||||
useBusinessAccountDetails,
|
||||
useBusinessAccounts,
|
||||
useAdminAccountAPI,
|
||||
} from "../hooks/circuit.js";
|
||||
import {
|
||||
buildRequestErrorMessage,
|
||||
@ -50,7 +50,6 @@ import {
|
||||
} from "../utils.js";
|
||||
import { ErrorBannerFloat } from "./BankFrame.js";
|
||||
import { ShowCashoutDetails } from "./BusinessAccount.js";
|
||||
import { PaymentOptions } from "./PaymentOptions.js";
|
||||
import { PaytoWireTransferForm } from "./PaytoWireTransferForm.js";
|
||||
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
||||
|
||||
@ -581,7 +580,6 @@ function CreateNewAccount({
|
||||
template={undefined}
|
||||
purpose="create"
|
||||
onChange={(a) => {
|
||||
console.log(a);
|
||||
setSubmitAccount(a);
|
||||
}}
|
||||
/>
|
||||
@ -831,6 +829,7 @@ function RemoveAccount({
|
||||
title: i18n.str`Can't delete the account`,
|
||||
description: i18n.str`Balance is not empty`,
|
||||
}}
|
||||
onClear={() => saveError(undefined)}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
|
@ -237,6 +237,7 @@ function CreateCashout({
|
||||
if (!result.ok) return onLoadNotOk(result);
|
||||
if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
|
||||
const config = ratiosResult.data;
|
||||
|
||||
const balance = Amounts.parseOrThrow(result.data.balance.amount);
|
||||
const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
|
||||
const zero = Amounts.zeroOfCurrency(balance.currency);
|
||||
@ -254,23 +255,14 @@ function CreateCashout({
|
||||
if (!sellRate || sellRate < 0) return <div>error rate</div>;
|
||||
|
||||
const amount = Amounts.parse(`${balance.currency}:${form.amount}`);
|
||||
const amount_debit = !amount
|
||||
? zero
|
||||
: form.isDebit
|
||||
? amount
|
||||
: truncate(Amounts.divide(Amounts.add(amount, sellFee).amount, sellRate));
|
||||
const credit_before_fee = !amount
|
||||
? zero
|
||||
: form.isDebit
|
||||
? truncate(Amounts.divide(amount, 1 / sellRate))
|
||||
: Amounts.add(amount, sellFee).amount;
|
||||
|
||||
const __amount_credit = Amounts.sub(credit_before_fee, sellFee).amount;
|
||||
const amount_credit = Amounts.parseOrThrow(
|
||||
`${fiatCurrency}:${Amounts.stringifyValue(__amount_credit)}`,
|
||||
);
|
||||
const calc = !amount
|
||||
? { debit: zero, credit: zero, beforeFee: zero }
|
||||
: !form.isDebit
|
||||
? calculateFromCredit(amount, sellFee, sellRate)
|
||||
: calculateFromDebit(amount, sellFee, sellRate);
|
||||
|
||||
const balanceAfter = Amounts.sub(balance, amount_debit).amount;
|
||||
const balanceAfter = Amounts.sub(balance, calc.debit).amount;
|
||||
|
||||
function updateForm(newForm: typeof form): void {
|
||||
setForm(newForm);
|
||||
@ -280,11 +272,11 @@ function CreateCashout({
|
||||
? i18n.str`required`
|
||||
: !amount
|
||||
? i18n.str`could not be parsed`
|
||||
: Amounts.cmp(limit, amount_debit) === -1
|
||||
: Amounts.cmp(limit, calc.debit) === -1
|
||||
? i18n.str`balance is not enough`
|
||||
: Amounts.cmp(credit_before_fee, sellFee) === -1
|
||||
: Amounts.cmp(calc.beforeFee, sellFee) === -1
|
||||
? i18n.str`the total amount to transfer does not cover the fees`
|
||||
: Amounts.isZero(amount_credit)
|
||||
: Amounts.isZero(calc.credit)
|
||||
? i18n.str`the total transfer at destination will be zero`
|
||||
: undefined,
|
||||
channel: !form.channel ? i18n.str`required` : undefined,
|
||||
@ -408,7 +400,7 @@ function CreateCashout({
|
||||
id="withdraw-amount"
|
||||
disabled
|
||||
name="withdraw-amount"
|
||||
value={amount_debit ? Amounts.stringifyValue(amount_debit) : ""}
|
||||
value={Amounts.stringifyValue(calc.debit)}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -454,7 +446,7 @@ function CreateCashout({
|
||||
// type="number"
|
||||
style={{ color: "black" }}
|
||||
disabled
|
||||
value={Amounts.stringifyValue(credit_before_fee)}
|
||||
value={Amounts.stringifyValue(calc.beforeFee)}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -503,7 +495,7 @@ function CreateCashout({
|
||||
id="withdraw-amount"
|
||||
disabled
|
||||
name="withdraw-amount"
|
||||
value={amount_credit ? Amounts.stringifyValue(amount_credit) : ""}
|
||||
value={Amounts.stringifyValue(calc.credit)}
|
||||
/>
|
||||
</div>
|
||||
</fieldset>
|
||||
@ -584,8 +576,10 @@ function CreateCashout({
|
||||
if (errors) return;
|
||||
try {
|
||||
const res = await createCashout({
|
||||
amount_credit: Amounts.stringify(amount_credit),
|
||||
amount_debit: Amounts.stringify(amount_debit),
|
||||
amount_credit: `${fiatCurrency}:${Amounts.stringifyValue(
|
||||
calc.credit,
|
||||
)}`,
|
||||
amount_debit: Amounts.stringify(calc.debit),
|
||||
subject: form.subject,
|
||||
tan_channel: form.channel,
|
||||
});
|
||||
@ -630,25 +624,6 @@ function CreateCashout({
|
||||
);
|
||||
}
|
||||
|
||||
const MAX_AMOUNT_DIGIT = 2;
|
||||
/**
|
||||
* Truncate the amount of digits to display
|
||||
* in the form based on the fee calculations
|
||||
*
|
||||
* Backend must have the same truncation
|
||||
* @param a
|
||||
* @returns
|
||||
*/
|
||||
function truncate(a: AmountJson): AmountJson {
|
||||
const str = Amounts.stringify(a);
|
||||
const idx = str.indexOf(".");
|
||||
if (idx === -1) {
|
||||
return a;
|
||||
}
|
||||
const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
|
||||
return Amounts.parseOrThrow(truncated);
|
||||
}
|
||||
|
||||
interface ShowCashoutProps {
|
||||
id: string;
|
||||
onCancel: () => void;
|
||||
@ -836,6 +811,58 @@ export function ShowCashoutDetails({
|
||||
);
|
||||
}
|
||||
|
||||
const MAX_AMOUNT_DIGIT = 2;
|
||||
/**
|
||||
* Truncate the amount of digits to display
|
||||
* in the form based on the fee calculations
|
||||
*
|
||||
* Backend must have the same truncation
|
||||
* @param a
|
||||
* @returns
|
||||
*/
|
||||
function truncate(a: AmountJson): AmountJson {
|
||||
const str = Amounts.stringify(a);
|
||||
const idx = str.indexOf(".");
|
||||
if (idx === -1) {
|
||||
return a;
|
||||
}
|
||||
const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
|
||||
return Amounts.parseOrThrow(truncated);
|
||||
}
|
||||
|
||||
type TransferCalculation = {
|
||||
debit: AmountJson;
|
||||
credit: AmountJson;
|
||||
beforeFee: AmountJson;
|
||||
};
|
||||
|
||||
function calculateFromDebit(
|
||||
amount: AmountJson,
|
||||
sellFee: AmountJson,
|
||||
sellRate: number,
|
||||
): TransferCalculation {
|
||||
const debit = amount;
|
||||
|
||||
const beforeFee = truncate(Amounts.divide(debit, 1 / sellRate));
|
||||
|
||||
const credit = Amounts.sub(beforeFee, sellFee).amount;
|
||||
return { debit, credit, beforeFee };
|
||||
}
|
||||
|
||||
function calculateFromCredit(
|
||||
amount: AmountJson,
|
||||
sellFee: AmountJson,
|
||||
sellRate: number,
|
||||
): TransferCalculation {
|
||||
const credit = amount;
|
||||
|
||||
const beforeFee = Amounts.add(credit, sellFee).amount;
|
||||
|
||||
const debit = truncate(Amounts.divide(beforeFee, sellRate));
|
||||
|
||||
return { debit, credit, beforeFee };
|
||||
}
|
||||
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Didn't expect to get here");
|
||||
}
|
||||
|
@ -14,12 +14,18 @@
|
||||
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser";
|
||||
import {
|
||||
ErrorType,
|
||||
useTranslationContext,
|
||||
} from "@gnu-taler/web-util/lib/index.browser";
|
||||
import { Fragment, h, VNode } from "preact";
|
||||
import { useEffect, useRef, useState } from "preact/hooks";
|
||||
import { useBackendContext } from "../context/backend.js";
|
||||
import { ErrorMessage } from "../context/pageState.js";
|
||||
import { useCredentialsChecker } from "../hooks/backend.js";
|
||||
import { bankUiSettings } from "../settings.js";
|
||||
import { undefinedIfEmpty } from "../utils.js";
|
||||
import { ErrorBannerFloat } from "./BankFrame.js";
|
||||
import { USERNAME_REGEX } from "./RegistrationPage.js";
|
||||
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";
|
||||
|
||||
@ -31,6 +37,8 @@ export function LoginForm({ onRegister }: { onRegister: () => void }): VNode {
|
||||
const [username, setUsername] = useState<string | undefined>();
|
||||
const [password, setPassword] = useState<string | undefined>();
|
||||
const { i18n } = useTranslationContext();
|
||||
const testLogin = useCredentialsChecker();
|
||||
const [error, saveError] = useState<ErrorMessage | undefined>();
|
||||
const ref = useRef<HTMLInputElement>(null);
|
||||
useEffect(function focusInput() {
|
||||
ref.current?.focus();
|
||||
@ -48,6 +56,9 @@ export function LoginForm({ onRegister }: { onRegister: () => void }): VNode {
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
|
||||
{error && (
|
||||
<ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
|
||||
)}
|
||||
<div class="login-div">
|
||||
<form
|
||||
class="login-form"
|
||||
@ -105,10 +116,41 @@ export function LoginForm({ onRegister }: { onRegister: () => void }): VNode {
|
||||
type="submit"
|
||||
class="pure-button pure-button-primary"
|
||||
disabled={!!errors}
|
||||
onClick={(e) => {
|
||||
onClick={async (e) => {
|
||||
e.preventDefault();
|
||||
if (!username || !password) return;
|
||||
backend.logIn({ username, password });
|
||||
const { valid, cause } = await testLogin(username, password);
|
||||
if (valid) {
|
||||
backend.logIn({ username, password });
|
||||
} else {
|
||||
switch (cause) {
|
||||
case ErrorType.CLIENT: {
|
||||
saveError({
|
||||
title: i18n.str`Wrong credentials or username`,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ErrorType.SERVER: {
|
||||
saveError({
|
||||
title: i18n.str`Server had a problem, try again later or report.`,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ErrorType.TIMEOUT: {
|
||||
saveError({
|
||||
title: i18n.str`Could not reach the server, please report.`,
|
||||
});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
saveError({
|
||||
title: i18n.str`Unexpected error, please report.`,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
backend.logOut();
|
||||
}
|
||||
setUsername(undefined);
|
||||
setPassword(undefined);
|
||||
}}
|
||||
|
Loading…
Reference in New Issue
Block a user