taler-typescript-core

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

commit 2c393173814afe4dc5a2ac7bec8ac8ca21495fef
parent 04e7e052dc4b9c89602a59dcc3532ea9a43f920c
Author: Sebastian <sebasjm@gmail.com>
Date:   Wed, 10 Sep 2025 10:12:58 -0300

fix #10272

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-merchant-self-provision-activation.ts | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mpackages/taler-harness/src/integrationtests/test-merchant-self-provision-forgot-password.ts | 126+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mpackages/taler-harness/src/integrationtests/test-merchant-self-provision-inactive-account-permissions.ts | 203+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mpackages/taler-harness/src/integrationtests/test-peer-push-large.ts | 2+-
Mpackages/taler-util/src/http-client/merchant.ts | 191++++++++++++++++++++++++++++++++-----------------------------------------------
Mpackages/taler-util/src/operation.ts | 28++++++++++++++++++++++++++--
Mpackages/taler-util/src/types-taler-merchant.ts | 3+++
7 files changed, 478 insertions(+), 153 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 @@ -18,10 +18,13 @@ * Imports. */ import { + alternativeOrThrow, HttpStatusCode, + LoginTokenScope, MerchantAuthMethod, setPrintHttpRequestAsCurl, succeedOrThrow, + TalerMerchantInstanceHttpClient, TalerMerchantManagementHttpClient, } from "@gnu-taler/taler-util"; import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js"; @@ -30,6 +33,7 @@ import { GlobalTestState } from "../harness/harness.js"; import { startTanHelper } from "harness/tan-helper.js"; import { chmodSync, fstat, writeFileSync } from "node:fs"; import { randomBytes } from "node:crypto"; +import { solveMFA } from "./test-merchant-self-provision-inactive-account-permissions.js"; /** * Do basic checks on instance management and authentication. @@ -62,21 +66,13 @@ taler-harness run-helper --socket ${socketFile} -- $@ additionalMerchantConfig(m) { m.modifyConfig(async (cfg) => { cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes"); - cfg.setString( - "merchant", - "HELPER_SMS", - helperScript - ); - cfg.setString( - "merchant", - "HELPER_EMAIL", - helperScript, - ); + cfg.setString("merchant", "HELPER_SMS", helperScript); + cfg.setString("merchant", "HELPER_EMAIL", helperScript); cfg.setString("merchant", "MANDATORY_TAN_CHANNELS", "email sms"); }); }, }); - const helper = await startTanHelper({socketFile}); + const helper = await startTanHelper({ socketFile }); const merchantClient = new TalerMerchantManagementHttpClient( merchant.makeInstanceBaseUrl(), @@ -105,16 +101,14 @@ taler-harness run-helper --socket ${socketFile} -- $@ use_stefan: false, }; const signupStart = - await merchantClient.createInstanceSelfProvision(instanceInfo); + alternativeOrThrow(await merchantClient.createInstanceSelfProvision(instanceInfo), HttpStatusCode.Accepted); // creation requires 2fa - t.assertDeepEqual(signupStart.type, "fail"); - t.assertDeepEqual(signupStart.case, HttpStatusCode.Accepted); - t.assertDeepEqual(signupStart.body.challenges.length, 2); - t.assertDeepEqual(signupStart.body.combi_and, true); + t.assertDeepEqual(signupStart.challenges.length, 2); + t.assertDeepEqual(signupStart.combi_and, true); - const firstChallenge = signupStart.body.challenges[0]; - const secondChallenge = signupStart.body.challenges[1]; + const firstChallenge = signupStart.challenges[0]; + const secondChallenge = signupStart.challenges[1]; //FIXME: check the order // always first emails since is cheaper @@ -135,7 +129,7 @@ taler-harness run-helper --socket ${socketFile} -- $@ ); const message = helper.getLastCodeForAddress(instanceInfo.phone_number); - const [tanCode,] = message.split('\n') + const [tanCode] = message.split("\n"); succeedOrThrow( await merchantClient.confirmChallenge(firstChallenge.challenge_id, { tan: tanCode, @@ -149,7 +143,7 @@ taler-harness run-helper --socket ${socketFile} -- $@ ); const message = helper.getLastCodeForAddress(instanceInfo.email); - const [tanCode,] = message.split('\n') + const [tanCode] = message.split("\n"); succeedOrThrow( await merchantClient.confirmChallenge(secondChallenge.challenge_id, { tan: tanCode, @@ -166,6 +160,11 @@ taler-harness run-helper --socket ${socketFile} -- $@ t.assertDeepEqual(completeSignup.type, "ok"); + const instanceApi = new TalerMerchantInstanceHttpClient( + merchantClient.getSubInstanceAPI(instanceInfo.id), + merchantClient.httpLib, + ); + { // new instance is completed, now it should be visible const r = succeedOrThrow( @@ -174,6 +173,45 @@ taler-harness run-helper --socket ${socketFile} -- $@ t.assertDeepEqual(r.instances.length, 3); } + const loginChallenge = alternativeOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + instanceInfo.auth.password, + { + scope: LoginTokenScope.All, + }, + ), + HttpStatusCode.Accepted, + ); + + await solveMFA(merchantClient, helper, loginChallenge, { + [TanChannel.EMAIL]: instanceInfo.email, + [TanChannel.SMS]: instanceInfo.phone_number, + }); + + const { access_token: token } = succeedOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + instanceInfo.auth.password, + { + scope: LoginTokenScope.All, + }, + { + challengeIds: loginChallenge.challenges.map((c) => c.challenge_id), + }, + ), + ); + + const det = succeedOrThrow( + await instanceApi.getCurrentInstanceDetails(token), + ); + + // check that the instance has the new email + t.assertDeepEqual(det.email, instanceInfo.email); + t.assertDeepEqual(det.email_validated, true); + t.assertDeepEqual(det.phone_number, instanceInfo.phone_number); + t.assertDeepEqual(det.phone_validated, true); + helper.stop(); } 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 @@ -19,11 +19,15 @@ */ import { AccessToken, + alternativeOrThrow, + failOrThrow, HttpStatusCode, LoginTokenScope, MerchantAuthMethod, succeedOrThrow, + TalerMerchantInstanceHttpClient, TalerMerchantManagementHttpClient, + TanChannel, URL, } from "@gnu-taler/taler-util"; import { @@ -36,13 +40,31 @@ import { } from "../harness/harness.js"; import { createTopsEnvironment } from "harness/tops.js"; import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js"; +import { randomBytes } from "node:crypto"; +import { chmodSync, writeFileSync } from "node:fs"; +import { startTanHelper } from "harness/tan-helper.js"; +import { solveMFA } from "./test-merchant-self-provision-inactive-account-permissions.js"; /** * The merchant should get the TAN code on request to be used to activate the account. */ -export async function runMerchantSelfProvisionForgotPasswordTest(t: GlobalTestState) { +export async function runMerchantSelfProvisionForgotPasswordTest( + t: GlobalTestState, +) { // Set up test environment + // FIXME: maybe merchant can use commands? + const RND = randomBytes(10).toString("hex"); + const socketFile = `${t.testDir}/tan-helper-${RND}.socket`; + const helperScript = `${t.testDir}/harness-helper-${RND}.sh`; + writeFileSync( + helperScript, + `#!/bin/bash +taler-harness run-helper --socket ${socketFile} -- $@ +`, + ); + chmodSync(helperScript, "777"); + const { walletClient, bankClient, @@ -50,19 +72,111 @@ export async function runMerchantSelfProvisionForgotPasswordTest(t: GlobalTestSt merchant, merchantAdminAccessToken, bank, - - } = await createSimpleTestkudosEnvironmentV3(t); + } = await createSimpleTestkudosEnvironmentV3(t, undefined, { + additionalMerchantConfig(m) { + m.modifyConfig(async (cfg) => { + cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes"); + cfg.setString("merchant", "HELPER_EMAIL", helperScript); + cfg.setString("merchant", "MANDATORY_TAN_CHANNELS", "email"); + }); + }, + }); + const helper = await startTanHelper({ socketFile }); const merchantClient = new TalerMerchantManagementHttpClient( merchant.makeInstanceBaseUrl(), ); { - const r = succeedOrThrow(await merchantClient.listInstances(merchantAdminAccessToken)); + const r = succeedOrThrow( + await merchantClient.listInstances(merchantAdminAccessToken), + ); t.assertDeepEqual(r.instances.length, 2); } - + const instanceInfo = { + id: "self-instance", + name: "My instance", + auth: { + method: MerchantAuthMethod.TOKEN, + password: "123", + }, + default_pay_delay: { d_us: "forever" as const }, + default_wire_transfer_delay: { d_us: "forever" as const }, + jurisdiction: {}, + address: {}, + email: "some@taler.net", + use_stefan: false, + }; + + const tryCreation = alternativeOrThrow( + await merchantClient.createInstanceSelfProvision(instanceInfo), + HttpStatusCode.Accepted, + ); + + await solveMFA(merchantClient, helper, tryCreation, { + [TanChannel.EMAIL]: instanceInfo.email, + }); + + succeedOrThrow( + await merchantClient.createInstanceSelfProvision(instanceInfo, { + challengeIds: tryCreation.challenges.map((c) => c.challenge_id), + }), + ); + + const instanceApi = new TalerMerchantInstanceHttpClient( + merchantClient.getSubInstanceAPI(instanceInfo.id), + merchantClient.httpLib, + ); + + const newPassword = { + method: MerchantAuthMethod.TOKEN, + password: "xxx", + }; + + const mfa = alternativeOrThrow( + await instanceApi.forgotPasswordSelfProvision(newPassword), + HttpStatusCode.Accepted, + ); + + await solveMFA(instanceApi, helper, mfa, { + [TanChannel.EMAIL]: instanceInfo.email, + }); + + succeedOrThrow( + await instanceApi.forgotPasswordSelfProvision(newPassword, { + challengeIds: mfa.challenges.map((c) => c.challenge_id), + }), + ); + + const mfa2 = alternativeOrThrow( + await instanceApi.createAccessToken(instanceInfo.id, newPassword.password, { + scope: LoginTokenScope.All, + }), + HttpStatusCode.Accepted, + ); + + await solveMFA(instanceApi, helper, mfa2, { + [TanChannel.EMAIL]: instanceInfo.email, + }); + + const tk = succeedOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + newPassword.password, + { + scope: LoginTokenScope.All, + }, + { + challengeIds: mfa2.challenges.map((c) => c.challenge_id), + }, + ), + ); + + helper.stop(); } -runMerchantSelfProvisionForgotPasswordTest.suites = ["merchant","self-provision"]; +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 @@ -18,21 +18,44 @@ * Imports. */ import { + alternativeOrThrow, + ChallengeResponse, + failOrThrow, + HttpStatusCode, + LoginTokenScope, + MerchantAuthMethod, succeedOrThrow, - TalerMerchantManagementHttpClient + TalerMerchantInstanceHttpClient, + TalerMerchantManagementHttpClient, + TanChannel, } from "@gnu-taler/taler-util"; import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js"; -import { - GlobalTestState -} from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; +import { chmodSync, writeFileSync } from "node:fs"; +import { randomBytes } from "node:crypto"; +import { startTanHelper, TestTanHelperService } from "harness/tan-helper.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) { +export async function runMerchantSelfProvisionInactiveAccountPermissionsTest( + t: GlobalTestState, +) { // Set up test environment + // FIXME: maybe merchant can use commands? + const RND = randomBytes(10).toString("hex"); + const socketFile = `${t.testDir}/tan-helper-${RND}.socket`; + const helperScript = `${t.testDir}/harness-helper-${RND}.sh`; + writeFileSync( + helperScript, + `#!/bin/bash + taler-harness run-helper --socket ${socketFile} -- $@ + `, + ); + chmodSync(helperScript, "777"); + const { walletClient, bankClient, @@ -40,18 +63,180 @@ export async function runMerchantSelfProvisionInactiveAccountPermissionsTest(t: merchant, bank, merchantAdminAccessToken, - } = await createSimpleTestkudosEnvironmentV3(t); + } = await createSimpleTestkudosEnvironmentV3(t, undefined, { + additionalMerchantConfig(m) { + m.modifyConfig(async (cfg) => { + cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes"); + cfg.setString("merchant", "HELPER_SMS", helperScript); + cfg.setString("merchant", "HELPER_EMAIL", helperScript); + cfg.setString("merchant", "MANDATORY_TAN_CHANNELS", "email"); + }); + }, + }); + const helper = await startTanHelper({ socketFile }); const merchantClient = new TalerMerchantManagementHttpClient( merchant.makeInstanceBaseUrl(), ); { - const r = succeedOrThrow(await merchantClient.listInstances(merchantAdminAccessToken)); + const r = succeedOrThrow( + await merchantClient.listInstances(merchantAdminAccessToken), + ); + t.assertDeepEqual(r.instances.length, 2); + } + const instanceInfo = { + id: "self-instance", + name: "My instance", + auth: { + method: MerchantAuthMethod.TOKEN, + password: "123", + }, + default_pay_delay: { d_us: "forever" as const }, + default_wire_transfer_delay: { d_us: "forever" as const }, + jurisdiction: {}, + address: {}, + email: "some@taler.net", + use_stefan: false, + }; + + const signupStart = alternativeOrThrow( + await merchantClient.createInstanceSelfProvision(instanceInfo), + HttpStatusCode.Accepted, + ); + + // creation requires 2fa + t.assertDeepEqual(signupStart.challenges.length, 1); + t.assertDeepEqual(signupStart.combi_and, true); + + const { challenge_id: firstChallenge } = signupStart.challenges[0]; + + { + // new instance is pending, then is not listed + const r = succeedOrThrow( + await merchantClient.listInstances(merchantAdminAccessToken), + ); t.assertDeepEqual(r.instances.length, 2); } - + const instanceApi = new TalerMerchantInstanceHttpClient( + merchantClient.getSubInstanceAPI(instanceInfo.id), + merchantClient.httpLib, + ); + + succeedOrThrow(await merchantClient.sendChallenge(firstChallenge)); + + const [tan] = helper.getLastCodeForAddress(instanceInfo.email).split("\n"); + + t.assertTrue(tan.length > 0, "the mechant didnt send the code to the email"); + + // in this case the user used the wrong email and + // needs to change it before confirm + + instanceInfo.email = "another@email.com"; + + const emailChange = alternativeOrThrow( + await merchantClient.createInstanceSelfProvision(instanceInfo), + HttpStatusCode.Accepted, + ); + + // creation requires 2fa + t.assertDeepEqual(emailChange.challenges.length, 1); + t.assertDeepEqual(emailChange.combi_and, true); + + await solveMFA(merchantClient, helper, emailChange, { + [TanChannel.EMAIL]: instanceInfo.email, + }); + + const wrongInfo = { + ...instanceInfo, + email: "just_wrong_email", + }; + + // const bad = failOrThrow( + await merchantClient.createInstanceSelfProvision(wrongInfo, { + challengeIds: emailChange.challenges.map(c => c.challenge_id), + }); + // , + // HttpStatusCode.Conflict, + // ); + + // console.log("ERRORORORORORO", bad) + + const completeSignup = succeedOrThrow( + await merchantClient.createInstanceSelfProvision(instanceInfo, { + challengeIds: emailChange.challenges.map(c => c.challenge_id), + }), + ); + + await merchantClient.createInstanceSelfProvision(wrongInfo, { + challengeIds: emailChange.challenges.map(c => c.challenge_id), + }); + + const loginChallenge = alternativeOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + instanceInfo.auth.password, + { + scope: LoginTokenScope.All, + }, + ), + HttpStatusCode.Accepted, + ); + + await solveMFA(merchantClient, helper, loginChallenge, { + [TanChannel.EMAIL]: instanceInfo.email, + }); + + const { access_token: token } = succeedOrThrow( + await instanceApi.createAccessToken( + instanceInfo.id, + instanceInfo.auth.password, + { + scope: LoginTokenScope.All, + }, + { + challengeIds: loginChallenge.challenges.map((c) => c.challenge_id), + }, + ), + ); + + const det = succeedOrThrow( + await instanceApi.getCurrentInstanceDetails(token), + ); + + + // check that the instance has the new email + t.assertDeepEqual(det.email, instanceInfo.email) + t.assertDeepEqual(det.email_validated, true) + helper.stop(); } -runMerchantSelfProvisionInactiveAccountPermissionsTest.suites = ["merchant","self-provision"]; +runMerchantSelfProvisionInactiveAccountPermissionsTest.suites = [ + "merchant", + "self-provision", +]; + +export async function solveMFA( + api: TalerMerchantInstanceHttpClient, + helper: TestTanHelperService, + resp: ChallengeResponse, + addrs: Partial<Record<TanChannel, string>>, +) { + for (const { + challenge_id: challenge, + challenge_type: type, + } of resp.challenges) { + const addrUsed = addrs[type]; + if (!addrUsed) { + throw Error( + `The MFA is asking unexpected channel: ${type} id ${challenge}`, + ); + } + succeedOrThrow(await api.sendChallenge(challenge)); + + const [tan] = helper.getLastCodeForAddress(addrUsed).split("\n"); + + succeedOrThrow(await api.confirmChallenge(challenge, { tan })); + } +} diff --git a/packages/taler-harness/src/integrationtests/test-peer-push-large.ts b/packages/taler-harness/src/integrationtests/test-peer-push-large.ts @@ -71,7 +71,7 @@ export async function runPeerPushLargeTest(t: GlobalTestState) { ] = await Promise.all([ createSimpleTestkudosEnvironmentV3(t, coinConfigList, { additionalBankConfig(b) { - + }, }), createWalletDaemonWithClient(t, { diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts @@ -2524,121 +2524,6 @@ export class TalerMerchantInstanceHttpClient { return opUnknownHttpFailure(resp); } } - - /** - * https://docs.taler.net/core/api-merchant.html#post--instances-$INSTANCE-forgot-password - */ - async forgotPasswordSelfProvision( - body: TalerMerchantApi.InstanceAuthConfigurationMessage, - params: { - challengeIds?: string[]; - } = {}, - ) { - const url = new URL(`forgot-password`, this.baseUrl); - - const headers: Record<string, string> = {}; - if (params.challengeIds && params.challengeIds.length > 0) { - headers["Taler-Challenge-Ids"] = params.challengeIds.join(", "); - } - const resp = await this.httpLib.fetch(url.href, { - method: "POST", - body, - headers, - }); - - switch (resp.status) { - case HttpStatusCode.NoContent: { - return opEmptySuccess(); - } - case HttpStatusCode.Accepted: { - return opKnownAlternativeHttpFailure( - resp, - resp.status, - codecForChallengeResponse(), - ); - } - case HttpStatusCode.Unauthorized: - return opKnownHttpFailure(resp.status, resp); - default: - return opUnknownHttpFailure(resp); - } - } - - /** - * Get the auth api against the current instance - * - * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-token - * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-token - */ - getAuthenticationAPI(): URL { - return new URL(`private/`, this.baseUrl); - } -} - -export type TalerMerchantManagementResultByMethod< - prop extends keyof TalerMerchantManagementHttpClient, -> = ResultByMethod<TalerMerchantManagementHttpClient, prop>; -export type TalerMerchantManagementErrorsByMethod< - prop extends keyof TalerMerchantManagementHttpClient, -> = FailCasesByMethod<TalerMerchantManagementHttpClient, prop>; - -export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttpClient { - readonly cacheManagementEvictor: CacheEvictor< - TalerMerchantInstanceCacheEviction | TalerMerchantManagementCacheEviction - >; - constructor( - readonly baseUrl: string, - httpClient?: HttpRequestLibrary, - // cacheManagementEvictor?: CacheEvictor<TalerMerchantManagementCacheEviction>, - cacheEvictor?: CacheEvictor< - TalerMerchantInstanceCacheEviction | TalerMerchantManagementCacheEviction - >, - ) { - super(baseUrl, httpClient, cacheEvictor); - this.cacheManagementEvictor = cacheEvictor ?? nullEvictor; - } - - getSubInstanceAPI(instanceId: string): string { - return new URL(`instances/${instanceId}/`, this.baseUrl).href; - } - - // - // 2FA Authentication - // - - /** - * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-auth-channels-$CHANNEL-validate - * - * @param channel - * @returns - */ - 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, - codecForChallengeResponse(), - ); - } - 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 * @@ -2744,6 +2629,82 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp return opUnknownHttpFailure(resp); } } + /** + * https://docs.taler.net/core/api-merchant.html#post--instances-$INSTANCE-forgot-password + */ + async forgotPasswordSelfProvision( + body: TalerMerchantApi.InstanceAuthConfigurationMessage, + params: { + challengeIds?: string[]; + } = {}, + ) { + const url = new URL(`forgot-password`, this.baseUrl); + + const headers: Record<string, string> = {}; + if (params.challengeIds && params.challengeIds.length > 0) { + headers["Taler-Challenge-Ids"] = params.challengeIds.join(", "); + } + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body, + headers, + }); + + switch (resp.status) { + case HttpStatusCode.NoContent: { + return opEmptySuccess(); + } + case HttpStatusCode.Accepted: { + return opKnownAlternativeHttpFailure( + resp, + resp.status, + codecForChallengeResponse(), + ); + } + case HttpStatusCode.Unauthorized: + return opKnownHttpFailure(resp.status, resp); + default: + return opUnknownHttpFailure(resp); + } + } + + /** + * Get the auth api against the current instance + * + * https://docs.taler.net/core/api-merchant.html#post-[-instances-$INSTANCE]-private-token + * https://docs.taler.net/core/api-merchant.html#delete-[-instances-$INSTANCE]-private-token + */ + getAuthenticationAPI(): URL { + return new URL(`private/`, this.baseUrl); + } +} + +export type TalerMerchantManagementResultByMethod< + prop extends keyof TalerMerchantManagementHttpClient, +> = ResultByMethod<TalerMerchantManagementHttpClient, prop>; +export type TalerMerchantManagementErrorsByMethod< + prop extends keyof TalerMerchantManagementHttpClient, +> = FailCasesByMethod<TalerMerchantManagementHttpClient, prop>; + +export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttpClient { + readonly cacheManagementEvictor: CacheEvictor< + TalerMerchantInstanceCacheEviction | TalerMerchantManagementCacheEviction + >; + constructor( + readonly baseUrl: string, + httpClient?: HttpRequestLibrary, + // cacheManagementEvictor?: CacheEvictor<TalerMerchantManagementCacheEviction>, + cacheEvictor?: CacheEvictor< + TalerMerchantInstanceCacheEviction | TalerMerchantManagementCacheEviction + >, + ) { + super(baseUrl, httpClient, cacheEvictor); + this.cacheManagementEvictor = cacheEvictor ?? nullEvictor; + } + + getSubInstanceAPI(instanceId: string): string { + return new URL(`instances/${instanceId}/`, this.baseUrl).href; + } // // Instance Management diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts @@ -204,7 +204,13 @@ export function opKnownTalerFailure<T extends TalerErrorCode>( export function opUnknownFailure(error: unknown): never { throw TalerError.fromException(error); } - +/** + * The operation result should be ok + * Return the body of the result + * + * @param resp + * @returns + */ export function succeedOrThrow<R>(resp: OperationResult<R, unknown>): R { if (isOperationOk(resp)) { return resp.body; @@ -216,13 +222,22 @@ export function succeedOrThrow<R>(resp: OperationResult<R, unknown>): R { throw TalerError.fromException(resp); } +/** + * The operation is expected to fail with a body. + * Return the body of the result. + * Throw if the operation didn't fail with expected code. + * + * @param resp + * @param s + * @returns + */ export function alternativeOrThrow<Error, Body, Alt>( resp: | OperationOk<Body> | OperationAlternative<Error, Alt> | OperationFail<Error>, s: Error, -): Promise<Alt> { +): Alt { if (isOperationOk(resp)) { throw TalerError.fromException( new Error(`request succeed but failure "${s}" was expected`), @@ -240,6 +255,15 @@ export function alternativeOrThrow<Error, Body, Alt>( return (resp as any).body; } +/** + * The operation is expected to fail. + * Return the error details. + * Throw if the operation didn't fail with expected code. + * + * @param resp + * @param s + * @returns + */ export function failOrThrow<E>( resp: OperationResult<unknown, E>, s: E, diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts @@ -3745,7 +3745,10 @@ export const codecForQueryInstancesResponse = buildCodecForObject<QueryInstancesResponse>() .property("name", codecForString()) .property("email", codecOptional(codecForString())) + .property("phone_number", codecOptional(codecForString())) .property("website", codecOptional(codecForString())) + .property("email_validated", codecOptional(codecForBoolean())) + .property("phone_validated", codecOptional(codecForBoolean())) .property("logo", codecOptional(codecForString())) .property("merchant_pub", codecForEddsaPublicKey()) .property("address", codecForLocation())