commit 026b03cad5ed2443d96dc9400b003a1550485c76
parent 4bb39d26c18e9d4e63b57a873225e7c2acc45b81
Author: Sebastian <sebasjm@gmail.com>
Date: Mon, 25 Aug 2025 08:41:46 -0300
fix #10286
Diffstat:
4 files changed, 173 insertions(+), 24 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
@@ -21,17 +21,18 @@ import {
HttpStatusCode,
MerchantAuthMethod,
succeedOrThrow,
- TalerMerchantManagementHttpClient
+ TalerMerchantManagementHttpClient,
} from "@gnu-taler/taler-util";
import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js";
-import {
- GlobalTestState
-} from "../harness/harness.js";
+import { GlobalTestState } from "../harness/harness.js";
+import { TanChannel } from "../../../taler-util/src/types-taler-corebank.js";
/**
* Do basic checks on instance management and authentication.
*/
-export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState) {
+export async function runMerchantSelfProvisionActivationTest(
+ t: GlobalTestState,
+) {
// Set up test environment
const {
@@ -41,18 +42,26 @@ export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState)
merchant,
bank,
merchantAdminAccessToken,
- } = await createSimpleTestkudosEnvironmentV3(t);
+ } = await createSimpleTestkudosEnvironmentV3(t, undefined, {
+ additionalMerchantConfig(m) {
+ m.modifyConfig(async (cfg) => {
+ cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes");
+ });
+ },
+ });
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 creation = await merchantClient.createInstanceSelfProvision({
+ const signupStart = await merchantClient.createInstanceSelfProvision({
id: "self-instance",
name: "My instance",
auth: {
@@ -63,13 +72,111 @@ export async function runMerchantSelfProvisionActivationTest(t: GlobalTestState)
default_wire_transfer_delay: { d_us: "forever" },
jurisdiction: {},
address: {},
+ email: "some@taler.net",
+ phone_number: "+1111",
use_stefan: false,
});
- t.assertTrue(creation.type === "fail" && creation.case === HttpStatusCode.Accepted);
+ // creation requires 2fa
+ t.assertDeepEqual(signupStart.type, "fail");
+ t.assertDeepEqual(signupStart.case, HttpStatusCode.Accepted);
+
+ {
+ // new instance is pending, then is not listed
+ const r = succeedOrThrow(
+ await merchantClient.listInstances(merchantAdminAccessToken),
+ );
+ t.assertDeepEqual(r.instances.length, 2);
+ }
+ const firstChallenge = String(signupStart.body.challenge_id);
+ {
+ const ch = succeedOrThrow(
+ await merchantClient.sendChallenge(firstChallenge),
+ );
+
+ // always first emails since is cheaper
+ t.assertDeepEqual(ch.tan_channel, TanChannel.EMAIL);
+ // FIXME: some how get the tan code, the merchant backend server should
+ // have a scritp that save the TAN code in a file so we can read it now
+ const tanCode = "1111";
+ succeedOrThrow(
+ await merchantClient.confirmChallenge(firstChallenge, {
+ tan: tanCode,
+ }),
+ );
+ }
+ const secondSignup = await merchantClient.createInstanceSelfProvision(
+ {
+ id: "self-instance",
+ name: "My instance",
+ auth: {
+ method: MerchantAuthMethod.TOKEN,
+ password: "123",
+ },
+ default_pay_delay: { d_us: "forever" },
+ default_wire_transfer_delay: { d_us: "forever" },
+ jurisdiction: {},
+ address: {},
+ email: "some@taler.net",
+ phone_number: "+1111",
+ use_stefan: false,
+ },
+ firstChallenge,
+ );
+ // creation requires 2fa
+ t.assertDeepEqual(secondSignup.type, "fail");
+ t.assertDeepEqual(secondSignup.case, HttpStatusCode.Accepted);
+ const secondChallenge = String(secondSignup.body.challenge_id);
+ {
+ const ch = succeedOrThrow(
+ await merchantClient.sendChallenge(secondChallenge),
+ );
+
+ // next challenge sms
+ t.assertDeepEqual(ch.tan_channel, TanChannel.SMS);
+
+ // FIXME: some how get the tan code, the merchant backend server should
+ // have a scritp that save the TAN code in a file so we can read it now
+ const tanCode = "1111";
+ succeedOrThrow(
+ await merchantClient.confirmChallenge(secondChallenge, {
+ tan: tanCode,
+ }),
+ );
+ }
+
+ const completeSignup = await merchantClient.createInstanceSelfProvision(
+ {
+ id: "self-instance",
+ name: "My instance",
+ auth: {
+ method: MerchantAuthMethod.TOKEN,
+ password: "123",
+ },
+ default_pay_delay: { d_us: "forever" },
+ default_wire_transfer_delay: { d_us: "forever" },
+ jurisdiction: {},
+ address: {},
+ email: "some@taler.net",
+ phone_number: "+1111",
+ use_stefan: false,
+ },
+ secondChallenge,
+ );
+
+ t.assertDeepEqual(completeSignup.type, "ok");
+
+ {
+ // new instance is completed, now it should be visible
+ const r = succeedOrThrow(
+ await merchantClient.listInstances(merchantAdminAccessToken),
+ );
+ t.assertDeepEqual(r.instances.length, 3);
+ }
+
}
runMerchantSelfProvisionActivationTest.suites = ["merchant", "self-provision"];
diff --git a/packages/taler-util/src/http-client/merchant.ts b/packages/taler-util/src/http-client/merchant.ts
@@ -2549,6 +2549,12 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp
// 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);
@@ -2660,10 +2666,14 @@ export class TalerMerchantManagementHttpClient extends TalerMerchantInstanceHttp
*/
async createInstanceSelfProvision(
body: TalerMerchantApi.InstanceConfigurationMessage,
+ cid?: string
) {
const url = new URL(`instances`, this.baseUrl);
const headers: Record<string, string> = {};
+ if (cid) {
+ headers["X-Challenge-Id"] = cid
+ }
const resp = await this.httpLib.fetch(url.href, {
method: "POST",
body,
diff --git a/packages/taler-util/src/types-taler-merchant.ts b/packages/taler-util/src/types-taler-merchant.ts
@@ -1039,7 +1039,10 @@ export const codecForMerchantContractChoice =
buildCodecForObject<MerchantContractChoice>()
.property("amount", codecForAmountString())
.property("description", codecOptional(codecForString()))
- .property("description_i18n", codecOptional(codecForInternationalizedString()))
+ .property(
+ "description_i18n",
+ codecOptional(codecForInternationalizedString()),
+ )
.property("inputs", codecForList(codecForMerchantContractInput()))
.property("outputs", codecForList(codecForMerchantContractOutput()))
.property("max_fee", codecForAmountString())
@@ -1219,7 +1222,7 @@ export interface TalerMerchantConfigResponse {
export enum MerchantTanChannel {
SMS = "sms",
- EMAIL = "email"
+ EMAIL = "email",
}
export interface ExchangeConfigInfo {
@@ -1592,6 +1595,10 @@ export interface InstanceConfigurationMessage {
// Merchant email for customer contact.
email?: string;
+ // Merchant phone number for password reset (2-FA)
+ // @since **v21**.
+ phone_number?: string;
+
// Merchant public website.
website?: string;
@@ -1712,6 +1719,10 @@ export interface InstanceReconfigurationMessage {
// Merchant email for customer contact.
email?: string;
+ // Merchant phone number for password reset (2-FA)
+ // @since **v21**.
+ phone_number?: string;
+
// Merchant public website.
website?: string;
@@ -1778,6 +1789,18 @@ export interface QueryInstancesResponse {
// Merchant email for customer contact.
email?: string;
+ // True if the email address was validated.
+ // @since **v21**.
+ email_validated?: boolean;
+
+ // Merchant phone number for password reset (2-FA)
+ // @since **v21**.
+ phone_number?: string;
+
+ // True if the email address was validated.
+ // @since **v21**.
+ phone_validated?: boolean;
+
// Merchant public website.
website?: string;
@@ -3564,15 +3587,22 @@ export const codecForTalerMerchantConfigResponse =
.property("currencies", codecForMap(codecForCurrencySpecificiation()))
.property("exchanges", codecForList(codecForExchangeConfigInfo()))
.property("implementation", codecOptional(codecForString()))
- .property("have_self_provisioning", codecOptionalDefault(codecForBoolean(), false))
- .property("mandatory_tan_channels", codecOptionalDefault(
- codecForList(
- codecForEither(
- codecForConstString(MerchantTanChannel.SMS),
- codecForConstString(MerchantTanChannel.EMAIL),
+ .property(
+ "have_self_provisioning",
+ codecOptionalDefault(codecForBoolean(), false),
+ )
+ .property(
+ "mandatory_tan_channels",
+ codecOptionalDefault(
+ codecForList(
+ codecForEither(
+ codecForConstString(MerchantTanChannel.SMS),
+ codecForConstString(MerchantTanChannel.EMAIL),
+ ),
),
- ), []
- ),)
+ [],
+ ),
+ )
.build("TalerMerchantApi.VersionResponse");
export const codecForClaimResponse = (): Codec<ClaimResponse> =>
@@ -4022,7 +4052,10 @@ export const codecForOrderChoice = (): Codec<OrderChoice> =>
buildCodecForObject<OrderChoice>()
.property("amount", codecForAmountString())
.property("description", codecOptional(codecForString()))
- .property("description_i18n", codecOptional(codecForInternationalizedString()))
+ .property(
+ "description_i18n",
+ codecOptional(codecForInternationalizedString()),
+ )
.property("max_fee", codecOptional(codecForAmountString()))
.property("inputs", codecOptional(codecForList(codecForOrderInput())))
.property("outputs", codecOptional(codecForList(codecForOrderOutput())))
diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx
@@ -24,7 +24,7 @@ import {
URL,
WalletBalance,
parseScopeInfoShort,
- stringifyScopeInfoShort
+ stringifyScopeInfoShort,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
@@ -73,11 +73,10 @@ export function HistoryPage({
const state = useAsyncAsHook(async () => {
const b = await api.wallet.call(WalletApiOperation.GetBalances, {});
const balances = b.balances;
- const tx = await api.wallet.call(WalletApiOperation.GetTransactions, {
+ const tx = await api.wallet.call(WalletApiOperation.GetTransactionsV2, {
scopeInfo: showSearch ? undefined : selectedScope,
- sort: "descending",
includeRefreshes: settings.showRefeshTransactions,
- search,
+ limit: -100
});
return { balances, transactions: tx.transactions };
}, [selectedScope, search]);