new libeufin api

This commit is contained in:
Sebastian 2023-09-25 09:31:17 -03:00
parent fd9ed97fdc
commit 0b2c03dc5e
No known key found for this signature in database
GPG Key ID: 173909D1A5F66069
13 changed files with 60 additions and 88 deletions

View File

@ -319,11 +319,10 @@ namespace SandboxBackend {
interface ListBankAccountsResponse { interface ListBankAccountsResponse {
accounts: AccountMinimalData[]; accounts: AccountMinimalData[];
} }
// interface Balance { interface Balance {
// amount: Amount; amount: Amount;
// credit_debit_indicator: "credit" | "debit"; credit_debit_indicator: "credit" | "debit";
// } }
type Balance = Amount
interface AccountMinimalData { interface AccountMinimalData {
// Username // Username
username: string; username: string;

View File

@ -40,6 +40,7 @@ import { useCallback, useEffect, useState } from "preact/hooks";
import { useSWRConfig } from "swr"; import { useSWRConfig } from "swr";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { bankUiSettings } from "../settings.js"; import { bankUiSettings } from "../settings.js";
import { AccessToken } from "./useCredentialsChecker.js";
/** /**
* Has the information to reach and * Has the information to reach and
@ -49,7 +50,7 @@ export type BackendState = LoggedIn | LoggedOut;
export interface BackendCredentials { export interface BackendCredentials {
username: string; username: string;
password: string; token: AccessToken;
} }
interface LoggedIn extends BackendCredentials { interface LoggedIn extends BackendCredentials {
@ -64,7 +65,7 @@ export const codecForBackendStateLoggedIn = (): Codec<LoggedIn> =>
buildCodecForObject<LoggedIn>() buildCodecForObject<LoggedIn>()
.property("status", codecForConstString("loggedIn")) .property("status", codecForConstString("loggedIn"))
.property("username", codecForString()) .property("username", codecForString())
.property("password", codecForString()) .property("token", codecForString() as Codec<AccessToken>)
.property("isUserAdministrator", codecForBoolean()) .property("isUserAdministrator", codecForBoolean())
.build("BackendState.LoggedIn"); .build("BackendState.LoggedIn");
@ -255,35 +256,11 @@ interface InvalidationResult {
error: unknown; error: unknown;
} }
export function useCredentialsCheckerOld() {
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<CheckResult> {
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, requestError: true, cause: error.cause };
}
return { valid: false, requestError: false, error };
}
};
}
export function useAuthenticatedBackend(): useBackendType { export function useAuthenticatedBackend(): useBackendType {
const { state } = useBackendContext(); const { state } = useBackendContext();
const { request: requestHandler } = useApiContext(); const { request: requestHandler } = useApiContext();
const creds = state.status === "loggedIn" ? state : undefined; const creds = state.status === "loggedIn" ? state.token : undefined;
const baseUrl = getInitialBackendBaseURL(); const baseUrl = getInitialBackendBaseURL();
const request = useCallback( const request = useCallback(
@ -291,14 +268,14 @@ export function useAuthenticatedBackend(): useBackendType {
path: string, path: string,
options: RequestOptions = {}, options: RequestOptions = {},
): Promise<HttpResponseOk<T>> { ): Promise<HttpResponseOk<T>> {
return requestHandler<T>(baseUrl, path, { basicAuth: creds, ...options }); return requestHandler<T>(baseUrl, path, { token: creds, ...options });
}, },
[baseUrl, creds], [baseUrl, creds],
); );
const fetcher = useCallback( const fetcher = useCallback(
function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> { function fetcherImpl<T>(endpoint: string): Promise<HttpResponseOk<T>> {
return requestHandler<T>(baseUrl, endpoint, { basicAuth: creds }); return requestHandler<T>(baseUrl, endpoint, { token: creds });
}, },
[baseUrl, creds], [baseUrl, creds],
); );
@ -309,7 +286,7 @@ export function useAuthenticatedBackend(): useBackendType {
number, number,
]): Promise<HttpResponseOk<T>> { ]): Promise<HttpResponseOk<T>> {
return requestHandler<T>(baseUrl, endpoint, { return requestHandler<T>(baseUrl, endpoint, {
basicAuth: creds, token: creds,
params: { delta: size, start: size * page }, params: { delta: size, start: size * page },
}); });
}, },
@ -321,7 +298,7 @@ export function useAuthenticatedBackend(): useBackendType {
> { > {
return Promise.all( return Promise.all(
endpoints.map((endpoint) => endpoints.map((endpoint) =>
requestHandler<T>(baseUrl, endpoint, { basicAuth: creds }), requestHandler<T>(baseUrl, endpoint, { token: creds }),
), ),
); );
}, },
@ -335,7 +312,7 @@ export function useAuthenticatedBackend(): useBackendType {
string, string,
]): Promise<HttpResponseOk<T>> { ]): Promise<HttpResponseOk<T>> {
return requestHandler<T>(baseUrl, endpoint, { return requestHandler<T>(baseUrl, endpoint, {
basicAuth: creds, token: creds,
params: { page: page || 1, size }, params: { page: page || 1, size },
}); });
}, },
@ -347,7 +324,7 @@ export function useAuthenticatedBackend(): useBackendType {
HttpResponseOk<T> HttpResponseOk<T>
> { > {
return requestHandler<T>(baseUrl, endpoint, { return requestHandler<T>(baseUrl, endpoint, {
basicAuth: creds, token: creds,
params: { account }, params: { account },
}); });
}, },

View File

@ -33,6 +33,7 @@ import {
// FIX default import https://github.com/microsoft/TypeScript/issues/49189 // FIX default import https://github.com/microsoft/TypeScript/issues/49189
import _useSWR, { SWRHook } from "swr"; import _useSWR, { SWRHook } from "swr";
import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util";
import { AccessToken } from "./useCredentialsChecker.js";
const useSWR = _useSWR as unknown as SWRHook; const useSWR = _useSWR as unknown as SWRHook;
export function useAdminAccountAPI(): AdminAccountAPI { export function useAdminAccountAPI(): AdminAccountAPI {
@ -90,7 +91,8 @@ export function useAdminAccountAPI(): AdminAccountAPI {
await mutateAll(/.*/); await mutateAll(/.*/);
logIn({ logIn({
username: account, username: account,
password: data.new_password, //FIXME: change password api
token: data.new_password as AccessToken,
}); });
} }
return res; return res;
@ -215,14 +217,15 @@ export interface CircuitAccountAPI {
async function getBusinessStatus( async function getBusinessStatus(
request: ReturnType<typeof useApiContext>["request"], request: ReturnType<typeof useApiContext>["request"],
basicAuth: { username: string; password: string }, username: string,
token: AccessToken,
): Promise<boolean> { ): Promise<boolean> {
try { try {
const url = getInitialBackendBaseURL(); const url = getInitialBackendBaseURL();
const result = await request<SandboxBackend.Circuit.CircuitAccountData>( const result = await request<SandboxBackend.Circuit.CircuitAccountData>(
url, url,
`circuit-api/accounts/${basicAuth.username}`, `circuit-api/accounts/${username}`,
{ basicAuth }, { token },
); );
return result.ok; return result.ok;
} catch (error) { } catch (error) {
@ -264,10 +267,10 @@ type CashoutEstimators = {
export function useEstimator(): CashoutEstimators { export function useEstimator(): CashoutEstimators {
const { state } = useBackendContext(); const { state } = useBackendContext();
const { request } = useApiContext(); const { request } = useApiContext();
const basicAuth = const creds =
state.status === "loggedOut" state.status === "loggedOut"
? undefined ? undefined
: { username: state.username, password: state.password }; : state.token;
return { return {
estimateByCredit: async (amount, fee, rate) => { estimateByCredit: async (amount, fee, rate) => {
const zeroBalance = Amounts.zeroOfCurrency(fee.currency); const zeroBalance = Amounts.zeroOfCurrency(fee.currency);
@ -282,7 +285,7 @@ export function useEstimator(): CashoutEstimators {
url, url,
`circuit-api/cashouts/estimates`, `circuit-api/cashouts/estimates`,
{ {
basicAuth, token: creds,
params: { params: {
amount_credit: Amounts.stringify(amount), amount_credit: Amounts.stringify(amount),
}, },
@ -313,7 +316,7 @@ export function useEstimator(): CashoutEstimators {
url, url,
`circuit-api/cashouts/estimates`, `circuit-api/cashouts/estimates`,
{ {
basicAuth, token: creds,
params: { params: {
amount_debit: Amounts.stringify(amount), amount_debit: Amounts.stringify(amount),
}, },
@ -339,11 +342,11 @@ export function useBusinessAccountFlag(): boolean | undefined {
const creds = const creds =
state.status === "loggedOut" state.status === "loggedOut"
? undefined ? undefined
: { username: state.username, password: state.password }; : {user: state.username, token: state.token};
useEffect(() => { useEffect(() => {
if (!creds) return; if (!creds) return;
getBusinessStatus(request, creds) getBusinessStatus(request, creds.user, creds.token)
.then((result) => { .then((result) => {
setIsBusiness(result); setIsBusiness(result);
}) })

View File

@ -23,13 +23,13 @@ export function useCredentialsChecker() {
const response = await request<LoginTokenSuccessResponse>(baseUrl, `accounts/${username}/token`, { const response = await request<LoginTokenSuccessResponse>(baseUrl, `accounts/${username}/token`, {
method: "POST", method: "POST",
basicAuth: { basicAuth: {
username: username, username,
password, password,
}, },
data, data,
contentType: "json" contentType: "json"
}); });
return { valid: true, token: response.data.token, expiration: response.data.expiration }; return { valid: true, token: `secret-token:${response.data.access_token}` as AccessToken, expiration: response.data.expiration };
} catch (error) { } catch (error) {
if (error instanceof RequestError) { if (error instanceof RequestError) {
return { valid: false, cause: error.cause }; return { valid: false, cause: error.cause };
@ -76,13 +76,13 @@ export function useCredentialsChecker() {
} }
} }
return requestNewLoginToken(baseUrl, token.token as AccessToken) return requestNewLoginToken(baseUrl, token.token)
} }
return { requestNewLoginToken, refreshLoginToken } return { requestNewLoginToken, refreshLoginToken }
} }
export interface LoginToken { export interface LoginToken {
token: string, token: AccessToken,
expiration: Timestamp, expiration: Timestamp,
} }
// token used to get loginToken // token used to get loginToken
@ -95,7 +95,7 @@ export type AccessToken = string & {
type YesOrNo = "yes" | "no"; type YesOrNo = "yes" | "no";
export type LoginResult = { export type LoginResult = {
valid: true; valid: true;
token: string; token: AccessToken;
expiration: Timestamp; expiration: Timestamp;
} | { } | {
valid: false; valid: false;
@ -121,7 +121,7 @@ export interface LoginTokenSuccessResponse {
// that are in scope for some time. Must be prefixed // that are in scope for some time. Must be prefixed
// with "Bearer " when used in the "Authorization" HTTP header. // with "Bearer " when used in the "Authorization" HTTP header.
// Will already begin with the RFC 8959 prefix. // Will already begin with the RFC 8959 prefix.
token: string; access_token: AccessToken;
// Scope of the token (which kinds of operations it will allow) // Scope of the token (which kinds of operations it will allow)
scope: "readonly" | "write"; scope: "readonly" | "write";

View File

@ -63,9 +63,7 @@ export function useComponentState({ account, goToBusinessAccount, goToConfirmOpe
const { data } = result; const { data } = result;
// FIXME: balance const balance = Amounts.parseOrThrow(data.balance.amount);
// const balance = Amounts.parseOrThrow(data.balance.amount);
const balance = Amounts.parseOrThrow(data.balance);
const debitThreshold = Amounts.parseOrThrow(data.debit_threshold); const debitThreshold = Amounts.parseOrThrow(data.debit_threshold);
const payto = parsePaytoUri(data.payto_uri); const payto = parsePaytoUri(data.payto_uri);

View File

@ -14,16 +14,14 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { Amounts, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser"; import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact"; import { Fragment, VNode, h } from "preact";
import { Transactions } from "../../components/Transactions/index.js"; import { Transactions } from "../../components/Transactions/index.js";
import { PaymentOptions } from "../PaymentOptions.js";
import { State } from "./index.js";
import { CopyButton } from "../../components/CopyButton.js";
import { bankUiSettings } from "../../settings.js";
import { useBusinessAccountDetails } from "../../hooks/circuit.js"; import { useBusinessAccountDetails } from "../../hooks/circuit.js";
import { useSettings } from "../../hooks/settings.js"; import { useSettings } from "../../hooks/settings.js";
import { bankUiSettings } from "../../settings.js";
import { PaymentOptions } from "../PaymentOptions.js";
import { State } from "./index.js";
export function InvalidIbanView({ error }: State.InvalidIban) { export function InvalidIbanView({ error }: State.InvalidIban) {
return ( return (

View File

@ -474,11 +474,9 @@ function AccountBalance({ account }: { account: string }): VNode {
const result = useAccountDetails(account); const result = useAccountDetails(account);
if (!result.ok) return <div /> if (!result.ok) return <div />
// FIXME: balance
return <div> return <div>
{Amounts.currencyOf(result.data.balance)} {Amounts.stringifyValue(result.data.balance)} {Amounts.currencyOf(result.data.balance.amount)}
{/* {Amounts.currencyOf(result.data.balance.amount)}
&nbsp;{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""} &nbsp;{result.data.balance.credit_debit_indicator === "debit" ? "-" : ""}
{Amounts.stringifyValue(result.data.balance.amount)} */} {Amounts.stringifyValue(result.data.balance.amount)}
</div> </div>
} }

View File

@ -18,13 +18,11 @@ import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser"; import { ErrorType, notifyError, useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact"; import { Fragment, VNode, h } from "preact";
import { useEffect, useRef, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useBackendContext } from "../context/backend.js"; import { useBackendContext } from "../context/backend.js";
import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
import { bankUiSettings } from "../settings.js"; import { bankUiSettings } from "../settings.js";
import { undefinedIfEmpty } from "../utils.js"; import { undefinedIfEmpty } from "../utils.js";
import { USERNAME_REGEX } from "./RegistrationPage.js";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { AccessToken, useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
import { useCredentialsCheckerOld } from "../hooks/backend.js";
/** /**
* Collect and submit login data. * Collect and submit login data.
@ -62,7 +60,7 @@ export function LoginForm({ onRegister }: { onRegister?: () => void }): VNode {
setBusy({}) setBusy({})
const result = await requestNewLoginToken(username, password); const result = await requestNewLoginToken(username, password);
if (result.valid) { if (result.valid) {
backend.logIn({ username, password }); backend.logIn({ username, token: result.token });
} else { } else {
const { cause } = result; const { cause } = result;
switch (cause.type) { switch (cause.type) {

View File

@ -28,6 +28,7 @@ import { bankUiSettings } from "../settings.js";
import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js"; import { buildRequestErrorMessage, undefinedIfEmpty } from "../utils.js";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js"; import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { getRandomPassword, getRandomUsername } from "./rnd.js"; import { getRandomPassword, getRandomUsername } from "./rnd.js";
import { useCredentialsChecker } from "../hooks/useCredentialsChecker.js";
const logger = new Logger("RegistrationPage"); const logger = new Logger("RegistrationPage");
@ -58,6 +59,7 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
const [name, setName] = useState<string | undefined>(); const [name, setName] = useState<string | undefined>();
const [password, setPassword] = useState<string | undefined>(); const [password, setPassword] = useState<string | undefined>();
const [repeatPassword, setRepeatPassword] = useState<string | undefined>(); const [repeatPassword, setRepeatPassword] = useState<string | undefined>();
const {requestNewLoginToken} = useCredentialsChecker()
const { register } = useTestingAPI(); const { register } = useTestingAPI();
const { i18n } = useTranslationContext(); const { i18n } = useTranslationContext();
@ -83,8 +85,11 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
if (!username || !password || !name) return; if (!username || !password || !name) return;
try { try {
await register({ name, username, password }); await register({ name, username, password });
const resp = await requestNewLoginToken(username, password)
setUsername(undefined); setUsername(undefined);
backend.logIn({ username, password }); if (resp.valid) {
backend.logIn({ username, token: resp.token });
}
onComplete(); onComplete();
} catch (error) { } catch (error) {
if (error instanceof RequestError) { if (error instanceof RequestError) {
@ -125,7 +130,10 @@ function RegistrationForm({ onComplete, onCancel }: { onComplete: () => void, on
setRepeatPassword(undefined); setRepeatPassword(undefined);
const username = `_${user.first}-${user.second}_` const username = `_${user.first}-${user.second}_`
await register({ username, name: `${user.first} ${user.second}`, password: pass }); await register({ username, name: `${user.first} ${user.second}`, password: pass });
backend.logIn({ username, password: pass }); const resp = await requestNewLoginToken(username, pass)
if (resp.valid) {
backend.logIn({ username, token: resp.token });
}
onComplete(); onComplete();
} catch (error) { } catch (error) {
if (error instanceof RequestError) { if (error instanceof RequestError) {

View File

@ -17,11 +17,8 @@ export function AdminAccount({ onRegister }: { onRegister: () => void }): VNode
} }
const { data } = result; const { data } = result;
//FIXME: libeufin does not follow the spec const balance = Amounts.parseOrThrow(data.balance.amount);
const balance = Amounts.parseOrThrow(data.balance); const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
const balanceIsDebit = true;
// const balance = Amounts.parseOrThrow(data.balance.amount);
// const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold); const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold);
const limit = balanceIsDebit const limit = balanceIsDebit

View File

@ -41,9 +41,7 @@ export function RemoveAccount({
if (focus) ref.current?.focus(); if (focus) ref.current?.focus();
}, [focus]); }, [focus]);
//FIXME: libeufin does not follow the spec const balance = Amounts.parse(result.data.balance.amount);
const balance = Amounts.parse(result.data.balance);
// const balance = Amounts.parse(result.data.balance.amount);
if (!balance) { if (!balance) {
return <div>there was an error reading the balance</div>; return <div>there was an error reading the balance</div>;
} }

View File

@ -236,11 +236,8 @@ function CreateCashout({
if (!ratiosResult.ok) return onLoadNotOk(ratiosResult); if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
const config = ratiosResult.data; const config = ratiosResult.data;
//FIXME: libeufin does not follow the spec const balance = Amounts.parseOrThrow(result.data.balance.amount);
const balance = Amounts.parseOrThrow(result.data.balance); const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
const balanceIsDebit = true;
// const balance = Amounts.parseOrThrow(result.data.balance.amount);
// const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold); const debitThreshold = Amounts.parseOrThrow(result.data.debit_threshold);
const zero = Amounts.zeroOfCurrency(balance.currency); const zero = Amounts.zeroOfCurrency(balance.currency);

View File

@ -26,6 +26,7 @@ import * as pages from "./pages/index.stories.js";
import { ComponentChildren, VNode, h as create } from "preact"; import { ComponentChildren, VNode, h as create } from "preact";
import { BackendStateProviderTesting } from "./context/backend.js"; import { BackendStateProviderTesting } from "./context/backend.js";
import { AccessToken } from "./hooks/useCredentialsChecker.js";
setupI18n("en", { en: {} }); setupI18n("en", { en: {} });
@ -56,7 +57,7 @@ function DefaultTestingContext({
state: { state: {
status: "loggedIn", status: "loggedIn",
username: "test", username: "test",
password: "pwd", token: "pwd" as AccessToken,
isUserAdministrator: false, isUserAdministrator: false,
}, },
}); });