commit ad7f42e766b268d51d23137d727ce1771b8a97a4
parent 9dd269c075713ffe80a8e05c09c1b5a525a10c35
Author: Sebastian <sebasjm@taler-systems.com>
Date: Tue, 28 Apr 2026 17:23:24 -0300
fix #11356 hash matches
Diffstat:
2 files changed, 138 insertions(+), 68 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-paivana.ts b/packages/taler-harness/src/integrationtests/test-paivana.ts
@@ -18,28 +18,36 @@
* Imports.
*/
import {
- bytesToString,
ConfirmPayResultType,
+ encodeCrock,
+ getRandomBytes,
Logger,
PreparePayResultType,
Result,
- sha256,
+ setPrintHttpRequestAsCurl,
stringToBytes,
+ succeedOrThrow,
+ TalerErrorCode,
+ TalerMerchantInstanceHttpClient,
+ TalerProtocolTimestamp,
TalerUriAction,
TalerUris,
+ timestampRoundedToBuffer,
WalletNotification
} from "@gnu-taler/taler-util";
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
+import { HashSha256 } from "../../../taler-util/src/sha256.js";
import {
createSimpleTestkudosEnvironmentV3,
- withdrawViaBankV3
+ withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState } from "../harness/harness.js";
-import { createHash } from "node:crypto";
+setPrintHttpRequestAsCurl(true);
const harnessHttpLib = createPlatformHttpLib({
enableThrottling: false,
+ printAsCurl: true,
});
export const logger = new Logger("test-paivana.ts");
@@ -50,9 +58,11 @@ export async function runPaivanaTest(t: GlobalTestState) {
walletClient,
bankClient,
exchange,
+ merchant,
paivana,
+ merchantAdminAccessToken,
} = await createSimpleTestkudosEnvironmentV3(t, undefined, {
- paivanaWebsite: "/.*.html" // block all html pages
+ paivanaWebsite: ".*.html", // block all html pages
});
const notifs: WalletNotification[] = [];
@@ -70,106 +80,166 @@ export async function runPaivanaTest(t: GlobalTestState) {
await withdrawalRes.withdrawalFinishedCond;
- const website = `${paivana.baseUrl}index.html`
+ const website = `${paivana.baseUrl}index.html`;
const firstRequest = await harnessHttpLib.fetch(website);
const templateURI = firstRequest.headers.get("paivana");
t.assertTrue(!!templateURI);
- const uri = Result.unpack(TalerUris.fromString(templateURI))
- t.assertTrue(uri.type === TalerUriAction.PayTemplate)
+ const uri = Result.unpack(TalerUris.fromString(templateURI));
+ t.assertTrue(uri.type === TalerUriAction.PayTemplate);
- const cur_time = Math.floor(Date.now() / 1000);// + 60*60*24;
+ const now = (Date.now())
+ const cur_time = Math.floor(now / 1000); // + 60*60*24;
- // not so random
- const nonce = Array.from(new Uint8Array(32))
- .map(b => b.toString(16).padStart(2, '0')).join('');
+ const webArr = stringToBytes(`${website}\0`);
+ const timeArr = timestampRoundedToBuffer(
+ TalerProtocolTimestamp.fromSeconds(cur_time),
+ );
+ const nonceArr = getRandomBytes(16);
+ const nonce = encodeCrock(nonceArr.buffer);
- // const sha = bytesToString(sha256(stringToBytes(nonce + website + cur_time)))
- // const hash = base64Url(sha)
-
- // using nodejs crypto lib
- const hash = createHash('sha256')
- .update(nonce + website + cur_time)
- .digest("base64")
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=+$/, '');
+ const binary = new HashSha256()
+ .update(nonceArr)
+ .update(webArr)
+ .update(timeArr)
+ .digest();
- const paivanaId = `${cur_time}-${hash}`
+ const hash = Buffer.from(binary).toString("base64url");
+
+ const paivanaId = `${cur_time}-${hash}`;
+
+ const merchantClient = new TalerMerchantInstanceHttpClient(
+ merchant.makeInstanceBaseUrl(),
+ );
logger.info("PAIVANA TEMPLATE ID::", paivanaId);
- const PAIVANA_POLL_BASE = `${website}/.well-known/paivana/sessions/${encodeURIComponent(paivanaId)}`;
- { // Check if we magically have access (guess what, no)
- const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}`);
- // const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}?timeout_ms=1000`);
- t.assertTrue(res.status === 402)
- }
+ // {
+ // // Check if we magically have access (guess what, no)
+ // const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}`);
+ // // const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}?timeout_ms=1000`);
+ // logger.info("first paivana check", res);
+ // t.assertTrue(res.status === 402);
+ // }
logger.info("access denied, we need to pay");
- { // Pay the access to the site
+ {
+ // Pay the access to the site
+ // This is part of the wallet and it may be a
+ // thrid device so no information produced
+ // here is available
+
const newTemplate = TalerUris.createTalerPayTemplate(
uri.merchantBaseUrl,
uri.templateId,
{
- fulfillmenURL: encodeURIComponent(website),
- sessionId: encodeURIComponent(paivanaId)
- }
+ fulfillmenURL: website,
+ sessionId: paivanaId,
+ },
);
const talerPayTemplateUri = TalerUris.toString(newTemplate);
logger.info("pay template", newTemplate, talerPayTemplateUri);
- const templateStatus = await walletClient.call(WalletApiOperation.PreparePayForTemplate, { talerPayTemplateUri })
- t.assertTrue(templateStatus.status === PreparePayResultType.PaymentPossible)
- const talerPayUri = templateStatus.talerUri
+ const templateStatus = await walletClient.call(
+ WalletApiOperation.PreparePayForTemplate,
+ { talerPayTemplateUri },
+ );
+ t.assertTrue(
+ templateStatus.status === PreparePayResultType.PaymentPossible,
+ );
+ const talerPayUri = templateStatus.talerUri;
logger.info("pay order", talerPayUri);
- const payStatus = await walletClient.call(WalletApiOperation.PreparePayForUri, { talerPayUri });
- t.assertTrue(payStatus.status === PreparePayResultType.PaymentPossible)
+ const payStatus = await walletClient.call(
+ WalletApiOperation.PreparePayForUri,
+ { talerPayUri },
+ );
+ t.assertTrue(payStatus.status === PreparePayResultType.PaymentPossible);
logger.info("transaction", payStatus.transactionId);
+ const startPayment = await walletClient.call(
+ WalletApiOperation.ConfirmPay,
+ {
+ transactionId: payStatus.transactionId,
+ },
+ );
+ TalerErrorCode;
+ t.assertTrue(startPayment.type === ConfirmPayResultType.Pending);
+ await walletClient.call(
+ WalletApiOperation.TestingWaitTransactionsFinal,
+ {},
+ );
const payment = await walletClient.call(WalletApiOperation.ConfirmPay, {
transactionId: payStatus.transactionId,
});
- t.assertTrue(payment.type === ConfirmPayResultType.Done)
+ t.assertTrue(payment.type === ConfirmPayResultType.Done);
logger.info("paid", payment.contractTerms.fulfillment_url);
- }
- logger.info("checking paivana state again",);
- let order_id;
- { // Check if we have access
- const res = await harnessHttpLib.fetch(`${PAIVANA_POLL_BASE}?timeout_ms=1000`);
- t.assertTrue(res.status === 200)
- try { order_id = (await res.json()).order_id ?? undefined; } catch (_) { }
+ const orderStatus = succeedOrThrow(
+ await merchantClient.getOrderDetails(
+ merchantAdminAccessToken,
+ payment.contractTerms.order_id,
+ {
+ sessionId: paivanaId,
+ },
+ ),
+ );
+
+ console.log("MERCHANT SAY: ", orderStatus);
+ t.assertTrue(orderStatus.order_status === "paid");
}
- t.assertTrue(order_id !== undefined)
- logger.info(`---- ORDER ID ${order_id}`);
- const res = await harnessHttpLib.fetch(`${website}/.well-known/paivana`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ order_id, nonce, cur_time, website }),
+ const order = succeedOrThrow(
+ await merchantClient.getOrderIdForSessionAndUrl(paivanaId, website),
+ );
+
+ logger.info("checking paivana state again");
+
+ // while (true) {
+ // // Check if we have access
+ // const res = await harnessHttpLib.fetch(
+ // `${PAIVANA_POLL_BASE}?timeout_ms=3000`,
+ // );
+ // logger.info("paivana say", res);
+ // if (res.status !== 200) {
+ // continue;
+ // }
+ // t.assertTrue(res.status === 200);
+ // try {
+ // order_id = (await res.json()).order_id ?? undefined;
+ // } catch (_) {}
+ // break;
+ // }
+
+ logger.info(`---- ORDER ID ${order}`, {
+ order_id: order.order_id,
+ nonce,
+ cur_time: { t_s: cur_time },
+ website,
});
- console.log(res)
+ // await waitMs(50000);
+ const res = await harnessHttpLib.fetch(
+ `${paivana.baseUrl}.well-known/paivana`,
+ {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: {
+ order_id: order.order_id,
+ nonce,
+ cur_time: { t_s: cur_time },
+ website,
+ },
+ redirect: "manual",
+ },
+ );
+
+ console.log(await res.json());
+ t.assertTrue(res.status === 200);
// TODO: the test is incomplete
// TODO: verify cookie
}
-function base64Url(str: string) {
- return Buffer
- .from(str, "utf8")
- .toString("base64")
- .replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=+$/, '');
-
-}
-
-function waitMs(ms: number) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-
runPaivanaTest.suites = ["wallet"];
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
@@ -35,7 +35,7 @@ export * from "./i18n.js";
export * from "./iban.js";
export * from "./invariants.js";
export * from "./kdf.js";
-export {sha256} from "./sha256.js";
+export {sha256, HashSha256} from "./sha256.js";
export * from "./libtool-version.js";
export * from "./logging.js";