taler-typescript-core

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

commit ad7f42e766b268d51d23137d727ce1771b8a97a4
parent 9dd269c075713ffe80a8e05c09c1b5a525a10c35
Author: Sebastian <sebasjm@taler-systems.com>
Date:   Tue, 28 Apr 2026 17:23:24 -0300

fix #11356 hash matches

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-paivana.ts | 204+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Mpackages/taler-util/src/index.ts | 2+-
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";