aboutsummaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2024-04-05 17:53:39 -0300
committerSebastian <sebasjm@gmail.com>2024-04-05 17:53:39 -0300
commitcc38998803141c42511e878441a5a8b15a387436 (patch)
tree8a9279ccd6349aac98e649ca0b7394c0630a805d /packages
parentf5747b394d14f65d9bee342eb30edf47a36d9751 (diff)
fix #8276
Diffstat (limited to 'packages')
-rw-r--r--packages/merchant-backoffice-ui/src/Application.tsx2
-rw-r--r--packages/merchant-backoffice-ui/src/context/session.ts2
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx25
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx162
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx95
-rw-r--r--packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx16
-rw-r--r--packages/taler-util/src/http-client/bank-revenue.ts21
7 files changed, 184 insertions, 139 deletions
diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx
index 1a4bd6708..d5a05e821 100644
--- a/packages/merchant-backoffice-ui/src/Application.tsx
+++ b/packages/merchant-backoffice-ui/src/Application.tsx
@@ -64,7 +64,7 @@ export function Application(): VNode {
de: strings["de"].completeness,
}}
>
- <MerchantApiProvider baseUrl={new URL("/", baseUrl)} frameOnError={OnConfigError} evictors={{
+ <MerchantApiProvider baseUrl={new URL("./", baseUrl)} frameOnError={OnConfigError} evictors={{
management: swrCacheEvictor
}}>
<SWRConfig
diff --git a/packages/merchant-backoffice-ui/src/context/session.ts b/packages/merchant-backoffice-ui/src/context/session.ts
index 7a5ef33d7..f3349bf83 100644
--- a/packages/merchant-backoffice-ui/src/context/session.ts
+++ b/packages/merchant-backoffice-ui/src/context/session.ts
@@ -188,7 +188,7 @@ export function useSessionContext(): SessionStateHandler {
if (state.impersonate === undefined) {
return;
}
- const newURL = new URL(`/`, state.impersonate.originalBackendUrl);
+ const newURL = new URL(`./`, state.impersonate.originalBackendUrl);
changeBackend(newURL);
const nextState: SessionState = {
status: "loggedIn",
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx
index 255caa375..d05375b6c 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/CreatePage.tsx
@@ -32,6 +32,7 @@ import { Input } from "../../../../components/form/Input.js";
import { InputPaytoForm } from "../../../../components/form/InputPaytoForm.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
+import { safeConvertURL } from "../update/UpdatePage.js";
type Entity = TalerMerchantApi.AccountAddDetails & { repeatPassword: string };
@@ -42,19 +43,11 @@ interface Props {
const accountAuthType = ["none", "basic"];
-function isValidURL(s: string): boolean {
- try {
- const parsed = new URL("/", s);
- return parsed instanceof URL;
- } catch (e) {
- return false;
- }
-}
-
export function CreatePage({ onCreate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
const [state, setState] = useState<Partial<Entity>>({});
+ const facadeURL = safeConvertURL(state.credit_facade_url);
const errors: FormErrors<Entity> = {
payto_uri: !state.payto_uri ? i18n.str`required` : undefined,
@@ -74,9 +67,15 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
}),
credit_facade_url: !state.credit_facade_url
? undefined
- : !isValidURL(state.credit_facade_url)
- ? i18n.str`not valid url`
- : undefined,
+ : !facadeURL
+ ? i18n.str`Invalid url`
+ : !facadeURL.href.endsWith("/")
+ ? i18n.str`URL should end with a '/'`
+ : facadeURL.searchParams.size > 0
+ ? i18n.str`URL should not contain params`
+ : facadeURL.hash
+ ? i18n.str`URL should not hash param`
+ : undefined,
repeatPassword: !state.credit_facade_credentials
? undefined
: state.credit_facade_credentials.type === "basic" &&
@@ -94,7 +93,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode {
if (hasErrors) return Promise.reject();
const credit_facade_url = !state.credit_facade_url
? undefined
- : new URL("/", state.credit_facade_url).href;
+ : facadeURL?.href;
const credit_facade_credentials:
| TalerMerchantApi.FacadeCredentials
| undefined =
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx
index 96ca8bf5e..fb50ab995 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/create/index.tsx
@@ -62,7 +62,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
onCreate={async (request: Entity) => {
const revenueAPI = !request.credit_facade_url
? undefined
- : new URL("/", request.credit_facade_url);
+ : new URL("./", request.credit_facade_url);
if (revenueAPI) {
const resp = await testRevenueAPI(
@@ -71,7 +71,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
);
if (resp.type === "fail") {
switch (resp.case) {
- case "no-config": {
+ case TestRevenueErrorType.NO_CONFIG: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -79,7 +79,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
});
return;
}
- case "client-bad-request": {
+ case TestRevenueErrorType.CLIENT_BAD_REQUEST: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -87,7 +87,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
});
return;
}
- case "unauthorized": {
+ case TestRevenueErrorType.UNAUTHORIZED: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -95,7 +95,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
});
return;
}
- case "not-found": {
+ case TestRevenueErrorType.NOT_FOUND: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -103,7 +103,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
});
return;
}
- case "error": {
+ case TestRevenueErrorType.GENERIC_ERROR: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -112,7 +112,7 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
return;
}
default: {
- assertUnreachable(resp)
+ assertUnreachable(resp.case);
}
}
}
@@ -136,92 +136,102 @@ export default function CreateValidator({ onConfirm, onBack }: Props): VNode {
);
}
+export enum TestRevenueErrorType {
+ NO_CONFIG,
+ CLIENT_BAD_REQUEST,
+ UNAUTHORIZED,
+ NOT_FOUND,
+ GENERIC_ERROR,
+}
+
export async function testRevenueAPI(
revenueAPI: URL,
creds: FacadeCredentials | undefined,
-): Promise<
- | OperationOk<void>
- | OperationFail<"no-config">
- | OperationFail<"client-bad-request">
- | OperationFail<"unauthorized">
- | OperationFail<"not-found">
- | OperationFail<"error">
-> {
+): Promise<OperationOk<void> | OperationFail<TestRevenueErrorType>> {
const api = new TalerRevenueHttpClient(
revenueAPI.href,
new BrowserFetchHttpLib(),
);
+ const auth =
+ creds === undefined
+ ? undefined
+ : creds.type === "none"
+ ? undefined
+ : creds.type === "basic"
+ ? {
+ username: creds.username,
+ password: creds.password,
+ }
+ : undefined;
+
try {
- const config = await api.getConfig();
+ const config = await api.getConfig(auth);
+
if (config.type === "fail") {
- return {
- type: "fail",
- case: "no-config",
- detail: {
- code: 1,
- },
- };
+ switch (config.case) {
+ case HttpStatusCode.Unauthorized: {
+ return {
+ type: "fail",
+ case: TestRevenueErrorType.UNAUTHORIZED,
+ detail: {
+ code: 1,
+ },
+ };
+ }
+ case HttpStatusCode.NotFound: {
+ return {
+ type: "fail",
+ case: TestRevenueErrorType.NO_CONFIG,
+ detail: {
+ code: 1,
+ },
+ };
+ }
+ }
+ }
+
+ const history = await api.getHistory(auth);
+
+ if (history.type === "fail") {
+ switch (history.case) {
+ case HttpStatusCode.BadRequest: {
+ return {
+ type: "fail",
+ case: TestRevenueErrorType.CLIENT_BAD_REQUEST,
+ detail: {
+ code: 1,
+ },
+ };
+ }
+ case HttpStatusCode.Unauthorized: {
+ return {
+ type: "fail",
+ case: TestRevenueErrorType.UNAUTHORIZED,
+ detail: {
+ code: 1,
+ },
+ };
+ }
+ case HttpStatusCode.NotFound: {
+ return {
+ type: "fail",
+ case: TestRevenueErrorType.NOT_FOUND,
+ detail: {
+ code: 1,
+ },
+ };
+ }
+ }
}
} catch (err) {
if (err instanceof TalerError) {
return {
type: "fail",
- case: "error",
+ case: TestRevenueErrorType.GENERIC_ERROR,
detail: err.errorDetail,
};
}
}
- if (creds) {
- const auth =
- creds.type === "basic"
- ? {
- username: creds.username,
- password: creds.password,
- }
- : undefined;
-
- try {
- const history = await api.getHistory(auth);
- if (history.type === "fail") {
- switch (history.case) {
- case HttpStatusCode.BadRequest: {
- return {
- type: "fail",
- case: "client-bad-request",
- detail: {
- code: 1,
- },
- };
- }
- case HttpStatusCode.Unauthorized: {
- return {
- type: "fail",
- case: "unauthorized",
- detail: {
- code: 1,
- },
- };
- }
- case HttpStatusCode.NotFound: {
- return {
- type: "fail",
- case: "not-found",
- detail: {
- code: 1,
- },
- };
- }
- }
- }
- } catch (err) {
- if (err instanceof TalerError) {
- return {
- type: "fail",
- case: "error",
- detail: err.errorDetail,
- };
- }
- }
- }
+
return opFixedSuccess(undefined);
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx
index 6dd264f29..1a8e9bdc1 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/UpdatePage.tsx
@@ -33,8 +33,7 @@ import { InputPaytoForm } from "../../../../components/form/InputPaytoForm.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
-type Entity = TalerMerchantApi.BankAccountEntry
- & WithId;
+type Entity = TalerMerchantApi.BankAccountEntry & WithId;
const accountAuthType = ["unedit", "none", "basic"];
interface Props {
@@ -43,32 +42,56 @@ interface Props {
account: Entity;
}
-
export function UpdatePage({ account, onUpdate, onBack }: Props): VNode {
const { i18n } = useTranslationContext();
- const [state, setState] = useState<Partial<TalerMerchantApi.AccountPatchDetails>>(account);
+ const [state, setState] =
+ useState<Partial<TalerMerchantApi.AccountPatchDetails>>(account);
// @ts-expect-error "unedit" is fine since is part of the accountAuthType values
if (state.credit_facade_credentials?.type === "unedit") {
// we use this to set creds to undefined but server don't get this type
- state.credit_facade_credentials = undefined
+ state.credit_facade_credentials = undefined;
}
+ const facadeURL = safeConvertURL(state.credit_facade_url);
+
const errors: FormErrors<TalerMerchantApi.AccountPatchDetails> = {
- credit_facade_url: !state.credit_facade_url ? undefined : !isValidURL(state.credit_facade_url) ? i18n.str`invalid url` : undefined,
+ credit_facade_url: !state.credit_facade_url
+ ? undefined
+ : !facadeURL
+ ? i18n.str`Invalid url`
+ : !facadeURL.href.endsWith("/")
+ ? i18n.str`URL should end with a '/'`
+ : facadeURL.searchParams.size > 0
+ ? i18n.str`URL should not contain params`
+ : facadeURL.hash
+ ? i18n.str`URL should not hash param`
+ : undefined,
credit_facade_credentials: undefinedIfEmpty({
+ username:
+ state.credit_facade_credentials?.type !== "basic"
+ ? undefined
+ : !state.credit_facade_credentials.username
+ ? i18n.str`required`
+ : undefined,
- username: state.credit_facade_credentials?.type !== "basic" ? undefined
- : !state.credit_facade_credentials.username ? i18n.str`required` : undefined,
-
- password: state.credit_facade_credentials?.type !== "basic" ? undefined
- : !state.credit_facade_credentials.password ? i18n.str`required` : undefined,
-
- repeatPassword: state.credit_facade_credentials?.type !== "basic" ? undefined
- : !(state.credit_facade_credentials as any).repeatPassword ? i18n.str`required` :
- (state.credit_facade_credentials as any).repeatPassword !== state.credit_facade_credentials.password ? i18n.str`doesn't match`
+ password:
+ state.credit_facade_credentials?.type !== "basic"
+ ? undefined
+ : !state.credit_facade_credentials.password
+ ? i18n.str`required`
: undefined,
+
+ repeatPassword:
+ state.credit_facade_credentials?.type !== "basic"
+ ? undefined
+ : !(state.credit_facade_credentials as any).repeatPassword
+ ? i18n.str`required`
+ : (state.credit_facade_credentials as any).repeatPassword !==
+ state.credit_facade_credentials.password
+ ? i18n.str`doesn't match`
+ : undefined,
}),
};
@@ -78,18 +101,25 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode {
const submitForm = () => {
if (hasErrors) return Promise.reject();
-
- const credit_facade_url = !state.credit_facade_url ? undefined : new URL("/", state.credit_facade_url).href
-
- const credit_facade_credentials: TalerMerchantApi.FacadeCredentials | undefined =
- credit_facade_url == undefined || state.credit_facade_credentials === undefined ? undefined :
- state.credit_facade_credentials.type === "basic" ? {
- type: "basic",
- password: state.credit_facade_credentials.password,
- username: state.credit_facade_credentials.username,
- } : {
- type: "none"
- };
+ const credit_facade_url = !state.credit_facade_url
+ ? undefined
+ : facadeURL?.href;
+
+ const credit_facade_credentials:
+ | TalerMerchantApi.FacadeCredentials
+ | undefined =
+ credit_facade_url == undefined ||
+ state.credit_facade_credentials === undefined
+ ? undefined
+ : state.credit_facade_credentials.type === "basic"
+ ? {
+ type: "basic",
+ password: state.credit_facade_credentials.password,
+ username: state.credit_facade_credentials.username,
+ }
+ : {
+ type: "none",
+ };
return onUpdate({ credit_facade_credentials, credit_facade_url });
};
@@ -140,7 +170,7 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode {
toStr={(str) => {
if (str === "none") return "Without authentication";
if (str === "basic") return "With authentication";
- return "Do not change"
+ return "Do not change";
}}
/>
{state.credit_facade_credentials?.type === "basic" ? (
@@ -191,11 +221,12 @@ export function UpdatePage({ account, onUpdate, onBack }: Props): VNode {
);
}
-function isValidURL(s: string): boolean {
+//TODO: move to utils
+export function safeConvertURL(s?: string): URL | undefined {
+ if (!s) return undefined;
try {
- const u = new URL("/", s)
- return true;
+ return new URL(s);
} catch (e) {
- return false;
+ return undefined;
}
}
diff --git a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
index cf1d5b0c7..519c9f56a 100644
--- a/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
+++ b/packages/merchant-backoffice-ui/src/paths/instance/accounts/update/index.tsx
@@ -35,7 +35,7 @@ import { Notification } from "../../../../utils/types.js";
import { LoginPage } from "../../../login/index.js";
import { NotFoundPageOrAdminCreate } from "../../../notfound/index.js";
import { UpdatePage } from "./UpdatePage.js";
-import { testRevenueAPI } from "../create/index.js";
+import { TestRevenueErrorType, testRevenueAPI } from "../create/index.js";
export type Entity = TalerMerchantApi.AccountPatchDetails & WithId;
@@ -83,7 +83,7 @@ export default function UpdateValidator({
onUpdate={async (request) => {
const revenueAPI = !request.credit_facade_url
? undefined
- : new URL("/", request.credit_facade_url);
+ : new URL("./", request.credit_facade_url);
if (revenueAPI) {
const resp = await testRevenueAPI(
@@ -92,7 +92,7 @@ export default function UpdateValidator({
);
if (resp.type === "fail") {
switch (resp.case) {
- case "no-config": {
+ case TestRevenueErrorType.NO_CONFIG: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -100,7 +100,7 @@ export default function UpdateValidator({
});
return;
}
- case "client-bad-request": {
+ case TestRevenueErrorType.CLIENT_BAD_REQUEST: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -108,7 +108,7 @@ export default function UpdateValidator({
});
return;
}
- case "unauthorized": {
+ case TestRevenueErrorType.UNAUTHORIZED: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -116,7 +116,7 @@ export default function UpdateValidator({
});
return;
}
- case "not-found": {
+ case TestRevenueErrorType.NOT_FOUND: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -124,7 +124,7 @@ export default function UpdateValidator({
});
return;
}
- case "error": {
+ case TestRevenueErrorType.GENERIC_ERROR: {
setNotif({
message: i18n.str`Could not create account`,
type: "ERROR",
@@ -133,7 +133,7 @@ export default function UpdateValidator({
return;
}
default: {
- assertUnreachable(resp)
+ assertUnreachable(resp.case)
}
}
}
diff --git a/packages/taler-util/src/http-client/bank-revenue.ts b/packages/taler-util/src/http-client/bank-revenue.ts
index b195e8c8f..34afe7d86 100644
--- a/packages/taler-util/src/http-client/bank-revenue.ts
+++ b/packages/taler-util/src/http-client/bank-revenue.ts
@@ -44,6 +44,10 @@ export type TalerBankRevenueErrorsByMethod<
prop extends keyof TalerRevenueHttpClient,
> = FailCasesByMethod<TalerRevenueHttpClient, prop>;
+type UsernameAndPassword = {
+ username: string;
+ password: string;
+};
/**
* The API is used by the merchant (or other parties) to query
* for incoming transactions to their account.
@@ -69,33 +73,34 @@ export class TalerRevenueHttpClient {
* https://docs.taler.net/core/api-bank-revenue.html#get--config
*
*/
- async getConfig() {
+ async getConfig(auth?: UsernameAndPassword) {
const url = new URL(`config`, this.baseUrl);
const resp = await this.httpLib.fetch(url.href, {
method: "GET",
+ headers: {
+ Authorization: auth
+ ? makeBasicAuthHeader(auth.username, auth.password)
+ : undefined,
+ },
});
switch (resp.status) {
case HttpStatusCode.Ok:
return opSuccessFromHttp(resp, codecForRevenueConfig());
+ case HttpStatusCode.Unauthorized:
+ return opKnownHttpFailure(resp.status, resp);
case HttpStatusCode.NotFound:
return opKnownHttpFailure(resp.status, resp);
default:
return opUnknownFailure(resp, await readTalerErrorResponse(resp));
}
}
-
/**
* https://docs.taler.net/core/api-bank-revenue.html#get--history
*
* @returns
*/
async getHistory(
- auth:
- | {
- username: string;
- password: string;
- }
- | undefined,
+ auth?: UsernameAndPassword,
params?: PaginationParams & LongPollParams,
) {
const url = new URL(`history`, this.baseUrl);