commit 69872d17622318d28233d3fd9ea24557ae379d61
parent f64ee751315a52fa43d7977b7b49da31a5ccd966
Author: Sebastian <sebasjm@gmail.com>
Date: Tue, 5 Aug 2025 13:55:12 +0200
working on some parts of DD67: self provision
Diffstat:
7 files changed, 391 insertions(+), 2 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts
@@ -0,0 +1,67 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ AccessToken,
+ HttpStatusCode,
+ LoginTokenScope,
+ MerchantAuthMethod,
+ succeedOrThrow,
+ TalerMerchantManagementHttpClient,
+ URL,
+} from "@gnu-taler/taler-util";
+import {
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ harnessHttpLib,
+ MerchantService,
+ setupDb,
+} from "../harness/harness.js";
+import { createTopsEnvironment } from "harness/tops.js";
+import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js";
+
+/**
+ * Do basic checks on instance management and authentication.
+ */
+export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ walletClient,
+ bankClient,
+ exchange,
+ merchant,
+ bank,
+
+ } = await createSimpleTestkudosEnvironmentV3(t);
+
+ const merchantClient = new TalerMerchantManagementHttpClient(
+ merchant.makeInstanceBaseUrl(),
+ );
+
+ {
+ const r = succeedOrThrow(await merchantClient.listInstances(undefined));
+ t.assertDeepEqual(r.instances.length, 2);
+ }
+
+
+}
+
+runMerchantSelfProvisionActivationTest.suites = ["merchant","self-provision"];
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts
@@ -0,0 +1,67 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ AccessToken,
+ HttpStatusCode,
+ LoginTokenScope,
+ MerchantAuthMethod,
+ succeedOrThrow,
+ TalerMerchantManagementHttpClient,
+ URL,
+} from "@gnu-taler/taler-util";
+import {
+ ExchangeService,
+ getTestHarnessPaytoForLabel,
+ GlobalTestState,
+ harnessHttpLib,
+ MerchantService,
+ setupDb,
+} from "../harness/harness.js";
+import { createTopsEnvironment } from "harness/tops.js";
+import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js";
+
+/**
+ * The merchant should get the TAN code on request to be used to activate the account.
+ */
+export async function runMerchantSelfProvisionForgotPasswordTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ walletClient,
+ bankClient,
+ exchange,
+ merchant,
+ bank,
+
+ } = await createSimpleTestkudosEnvironmentV3(t);
+
+ const merchantClient = new TalerMerchantManagementHttpClient(
+ merchant.makeInstanceBaseUrl(),
+ );
+
+ {
+ const r = succeedOrThrow(await merchantClient.listInstances(undefined));
+ t.assertDeepEqual(r.instances.length, 2);
+ }
+
+
+}
+
+runMerchantSelfProvisionForgotPasswordTest.suites = ["merchant","self-provision"];
diff --git a/packages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts b/packages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts
@@ -0,0 +1,57 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 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/>
+ */
+
+/**
+ * Imports.
+ */
+import {
+ succeedOrThrow,
+ TalerMerchantManagementHttpClient
+} from "@gnu-taler/taler-util";
+import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js";
+import {
+ GlobalTestState
+} from "../harness/harness.js";
+
+/**
+ * Test that the merchant can change name and emails adress but can't start kyc process
+ * before activating the account
+ */
+export async function runMerchantSelfProvisionInactiveAccountPermissionsTest(t: GlobalTestState) {
+ // Set up test environment
+
+ const {
+ walletClient,
+ bankClient,
+ exchange,
+ merchant,
+ bank,
+
+ } = await createSimpleTestkudosEnvironmentV3(t);
+
+ const merchantClient = new TalerMerchantManagementHttpClient(
+ merchant.makeInstanceBaseUrl(),
+ );
+
+ {
+ const r = succeedOrThrow(await merchantClient.listInstances(undefined));
+ t.assertDeepEqual(r.instances.length, 2);
+ }
+
+
+}
+
+runMerchantSelfProvisionInactiveAccountPermissionsTest.suites = ["merchant","self-provision"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -189,6 +189,9 @@ import { runWithdrawalHugeTest } from "./test-withdrawal-huge.js";
import { runWithdrawalIdempotentTest } from "./test-withdrawal-idempotent.js";
import { runWithdrawalManualTest } from "./test-withdrawal-manual.js";
import { runWithdrawalPrepareTest } from "./test-withdrawal-prepare.js";
+import { runMerchantSelfProvisionActivationTest } from "./test-merchant-self-provision-activation.js";
+import { runMerchantSelfProvisionForgotPasswordTest } from "./test-merchant-self-provision-forgot-password.js";
+import { runMerchantSelfProvisionInactiveAccountPermissionsTest } from "./test-merchant-self-provision-inactive-account-permissions.js";
/**
* Test runner.
@@ -303,6 +306,9 @@ const allTests: TestMainFunction[] = [
runWithdrawalFlexTest,
runExchangeMasterPubChangeTest,
runMerchantCategoriesTest,
+ runMerchantSelfProvisionActivationTest,
+ runMerchantSelfProvisionForgotPasswordTest,
+ runMerchantSelfProvisionInactiveAccountPermissionsTest,
runWithdrawalExternalTest,
runWithdrawalIdempotentTest,
runKycThresholdWithdrawalTest,
diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts
@@ -21,6 +21,7 @@ import {
HttpStatusCode,
LibtoolVersion,
LoginTokenRequest,
+ MerchantTanChannel,
OperationAlternative,
OperationFail,
OperationOk,
@@ -73,6 +74,7 @@ import {
opFixedSuccess,
opKnownAlternativeHttpFailure,
opKnownHttpFailure,
+ opKnownTalerFailure,
opUnknownHttpFailure,
} from "@gnu-taler/taler-util";
import {
@@ -80,6 +82,7 @@ import {
HttpResponse,
createPlatformHttpLib,
readSuccessResponseJsonOrThrow,
+ readTalerErrorResponse,
} from "@gnu-taler/taler-util/http";
import { opSuccessFromHttp } from "../operation.js";
import {
@@ -89,6 +92,7 @@ import {
makeBearerTokenAuthHeader,
nullEvictor,
} from "./utils.js";
+import { ChallengeSolve, codecForChallenge, codecForTanTransmission } from "../types-taler-corebank.js";
export type TalerMerchantInstanceResultByMethod<
prop extends keyof TalerMerchantInstanceHttpClient,
@@ -150,7 +154,7 @@ export enum TalerMerchantManagementCacheEviction {
* Uses libtool's current:revision:age versioning.
*/
export class TalerMerchantInstanceHttpClient {
- public readonly PROTOCOL_VERSION = "20:0:2";
+ public readonly PROTOCOL_VERSION = "21:0:3";
readonly httpLib: HttpRequestLibrary;
readonly cacheEvictor: CacheEvictor<TalerMerchantInstanceCacheEviction>;
@@ -2509,10 +2513,180 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp
}
//
+ // 2FA Authentication
+ //
+
+ async requestAccountChannelValidation(channel: MerchantTanChannel) {
+ const url = new URL(`auth-channels/${channel}/validate`, this.baseUrl);
+
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: {
+ return opEmptySuccess();
+ }
+ case HttpStatusCode.Accepted: {
+ return opKnownAlternativeHttpFailure(
+ resp,
+ resp.status,
+ codecForChallenge(),
+ );
+ }
+ case HttpStatusCode.Unauthorized:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Conflict:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownHttpFailure(resp);
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-challenge-$CHALLENGE_ID
+ *
+ */
+ async sendChallenge(cid: string) {
+ const url = new URL(`challenge/${cid}`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ });
+ switch (resp.status) {
+ case HttpStatusCode.Ok:
+ return opSuccessFromHttp(resp, codecForTanTransmission());
+ case HttpStatusCode.Unauthorized:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Forbidden:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.TooManyRequests:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.BadGateway: {
+ const details = await readTalerErrorResponse(resp);
+ switch (details.code) {
+ case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
+ return opKnownTalerFailure(details.code, details);
+ default:
+ return opUnknownHttpFailure(resp, details);
+ }
+ }
+ default:
+ return opUnknownHttpFailure(resp);
+ }
+ }
+
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-challenge-$CHALLENGE_ID-confirm
+ *
+ */
+ async confirmChallenge(
+ cid: string,
+ body: ChallengeSolve,
+ ) {
+ const url = new URL(`challenge/${cid}/confirm`, this.baseUrl);
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ });
+ switch (resp.status) {
+ case HttpStatusCode.NoContent:
+ return opEmptySuccess();
+ case HttpStatusCode.Unauthorized:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.NotFound:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Conflict: {
+ const details = await readTalerErrorResponse(resp);
+ switch (details.code) {
+ case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED:
+ return opKnownTalerFailure(details.code, details);
+ case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED:
+ return opKnownTalerFailure(details.code, details);
+ default:
+ return opUnknownHttpFailure(resp, details);
+ }
+ }
+ case HttpStatusCode.TooManyRequests:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownHttpFailure(resp);
+ }
+ }
+
+
+
+ //
// Instance Management
//
/**
+ * https://docs.taler.net/core/api-merchant.html#post--instances
+ */
+ async createInstanceSelfProvision(
+ body: TalerMerchantApi.InstanceConfigurationMessage,
+ ) {
+ const url = new URL(`instances`, this.baseUrl);
+
+ const headers: Record<string, string> = {};
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: {
+ this.cacheManagementEvictor.notifySuccess(
+ TalerMerchantManagementCacheEviction.CREATE_INSTANCE,
+ );
+ return opEmptySuccess();
+ }
+ case HttpStatusCode.Unauthorized:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Conflict:
+ return opKnownHttpFailure(resp.status, resp);
+ default:
+ return opUnknownHttpFailure(resp);
+ }
+ }
+
+ /**
+ * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-forgot-password
+ */
+ async forgotPasswordSelfProvision(
+ body: TalerMerchantApi.InstanceConfigurationMessage,
+ ) {
+ const url = new URL(`forgot-password`, this.baseUrl);
+
+ const headers: Record<string, string> = {};
+ const resp = await this.httpLib.fetch(url.href, {
+ method: "POST",
+ body,
+ headers,
+ });
+
+ switch (resp.status) {
+ case HttpStatusCode.NoContent: {
+ return opEmptySuccess();
+ }
+ case HttpStatusCode.Unauthorized:
+ return opKnownHttpFailure(resp.status, resp);
+ case HttpStatusCode.Accepted: {
+ return opKnownAlternativeHttpFailure(
+ resp,
+ resp.status,
+ codecForChallenge(),
+ );
+ }
+ default:
+ return opUnknownHttpFailure(resp);
+ }
+ }
+
+ /**
* https://docs.taler.net/core/api-merchant.html#post--management-instances
*/
async createInstance(
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
@@ -1203,6 +1203,22 @@ export interface TalerMerchantConfigResponse {
// Array of exchanges trusted by the merchant.
// Since protocol **v6**.
exchanges: ExchangeConfigInfo[];
+
+ // Set when the merchant supports
+ // self-provisioning instances.
+ // Since protocol **v21**
+ have_self_provisioning?: boolean;
+
+ // Tan channels that are required
+ // to be confirmed for an instance to
+ // be useable.
+ // Since protocol **v21**
+ mandatory_tan_channels?: MerchantTanChannel[];
+}
+
+export enum MerchantTanChannel {
+ SMS = "sms",
+ EMAIL = "email"
}
export interface ExchangeConfigInfo {
diff --git a/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts b/packages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts
@@ -36,7 +36,9 @@ export function useComponentState({
const { pushAlertOnError } = useAlertContext();
const hook = useAsyncAsHook(() =>
- api.wallet.call(WalletApiOperation.ListExchanges, {}),
+ api.wallet.call(WalletApiOperation.ListExchanges, {
+ filterByScope: scope
+ }),
);
const { i18n } = useTranslationContext();