taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

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:
Apackages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-harness/src/integrationtests/testrunner.ts | 6++++++
Mpackages/taler-util/src/http-client/merchant.ts | 176++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mpackages/taler-util/src/types-taler-merchant.ts | 16++++++++++++++++
Mpackages/taler-wallet-webextension/src/cta/InvoiceCreate/state.ts | 4+++-
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();