From 8e1ccefedd48f0076a53a14ecc2e2d5d094b90a9 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 2 May 2024 14:29:30 +0200 Subject: wallet-core: require canonicalized base URLs in requests --- API_CHANGES.md | 34 +--- packages/taler-util/src/codec.ts | 2 - packages/taler-util/src/wallet-types.ts | 204 ++++++++------------- packages/taler-wallet-core/src/backup/index.ts | 3 +- packages/taler-wallet-core/src/exchanges.ts | 23 +-- packages/taler-wallet-core/src/versions.ts | 2 +- packages/taler-wallet-core/src/wallet-api-types.ts | 10 + packages/taler-wallet-core/src/wallet.ts | 8 + packages/taler-wallet-core/src/withdraw.ts | 17 +- 9 files changed, 112 insertions(+), 191 deletions(-) diff --git a/API_CHANGES.md b/API_CHANGES.md index f6fbf17f5..dbf54d456 100644 --- a/API_CHANGES.md +++ b/API_CHANGES.md @@ -4,33 +4,7 @@ This files contains all the API changes for the current release: ## wallet-core -- AcceptManualWithdrawalResult.exchangePaytoUris is deprecated -- WithdrawalExchangeAccountDetails.transferAmount is now optional (if conversion applies) -- added WithdrawalExchangeAccountDetails.currencySpecification about the transferAmount currency -- 2023-12-05 dold: added WithdrawalExchangeAccountDetails.{status,conversionError} to inform the client - about errors with a particular conversion account instead of failing the whole withdrawal(-info) request. -- 2023-12-06 dold: added the exchangeBaseUrl to PreparePeerPushCreditResponse, allowing the UI - to check the exchange status for the peer push credit. -- 2023-12-06 dold: added a new getExchangeEntryForUri request, which allows the client to - get information about an existing exchange entry with DD48 semantics. - The older call "getExchangeDetailedInfo" also computes loads of information - for fee comparison and we should eventually rename it to something more appropriate - (like getExchangeFeeDetailsForUri). -- 2023-12-06 dold: Deprecate the tosStatus in the withdrawal details response. - This field does not conform to DD48 semantics and the client should - request the ToS status separately via a getExchangeEntryForUri request. -- 2023-12-07 dold: Add the prepareWithdrawExchange request for withdrawals - via a taler://withdraw-exchange URI. -- 2023-12-11 dold: Add exchangeBaseUrl to the checkPeerPushDebit response. -- 2023-12-11 dold: Add scopeInfo to exchange entry list items. -- BREAK 2023-12-12 dold: Remove forceUpdate and masterPub arguments from addExchange - request. This request has previously been overloaded both to update an - exchange entry as well as to add it. - To update the entry, updateExchangeEntry should be used instead. -- 2023-12-12 dold: the getExchangeTos request not accepts an additional - acceptLanguage field in the request. The response now contains an optional - contentLanguage field that is returned if the exchange reports it. -- 2023-12-12 2:0:1 dold: The checkPeerPushDebit now returns a maximum - expiration date based on the expiry of selected coins. -- 2023-12-13 3:0:2 dold: getVersion now returns the supported API version - ranges for all bank APIs separately. +### v5 + +- all base URLs passed to wallet-core requests must be canonicalized, + with the exception of the new `canonicalizeBaseUrl` request. diff --git a/packages/taler-util/src/codec.ts b/packages/taler-util/src/codec.ts index 678c3f092..89db2de06 100644 --- a/packages/taler-util/src/codec.ts +++ b/packages/taler-util/src/codec.ts @@ -514,5 +514,3 @@ export function codecForEither>>( }, }; } - -const x = codecForEither(codecForString(), codecForNumber()); diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index a52b3e6ba..ec6cb6f58 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -50,6 +50,7 @@ import { CurrencySpecification, TemplateParams, WithdrawalOperationStatus, + canonicalizeBaseUrl, } from "./index.js"; import { VersionMatchResult } from "./libtool-version.js"; import { PaytoUri } from "./payto.js"; @@ -149,6 +150,27 @@ function codecForTombstoneIdStr(): Codec { }; } +export function codecForCanonBaseUrl(): Codec { + return { + decode(x: any, c?: Context): string { + if (typeof x === "string") { + const canon = canonicalizeBaseUrl(x); + if (x !== canon) { + throw new DecodingError( + `expected canonicalized base URL at ${renderContext( + c, + )} but got value '${x}'`, + ); + } + return x; + } + throw new DecodingError( + `expected base URL at ${renderContext(c)} but got type ${typeof x}`, + ); + }, + }; +} + /** * Response for the create reserve request to the wallet. */ @@ -294,15 +316,10 @@ interface GetPlanForPaymentRequest extends GetPlanToCompleteOperation { maxDepositFee: AmountString; } -// interface GetPlanForTipRequest extends GetPlanForOperationBase { -// type: TransactionType.Tip; -// } -// interface GetPlanForRefundRequest extends GetPlanForOperationBase { -// type: TransactionType.Refund; -// } interface GetPlanForPullDebitRequest extends GetPlanToCompleteOperation { type: TransactionType.PeerPullDebit; } + interface GetPlanForPushCreditRequest extends GetPlanToCompleteOperation { type: TransactionType.PeerPushCredit; } @@ -745,71 +762,6 @@ export interface PrepareRefundResult { info: OrderShortInfo; } -export interface PrepareTipResult { - /** - * Unique ID for the tip assigned by the wallet. - * Typically different from the merchant-generated tip ID. - * - * @deprecated use transactionId instead - */ - walletRewardId: string; - - /** - * Tip transaction ID. - */ - transactionId: TransactionIdStr; - - /** - * Has the tip already been accepted? - */ - accepted: boolean; - - /** - * Amount that the merchant gave. - */ - rewardAmountRaw: AmountString; - - /** - * Amount that arrived at the wallet. - * Might be lower than the raw amount due to fees. - */ - rewardAmountEffective: AmountString; - - /** - * Base URL of the merchant backend giving then tip. - */ - merchantBaseUrl: string; - - /** - * Base URL of the exchange that is used to withdraw the tip. - * Determined by the merchant, the wallet/user has no choice here. - */ - exchangeBaseUrl: string; - - /** - * Time when the tip will expire. After it expired, it can't be picked - * up anymore. - */ - expirationTimestamp: TalerProtocolTimestamp; -} - -export interface AcceptTipResponse { - transactionId: TransactionIdStr; - next_url?: string; -} - -export const codecForPrepareTipResult = (): Codec => - buildCodecForObject() - .property("accepted", codecForBoolean()) - .property("rewardAmountRaw", codecForAmountString()) - .property("rewardAmountEffective", codecForAmountString()) - .property("exchangeBaseUrl", codecForString()) - .property("merchantBaseUrl", codecForString()) - .property("expirationTimestamp", codecForTimestamp) - .property("walletRewardId", codecForString()) - .property("transactionId", codecForTransactionIdStr()) - .build("PrepareRewardResult"); - export interface BenchmarkResult { time: { [s: string]: number }; repetitions: number; @@ -1063,7 +1015,7 @@ export interface TalerErrorDetail { /** * Minimal information needed about a planchet for unblinding a signature. * - * Can be a withdrawal/tipping/refresh planchet. + * Can be a withdrawal/refresh planchet. */ export interface PlanchetUnblindInfo { denomPub: DenominationPubKey; @@ -1470,7 +1422,7 @@ export const codecForFeesByOperations = (): Codec< export const codecForExchangeFullDetails = (): Codec => buildCodecForObject() .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("paytoUris", codecForList(codecForString())) .property("auditors", codecForList(codecForExchangeAuditor())) .property("wireInfo", codecForWireInfo()) @@ -1485,7 +1437,7 @@ export const codecForExchangeFullDetails = (): Codec => export const codecForExchangeListItem = (): Codec => buildCodecForObject() .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("masterPub", codecOptional(codecForString())) .property("paytoUris", codecForList(codecForString())) .property("tosStatus", codecForAny()) @@ -1709,7 +1661,7 @@ export interface TestPayArgs { export const codecForTestPayArgs = (): Codec => buildCodecForObject() - .property("merchantBaseUrl", codecForString()) + .property("merchantBaseUrl", codecForCanonBaseUrl()) .property("merchantAuthToken", codecOptional(codecForString())) .property("amount", codecForAmountString()) .property("summary", codecForString()) @@ -1727,12 +1679,12 @@ export interface IntegrationTestArgs { export const codecForIntegrationTestArgs = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("merchantBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("merchantBaseUrl", codecForCanonBaseUrl()) .property("merchantAuthToken", codecOptional(codecForString())) .property("amountToSpend", codecForAmountString()) .property("amountToWithdraw", codecForAmountString()) - .property("corebankApiBaseUrl", codecForString()) + .property("corebankApiBaseUrl", codecForCanonBaseUrl()) .build("IntegrationTestArgs"); export interface IntegrationTestV2Args { @@ -1744,10 +1696,10 @@ export interface IntegrationTestV2Args { export const codecForIntegrationTestV2Args = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) - .property("merchantBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) + .property("merchantBaseUrl", codecForCanonBaseUrl()) .property("merchantAuthToken", codecOptional(codecForString())) - .property("corebankApiBaseUrl", codecForString()) + .property("corebankApiBaseUrl", codecForCanonBaseUrl()) .build("IntegrationTestV2Args"); export interface GetExchangeEntryByUrlRequest { @@ -1757,7 +1709,7 @@ export interface GetExchangeEntryByUrlRequest { export const codecForGetExchangeEntryByUrlRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .build("GetExchangeEntryByUrlRequest"); export type GetExchangeEntryByUrlResponse = ExchangeListItem; @@ -1775,7 +1727,7 @@ export interface AddExchangeRequest { export const codecForAddExchangeRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("forceUpdate", codecOptional(codecForBoolean())) .property("masterPub", codecOptional(codecForString())) .build("AddExchangeRequest"); @@ -1788,7 +1740,7 @@ export interface UpdateExchangeEntryRequest { export const codecForUpdateExchangeEntryRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("force", codecOptional(codecForBoolean())) .build("UpdateExchangeEntryRequest"); @@ -1799,7 +1751,7 @@ export interface GetExchangeResourcesRequest { export const codecForGetExchangeResourcesRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .build("GetExchangeResourcesRequest"); export interface GetExchangeResourcesResponse { @@ -1813,7 +1765,7 @@ export interface DeleteExchangeRequest { export const codecForDeleteExchangeRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("purge", codecOptional(codecForBoolean())) .build("DeleteExchangeRequest"); @@ -1824,7 +1776,7 @@ export interface ForceExchangeUpdateRequest { export const codecForForceExchangeUpdateRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .build("AddExchangeRequest"); export interface GetExchangeTosRequest { @@ -1835,7 +1787,7 @@ export interface GetExchangeTosRequest { export const codecForGetExchangeTosRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("acceptedFormat", codecOptional(codecForList(codecForString()))) .property("acceptLanguage", codecOptional(codecForString())) .build("GetExchangeTosRequest"); @@ -1858,7 +1810,7 @@ export interface AcceptManualWithdrawalRequest { export const codecForAcceptManualWithdrawalRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("amount", codecForAmountString()) .property("restrictAge", codecOptional(codecForNumber())) .property("forceReservePriv", codecOptional(codecForString())) @@ -1893,7 +1845,7 @@ export interface PrepareBankIntegratedWithdrawalRequest { export const codecForPrepareBankIntegratedWithdrawalRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("talerWithdrawUri", codecForString()) .property("forcedDenomSel", codecForAny()) .property("restrictAge", codecOptional(codecForNumber())) @@ -1923,7 +1875,7 @@ export interface AcceptBankIntegratedWithdrawalRequest { export const codecForAcceptBankIntegratedWithdrawalRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("talerWithdrawUri", codecForString()) .property("forcedDenomSel", codecForAny()) .property("restrictAge", codecOptional(codecForNumber())) @@ -1932,7 +1884,7 @@ export const codecForAcceptBankIntegratedWithdrawalRequest = export const codecForGetWithdrawalDetailsForAmountRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("amount", codecForAmountString()) .property("restrictAge", codecOptional(codecForNumber())) .property("clientCancellationId", codecOptional(codecForString())) @@ -1945,7 +1897,7 @@ export interface AcceptExchangeTosRequest { export const codecForAcceptExchangeTosRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .build("AcceptExchangeTosRequest"); export interface ForgetExchangeTosRequest { @@ -1955,7 +1907,7 @@ export interface ForgetExchangeTosRequest { export const codecForForgetExchangeTosRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .build("ForgetExchangeTosRequest"); export interface AcceptRefundRequest { @@ -2059,7 +2011,7 @@ export interface SharePaymentRequest { } export const codecForSharePaymentRequest = (): Codec => buildCodecForObject() - .property("merchantBaseUrl", codecForString()) + .property("merchantBaseUrl", codecForCanonBaseUrl()) .property("orderId", codecForString()) .build("SharePaymentRequest"); @@ -2208,9 +2160,9 @@ export const codecForWithdrawTestBalance = (): Codec => buildCodecForObject() .property("amount", codecForAmountString()) - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("forcedDenomSel", codecForAny()) - .property("corebankApiBaseUrl", codecForString()) + .property("corebankApiBaseUrl", codecForCanonBaseUrl()) .build("WithdrawTestBalanceRequest"); export interface SetCoinSuspendedRequest { @@ -2271,32 +2223,6 @@ export const codecForStartRefundQueryRequest = .property("transactionId", codecForTransactionIdStr()) .build("StartRefundQueryRequest"); -export interface PrepareRewardRequest { - talerRewardUri: string; -} - -export const codecForPrepareRewardRequest = (): Codec => - buildCodecForObject() - .property("talerRewardUri", codecForString()) - .build("PrepareRewardRequest"); - -export interface AcceptRewardRequest { - /** - * @deprecated use transactionId - */ - walletRewardId?: string; - /** - * it will be required when "walletRewardId" is removed - */ - transactionId?: TransactionIdStr; -} - -export const codecForAcceptTipRequest = (): Codec => - buildCodecForObject() - .property("walletRewardId", codecOptional(codecForString())) - .property("transactionId", codecOptional(codecForTransactionIdStr())) - .build("AcceptRewardRequest"); - export interface FailTransactionRequest { transactionId: TransactionIdStr; } @@ -2414,7 +2340,7 @@ export const codecForWithdrawUriInfoResponse = ), ) .property("amount", codecForAmountString()) - .property("defaultExchangeBaseUrl", codecOptional(codecForString())) + .property("defaultExchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("possibleExchanges", codecForList(codecForExchangeListItem())) .build("WithdrawUriInfoResponse"); @@ -2779,7 +2705,7 @@ export interface CheckPeerPushDebitRequest { export const codecForCheckPeerPushDebitRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecOptional(codecForString())) + .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .property("amount", codecForAmountString()) .build("CheckPeerPushDebitRequest"); @@ -2918,7 +2844,7 @@ export const codecForPreparePeerPullPaymentRequest = (): Codec => buildCodecForObject() .property("amount", codecForAmountString()) - .property("exchangeBaseUrl", codecOptional(codecForString())) + .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .build("CheckPeerPullCreditRequest"); export interface CheckPeerPullCreditResponse { @@ -2941,7 +2867,7 @@ export const codecForInitiatePeerPullPaymentRequest = (): Codec => buildCodecForObject() .property("partialContractTerms", codecForPeerContractTerms()) - .property("exchangeBaseUrl", codecOptional(codecForString())) + .property("exchangeBaseUrl", codecOptional(codecForCanonBaseUrl())) .build("InitiatePeerPullCreditRequest"); export interface InitiatePeerPullCreditResponse { @@ -2956,6 +2882,20 @@ export interface InitiatePeerPullCreditResponse { transactionId: TransactionIdStr; } +export interface CanonicalizeBaseUrlRequest { + url: string; +} + +export const codecForCanonicalizeBaseUrlRequest = + (): Codec => + buildCodecForObject() + .property("url", codecForString()) + .build("CanonicalizeBaseUrlRequest"); + +export interface CanonicalizeBaseUrlResponse { + url: string; +} + export interface ValidateIbanRequest { iban: string; } @@ -3083,7 +3023,7 @@ export interface TestingGetDenomStatsResponse { export const codecForTestingGetDenomStatsRequest = (): Codec => buildCodecForObject() - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .build("TestingGetDenomStatsRequest"); export interface WithdrawalExchangeAccountDetails { @@ -3203,7 +3143,7 @@ export const codecForAddGlobalCurrencyExchangeRequest = (): Codec => buildCodecForObject() .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("exchangeMasterPub", codecForString()) .build("AddGlobalCurrencyExchangeRequest"); @@ -3217,7 +3157,7 @@ export const codecForRemoveGlobalCurrencyExchangeRequest = (): Codec => buildCodecForObject() .property("currency", codecForString()) - .property("exchangeBaseUrl", codecForString()) + .property("exchangeBaseUrl", codecForCanonBaseUrl()) .property("exchangeMasterPub", codecForString()) .build("RemoveGlobalCurrencyExchangeRequest"); @@ -3231,7 +3171,7 @@ export const codecForAddGlobalCurrencyAuditorRequest = (): Codec => buildCodecForObject() .property("currency", codecForString()) - .property("auditorBaseUrl", codecForString()) + .property("auditorBaseUrl", codecForCanonBaseUrl()) .property("auditorPub", codecForString()) .build("AddGlobalCurrencyAuditorRequest"); @@ -3245,7 +3185,7 @@ export const codecForRemoveGlobalCurrencyAuditorRequest = (): Codec => buildCodecForObject() .property("currency", codecForString()) - .property("auditorBaseUrl", codecForString()) + .property("auditorBaseUrl", codecForCanonBaseUrl()) .property("auditorPub", codecForString()) .build("RemoveGlobalCurrencyAuditorRequest"); diff --git a/packages/taler-wallet-core/src/backup/index.ts b/packages/taler-wallet-core/src/backup/index.ts index 16b5488e7..15904b470 100644 --- a/packages/taler-wallet-core/src/backup/index.ts +++ b/packages/taler-wallet-core/src/backup/index.ts @@ -46,7 +46,6 @@ import { buildCodecForUnion, bytesToString, canonicalJson, - canonicalizeBaseUrl, checkDbInvariant, checkLogicInvariant, codecForBoolean, @@ -570,7 +569,7 @@ export async function addBackupProvider( ): Promise { logger.info(`adding backup provider ${j2s(req)}`); await provideBackupState(wex); - const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl); + const canonUrl = req.backupProviderBaseUrl; await wex.db.runReadWriteTx( { storeNames: ["backupProviders"] }, async (tx) => { diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index d5ca7abbf..9072a4e6e 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -78,7 +78,6 @@ import { WireFeesJson, WireInfo, assertUnreachable, - canonicalizeBaseUrl, checkDbInvariant, codecForExchangeKeysJson, durationMul, @@ -914,10 +913,8 @@ async function startUpdateExchangeEntry( exchangeBaseUrl: string, options: { forceUpdate?: boolean } = {}, ): Promise { - const canonBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); - logger.info( - `starting update of exchange entry ${canonBaseUrl}, forced=${ + `starting update of exchange entry ${exchangeBaseUrl}, forced=${ options.forceUpdate ?? false }`, ); @@ -940,7 +937,7 @@ async function startUpdateExchangeEntry( await wex.db.runReadWriteTx( { storeNames: ["exchanges", "operationRetries"] }, async (tx) => { - const r = await tx.exchanges.get(canonBaseUrl); + const r = await tx.exchanges.get(exchangeBaseUrl); if (!r) { throw Error("exchange not found"); } @@ -988,7 +985,7 @@ async function startUpdateExchangeEntry( ); wex.ws.notify({ type: NotificationType.ExchangeStateTransition, - exchangeBaseUrl: canonBaseUrl, + exchangeBaseUrl, newExchangeState: newExchangeState, oldExchangeState: oldExchangeState, }); @@ -1160,10 +1157,8 @@ export async function fetchFreshExchange( expectedMasterPub?: string; } = {}, ): Promise { - const canonUrl = canonicalizeBaseUrl(baseUrl); - if (!options.forceUpdate) { - const cachedResp = wex.ws.exchangeCache.get(canonUrl); + const cachedResp = wex.ws.exchangeCache.get(baseUrl); if (cachedResp) { return cachedResp; } @@ -1173,12 +1168,12 @@ export async function fetchFreshExchange( await wex.taskScheduler.ensureRunning(); - await startUpdateExchangeEntry(wex, canonUrl, { + await startUpdateExchangeEntry(wex, baseUrl, { forceUpdate: options.forceUpdate, }); - const resp = await waitReadyExchange(wex, canonUrl, options); - wex.ws.exchangeCache.put(canonUrl, resp); + const resp = await waitReadyExchange(wex, baseUrl, options); + wex.ws.exchangeCache.put(baseUrl, resp); return resp; } @@ -1292,7 +1287,6 @@ export async function updateExchangeFromUrlHandler( exchangeBaseUrl: string, ): Promise { logger.trace(`updating exchange info for ${exchangeBaseUrl}`); - exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); const oldExchangeRec = await wex.db.runReadOnlyTx( { storeNames: ["exchanges"] }, @@ -2233,7 +2227,6 @@ export async function markExchangeUsed( tx: WalletDbReadWriteTransaction<["exchanges"]>, exchangeBaseUrl: string, ): Promise<{ notif: WalletNotification | undefined }> { - exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); logger.info(`marking exchange ${exchangeBaseUrl} as used`); const exch = await tx.exchanges.get(exchangeBaseUrl); if (!exch) { @@ -2529,7 +2522,7 @@ export async function deleteExchange( req: DeleteExchangeRequest, ): Promise { let inUse: boolean = false; - const exchangeBaseUrl = canonicalizeBaseUrl(req.exchangeBaseUrl); + const exchangeBaseUrl = req.exchangeBaseUrl; await wex.db.runReadWriteTx( { storeNames: [ diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts index ad58a66ec..d33a23cdd 100644 --- a/packages/taler-wallet-core/src/versions.ts +++ b/packages/taler-wallet-core/src/versions.ts @@ -52,7 +52,7 @@ export const WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION = "2:0:0"; /** * Libtool version of the wallet-core API. */ -export const WALLET_CORE_API_PROTOCOL_VERSION = "4:0:0"; +export const WALLET_CORE_API_PROTOCOL_VERSION = "5:0:0"; /** * Libtool rules: diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index f83db6039..ed882708c 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -38,6 +38,8 @@ import { ApplyDevExperimentRequest, BackupRecovery, BalancesResponse, + CanonicalizeBaseUrlRequest, + CanonicalizeBaseUrlResponse, CheckPeerPullCreditRequest, CheckPeerPullCreditResponse, CheckPeerPushDebitRequest, @@ -258,6 +260,7 @@ export enum WalletApiOperation { RemoveGlobalCurrencyAuditor = "removeGlobalCurrencyAuditor", ListAssociatedRefreshes = "listAssociatedRefreshes", Shutdown = "shutdown", + CanonicalizeBaseUrl = "canonicalizeBaseUrl", TestingWaitTransactionsFinal = "testingWaitTransactionsFinal", TestingWaitRefreshesFinal = "testingWaitRefreshesFinal", TestingWaitTransactionState = "testingWaitTransactionState", @@ -958,6 +961,12 @@ export type ValidateIbanOp = { response: ValidateIbanResponse; }; +export type CanonicalizeBaseUrlOp = { + op: WalletApiOperation.CanonicalizeBaseUrl; + request: CanonicalizeBaseUrlRequest; + response: CanonicalizeBaseUrlResponse; +}; + // group: Database Management /** @@ -1319,6 +1328,7 @@ export type WalletOperations = { [WalletApiOperation.Shutdown]: ShutdownOp; [WalletApiOperation.PrepareBankIntegratedWithdrawal]: PrepareBankIntegratedWithdrawalOp; [WalletApiOperation.ConfirmWithdrawal]: ConfirmWithdrawalOp; + [WalletApiOperation.CanonicalizeBaseUrl]: CanonicalizeBaseUrlOp; }; export type WalletCoreRequestType< diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index fc612b189..ea47ffad7 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -70,6 +70,7 @@ import { WalletCoreVersion, WalletNotification, WalletRunConfig, + canonicalizeBaseUrl, checkDbInvariant, codecForAbortTransaction, codecForAcceptBankIntegratedWithdrawalRequest, @@ -82,6 +83,7 @@ import { codecForAddKnownBankAccounts, codecForAny, codecForApplyDevExperiment, + codecForCanonicalizeBaseUrlRequest, codecForCheckPeerPullPaymentRequest, codecForCheckPeerPushDebitRequest, codecForConfirmPayRequest, @@ -1477,6 +1479,12 @@ async function dispatchRequestInternal( const req = codecForGetExchangeResourcesRequest().decode(payload); return await getExchangeResources(wex, req.exchangeBaseUrl); } + case WalletApiOperation.CanonicalizeBaseUrl: { + const req = codecForCanonicalizeBaseUrlRequest().decode(payload); + return { + url: canonicalizeBaseUrl(req.url), + }; + } case WalletApiOperation.TestingInfiniteTransactionLoop: { const myDelayMs = (payload as any).delayMs ?? 5; const shouldFetch = !!(payload as any).shouldFetch; diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 106bd93a4..fd23fef5b 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -78,7 +78,6 @@ import { WithdrawalType, addPaytoQueryParams, assertUnreachable, - canonicalizeBaseUrl, checkDbInvariant, checkLogicInvariant, codeForBankWithdrawalOperationPostResponse, @@ -2568,7 +2567,7 @@ export async function internalPrepareCreateWithdrawalGroup( args.reserveKeyPair ?? (await wex.cryptoApi.createEddsaKeypair({})); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); const secretSeed = encodeCrock(getRandomBytes(32)); - const canonExchange = canonicalizeBaseUrl(args.exchangeBaseUrl); + const exchangeBaseUrl = args.exchangeBaseUrl; const amount = args.amount; const currency = Amounts.currencyOf(amount); @@ -2595,10 +2594,10 @@ export async function internalPrepareCreateWithdrawalGroup( withdrawalGroupId = encodeCrock(getRandomBytes(32)); } - await updateWithdrawalDenoms(wex, canonExchange); + await updateWithdrawalDenoms(wex, exchangeBaseUrl); const denoms = await getCandidateWithdrawalDenoms( wex, - canonExchange, + exchangeBaseUrl, currency, ); @@ -2623,7 +2622,7 @@ export async function internalPrepareCreateWithdrawalGroup( const withdrawalGroup: WithdrawalGroupRecord = { denomSelUid, denomsSel: initialDenomSel, - exchangeBaseUrl: canonExchange, + exchangeBaseUrl: exchangeBaseUrl, instructedAmount: Amounts.stringify(amount), timestampStart: timestampPreciseToDb(now), rawWithdrawalAmount: initialDenomSel.totalWithdrawCost, @@ -2639,7 +2638,7 @@ export async function internalPrepareCreateWithdrawalGroup( wgInfo: args.wgInfo, }; - await fetchFreshExchange(wex, canonExchange); + await fetchFreshExchange(wex, exchangeBaseUrl); const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: withdrawalGroup.withdrawalGroupId, @@ -2649,7 +2648,7 @@ export async function internalPrepareCreateWithdrawalGroup( withdrawalGroup, transactionId, creationInfo: { - canonExchange, + canonExchange: exchangeBaseUrl, amount, }, }; @@ -2822,7 +2821,7 @@ export async function prepareBankIntegratedWithdrawal( }; } - const selectedExchange = canonicalizeBaseUrl(req.selectedExchange); + const selectedExchange = req.selectedExchange; const exchange = await fetchFreshExchange(wex, selectedExchange); const withdrawInfo = await getBankWithdrawalInfo( @@ -2934,7 +2933,7 @@ export async function acceptWithdrawalFromUri( restrictAge?: number; }, ): Promise { - const selectedExchange = canonicalizeBaseUrl(req.selectedExchange); + const selectedExchange = req.selectedExchange; logger.info( `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, ); -- cgit v1.2.3