diff options
author | Özgür Kesim <oec-taler@kesim.org> | 2023-09-09 07:34:11 +0200 |
---|---|---|
committer | Özgür Kesim <oec-taler@kesim.org> | 2023-09-09 07:34:11 +0200 |
commit | 5495551071a3fdc36c38deb4c1cf6f4aa5b98bd4 (patch) | |
tree | adf7730b190618a0499e50a2d43cf1b850cddd16 /packages/taler-util | |
parent | 94cfcc875065f988815c31aaf8ebf36f75ac5983 (diff) | |
parent | 6c3cfa9be7a332c2cc8490f25ebd6c73c8244842 (diff) |
Merge branch 'master' into age-withdraw
Diffstat (limited to 'packages/taler-util')
-rw-r--r-- | packages/taler-util/package.json | 6 | ||||
-rw-r--r-- | packages/taler-util/src/MerchantApiClient.ts | 331 | ||||
-rw-r--r-- | packages/taler-util/src/backup-types.ts | 19 | ||||
-rw-r--r-- | packages/taler-util/src/bank-api-client.ts | 325 | ||||
-rw-r--r-- | packages/taler-util/src/bitcoin.ts | 8 | ||||
-rw-r--r-- | packages/taler-util/src/http-common.ts | 7 | ||||
-rw-r--r-- | packages/taler-util/src/http-impl.node.ts | 6 | ||||
-rw-r--r-- | packages/taler-util/src/http-impl.qtart.ts | 6 | ||||
-rw-r--r-- | packages/taler-util/src/index.ts | 3 | ||||
-rw-r--r-- | packages/taler-util/src/libeufin-api-types.ts | 31 | ||||
-rw-r--r-- | packages/taler-util/src/merchant-api-types.ts | 17 | ||||
-rw-r--r-- | packages/taler-util/src/payto.ts | 6 | ||||
-rw-r--r-- | packages/taler-util/src/time.ts | 14 | ||||
-rw-r--r-- | packages/taler-util/src/transactions-types.ts | 17 | ||||
-rw-r--r-- | packages/taler-util/src/wallet-types.ts | 62 | ||||
-rw-r--r-- | packages/taler-util/tsconfig.json | 4 |
16 files changed, 792 insertions, 70 deletions
diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json index 6ac9a2689..0bb98767d 100644 --- a/packages/taler-util/package.json +++ b/packages/taler-util/package.json @@ -68,14 +68,14 @@ "esbuild": "^0.17.7", "prettier": "^2.8.8", "rimraf": "^3.0.2", - "typescript": "^5.1.3" + "typescript": "^5.2.2" }, "dependencies": { "big-integer": "^1.6.51", "fflate": "^0.7.4", + "hash-wasm": "^4.9.0", "jed": "^1.1.1", - "tslib": "^2.5.3", - "hash-wasm": "^4.9.0" + "tslib": "^2.5.3" }, "ava": { "files": [ diff --git a/packages/taler-util/src/MerchantApiClient.ts b/packages/taler-util/src/MerchantApiClient.ts new file mode 100644 index 000000000..cbdcb9fdf --- /dev/null +++ b/packages/taler-util/src/MerchantApiClient.ts @@ -0,0 +1,331 @@ +/* + This file is part of GNU Taler + (C) 2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + createPlatformHttpLib, + expectSuccessResponseOrThrow, + readSuccessResponseJsonOrThrow, +} from "./http.js"; +import { FacadeCredentials } from "./libeufin-api-types.js"; +import { Logger } from "./logging.js"; +import { + MerchantReserveCreateConfirmation, + codecForMerchantReserveCreateConfirmation, + TippingReserveStatus, + MerchantInstancesResponse, + MerchantPostOrderRequest, + MerchantPostOrderResponse, + codecForMerchantPostOrderResponse, + MerchantOrderPrivateStatusResponse, + codecForMerchantOrderPrivateStatusResponse, + RewardCreateRequest, + RewardCreateConfirmation, + MerchantTemplateAddDetails, +} from "./merchant-api-types.js"; +import { AmountString } from "./taler-types.js"; +import { TalerProtocolDuration } from "./time.js"; + +const logger = new Logger("MerchantApiClient.ts"); + +export interface MerchantAuthConfiguration { + method: "external" | "token"; + token?: string; +} + +// FIXME: Why do we need this? Describe / fix! +export interface PartialMerchantInstanceConfig { + auth?: MerchantAuthConfiguration; + id: string; + name: string; + paytoUris: string[]; + address?: unknown; + jurisdiction?: unknown; + defaultWireTransferDelay?: TalerProtocolDuration; + defaultPayDelay?: TalerProtocolDuration; +} + +export interface CreateMerchantTippingReserveRequest { + // Amount that the merchant promises to put into the reserve + initial_balance: AmountString; + + // Exchange the merchant intends to use for tipping + exchange_url: string; + + // Desired wire method, for example "iban" or "x-taler-bank" + wire_method: string; +} + +export interface DeleteTippingReserveArgs { + reservePub: string; + purge?: boolean; +} + +export interface MerchantInstanceConfig { + accounts: MerchantBankAccount[]; + auth: MerchantAuthConfiguration; + id: string; + name: string; + address: unknown; + jurisdiction: unknown; + use_stefan: boolean; + default_wire_transfer_delay: TalerProtocolDuration; + default_pay_delay: TalerProtocolDuration; +} + +interface MerchantBankAccount { + // The payto:// URI where the wallet will send coins. + payto_uri: string; + + // Optional base URL for a facade where the + // merchant backend can see incoming wire + // transfers to reconcile its accounting + // with that of the exchange. Used by + // taler-merchant-wirewatch. + credit_facade_url?: string; + + // Credentials for accessing the credit facade. + credit_facade_credentials?: FacadeCredentials; +} + +export interface MerchantInstanceConfig { + accounts: MerchantBankAccount[]; + auth: MerchantAuthConfiguration; + id: string; + name: string; + address: unknown; + jurisdiction: unknown; + use_stefan: boolean; + default_wire_transfer_delay: TalerProtocolDuration; + default_pay_delay: TalerProtocolDuration; +} + +export interface PrivateOrderStatusQuery { + instance?: string; + orderId: string; + sessionId?: string; +} + +/** + * Client for the GNU Taler merchant backend. + */ +export class MerchantApiClient { + /** + * Base URL for the particular instance that this merchant API client + * is for. + */ + private baseUrl: string; + + readonly auth: MerchantAuthConfiguration; + + constructor(baseUrl: string, auth?: MerchantAuthConfiguration) { + this.baseUrl = baseUrl; + + this.auth = auth ?? { + method: "external", + }; + } + + httpClient = createPlatformHttpLib(); + + async changeAuth(auth: MerchantAuthConfiguration): Promise<void> { + const url = new URL("private/auth", this.baseUrl); + const res = await this.httpClient.fetch(url.href, { + method: "POST", + body: auth, + headers: this.makeAuthHeader(), + }); + await expectSuccessResponseOrThrow(res); + } + + async deleteTippingReserve(req: DeleteTippingReserveArgs): Promise<void> { + const url = new URL(`private/reserves/${req.reservePub}`, this.baseUrl); + if (req.purge) { + url.searchParams.set("purge", "YES"); + } + const resp = await this.httpClient.fetch(url.href, { + method: "DELETE", + headers: this.makeAuthHeader(), + }); + logger.info(`delete status: ${resp.status}`); + return; + } + + async createTippingReserve( + req: CreateMerchantTippingReserveRequest, + ): Promise<MerchantReserveCreateConfirmation> { + const url = new URL("private/reserves", this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "POST", + body: req, + headers: this.makeAuthHeader(), + }); + const respData = readSuccessResponseJsonOrThrow( + resp, + codecForMerchantReserveCreateConfirmation(), + ); + return respData; + } + + async getPrivateInstanceInfo(): Promise<any> { + const url = new URL("private", this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "GET", + headers: this.makeAuthHeader(), + }); + return await resp.json(); + } + + async getPrivateTipReserves(): Promise<TippingReserveStatus> { + const url = new URL("private/reserves", this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "GET", + headers: this.makeAuthHeader(), + }); + // FIXME: Validate! + return await resp.json(); + } + + async deleteInstance(instanceId: string) { + const url = new URL(`management/instances/${instanceId}`, this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "DELETE", + headers: this.makeAuthHeader(), + }); + await expectSuccessResponseOrThrow(resp); + } + + async createInstance(req: MerchantInstanceConfig): Promise<void> { + const url = new URL("management/instances", this.baseUrl); + await this.httpClient.fetch(url.href, { + method: "POST", + body: req, + headers: this.makeAuthHeader(), + }); + } + + async getInstances(): Promise<MerchantInstancesResponse> { + const url = new URL("management/instances", this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + headers: this.makeAuthHeader(), + }); + return resp.json(); + } + + async getInstanceFullDetails(instanceId: string): Promise<any> { + const url = new URL(`management/instances/${instanceId}`, this.baseUrl); + try { + const resp = await this.httpClient.fetch(url.href, { + headers: this.makeAuthHeader(), + }); + return resp.json(); + } catch (e) { + throw e; + } + } + + async createOrder( + req: MerchantPostOrderRequest, + ): Promise<MerchantPostOrderResponse> { + let url = new URL("private/orders", this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "POST", + body: req, + headers: this.makeAuthHeader(), + }); + return readSuccessResponseJsonOrThrow( + resp, + codecForMerchantPostOrderResponse(), + ); + } + + async queryPrivateOrderStatus( + query: PrivateOrderStatusQuery, + ): Promise<MerchantOrderPrivateStatusResponse> { + const reqUrl = new URL(`private/orders/${query.orderId}`, this.baseUrl); + if (query.sessionId) { + reqUrl.searchParams.set("session_id", query.sessionId); + } + const resp = await this.httpClient.fetch(reqUrl.href, { + headers: this.makeAuthHeader(), + }); + return readSuccessResponseJsonOrThrow( + resp, + codecForMerchantOrderPrivateStatusResponse(), + ); + } + + async giveTip(req: RewardCreateRequest): Promise<RewardCreateConfirmation> { + const reqUrl = new URL(`private/tips`, this.baseUrl); + const resp = await this.httpClient.fetch(reqUrl.href, { + method: "POST", + body: req, + }); + // FIXME: validate + return resp.json(); + } + + async queryTippingReserves(): Promise<TippingReserveStatus> { + const reqUrl = new URL(`private/reserves`, this.baseUrl); + const resp = await this.httpClient.fetch(reqUrl.href, { + headers: this.makeAuthHeader(), + }); + // FIXME: validate + return resp.json(); + } + + async giveRefund(r: { + instance: string; + orderId: string; + amount: string; + justification: string; + }): Promise<{ talerRefundUri: string }> { + const reqUrl = new URL(`private/orders/${r.orderId}/refund`, this.baseUrl); + const resp = await this.httpClient.fetch(reqUrl.href, { + method: "POST", + body: { + refund: r.amount, + reason: r.justification, + }, + }); + const respBody = await resp.json(); + return { + talerRefundUri: respBody.taler_refund_uri, + }; + } + + async createTemplate(req: MerchantTemplateAddDetails) { + let url = new URL("private/templates", this.baseUrl); + const resp = await this.httpClient.fetch(url.href, { + method: "POST", + body: req, + headers: this.makeAuthHeader(), + }); + if (resp.status !== 204) { + throw Error("failed to create template"); + } + } + + private makeAuthHeader(): Record<string, string> { + switch (this.auth.method) { + case "external": + return {}; + case "token": + return { + Authorization: `Bearer ${this.auth.token}`, + }; + } + } +} diff --git a/packages/taler-util/src/backup-types.ts b/packages/taler-util/src/backup-types.ts index 2eba1e4ca..8c38b70a6 100644 --- a/packages/taler-util/src/backup-types.ts +++ b/packages/taler-util/src/backup-types.ts @@ -14,6 +14,8 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { AmountString } from "./taler-types.js"; + export interface BackupRecovery { walletRootPriv: string; providers: { @@ -21,3 +23,20 @@ export interface BackupRecovery { url: string; }[]; } + +export class BackupBackupProviderTerms { + /** + * Last known supported protocol version. + */ + supported_protocol_version: string; + + /** + * Last known annual fee. + */ + annual_fee: AmountString; + + /** + * Last known storage limit. + */ + storage_limit_in_megabytes: number; +} diff --git a/packages/taler-util/src/bank-api-client.ts b/packages/taler-util/src/bank-api-client.ts new file mode 100644 index 000000000..cc4123500 --- /dev/null +++ b/packages/taler-util/src/bank-api-client.ts @@ -0,0 +1,325 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * Client for the Taler (demo-)bank. + */ + +/** + * Imports. + */ +import { + AmountString, + base64FromArrayBuffer, + buildCodecForObject, + Codec, + codecForAny, + codecForString, + encodeCrock, + generateIban, + getRandomBytes, + j2s, + Logger, + stringToBytes, + TalerError, + TalerErrorCode, +} from "@gnu-taler/taler-util"; +import { + checkSuccessResponseOrThrow, + createPlatformHttpLib, + HttpRequestLibrary, + readSuccessResponseJsonOrThrow, +} from "@gnu-taler/taler-util/http"; + +const logger = new Logger("bank-api-client.ts"); + +export enum CreditDebitIndicator { + Credit = "credit", + Debit = "debit", +} + +export interface BankAccountBalanceResponse { + balance: { + amount: AmountString; + credit_debit_indicator: CreditDebitIndicator; + }; +} + +export interface BankUser { + username: string; + password: string; + accountPaytoUri: string; +} + +export interface WithdrawalOperationInfo { + withdrawal_id: string; + taler_withdraw_uri: string; +} + +/** + * Helper function to generate the "Authorization" HTTP header. + */ +function makeBasicAuthHeader(username: string, password: string): string { + const auth = `${username}:${password}`; + const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth)); + return `Basic ${authEncoded}`; +} + +const codecForWithdrawalOperationInfo = (): Codec<WithdrawalOperationInfo> => + buildCodecForObject<WithdrawalOperationInfo>() + .property("withdrawal_id", codecForString()) + .property("taler_withdraw_uri", codecForString()) + .build("WithdrawalOperationInfo"); + +export interface BankAccessApiClientArgs { + auth?: { username: string; password: string }; + httpClient?: HttpRequestLibrary; +} + +export interface BankAccessApiCreateTransactionRequest { + amount: AmountString; + paytoUri: string; +} + +export class WireGatewayApiClientArgs { + auth?: { + username: string; + password: string; + }; + httpClient?: HttpRequestLibrary; +} + +/** + * This API look like it belongs to harness + * but it will be nice to have in utils to be used by others + */ +export class WireGatewayApiClient { + httpLib; + + constructor( + private baseUrl: string, + private args: WireGatewayApiClientArgs = {}, + ) { + this.httpLib = args.httpClient ?? createPlatformHttpLib(); + } + + private makeAuthHeader(): Record<string, string> { + const auth = this.args.auth; + if (auth) { + return { + Authorization: makeBasicAuthHeader(auth.username, auth.password), + }; + } + return {}; + } + + async adminAddIncoming(params: { + amount: string; + reservePub: string; + debitAccountPayto: string; + }): Promise<void> { + let url = new URL(`admin/add-incoming`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: { + amount: params.amount, + reserve_pub: params.reservePub, + debit_account: params.debitAccountPayto, + }, + headers: this.makeAuthHeader(), + }); + logger.info(`add-incoming response status: ${resp.status}`); + await checkSuccessResponseOrThrow(resp); + } +} + +/** + * This API look like it belongs to harness + * but it will be nice to have in utils to be used by others + */ +export class BankAccessApiClient { + httpLib: HttpRequestLibrary; + + constructor( + private baseUrl: string, + private args: BankAccessApiClientArgs = {}, + ) { + this.httpLib = args.httpClient ?? createPlatformHttpLib(); + } + + setAuth(auth: { username: string; password: string }) { + this.args.auth = auth; + } + + private makeAuthHeader(): Record<string, string> { + if (!this.args.auth) { + return {}; + } + const authHeaderValue = makeBasicAuthHeader( + this.args.auth.username, + this.args.auth.password, + ); + return { + Authorization: authHeaderValue, + }; + } + + async getAccountBalance( + username: string, + ): Promise<BankAccountBalanceResponse> { + const url = new URL(`accounts/${username}`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + headers: this.makeAuthHeader(), + }); + return await resp.json(); + } + + async getTransactions(username: string): Promise<void> { + const reqUrl = new URL(`accounts/${username}/transactions`, this.baseUrl); + const resp = await this.httpLib.fetch(reqUrl.href, { + method: "GET", + headers: { + ...this.makeAuthHeader(), + }, + }); + + const res = await readSuccessResponseJsonOrThrow(resp, codecForAny()); + logger.info(`result: ${j2s(res)}`); + } + + async createTransaction( + username: string, + req: BankAccessApiCreateTransactionRequest, + ): Promise<any> { + const reqUrl = new URL(`accounts/${username}/transactions`, this.baseUrl); + + const resp = await this.httpLib.fetch(reqUrl.href, { + method: "POST", + body: req, + headers: this.makeAuthHeader(), + }); + + return await readSuccessResponseJsonOrThrow(resp, codecForAny()); + } + + async registerAccount( + username: string, + password: string, + options: { + iban?: string; + } = {}, + ): Promise<BankUser> { + const url = new URL("testing/register", this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: { + username, + password, + iban: options?.iban, + }, + }); + let paytoUri = `payto://x-taler-bank/localhost/${username}`; + if (resp.status !== 200 && resp.status !== 202 && resp.status !== 204) { + logger.error(`${j2s(await resp.json())}`); + throw TalerError.fromDetail( + TalerErrorCode.GENERIC_UNEXPECTED_REQUEST_ERROR, + { + httpStatusCode: resp.status, + }, + ); + } + try { + // Pybank has no body, thus this might throw. + const respJson = await resp.json(); + // LibEuFin demobank returns payto URI in response + if (respJson.paytoUri) { + paytoUri = respJson.paytoUri; + } + } catch (e) { + // Do nothing + } + return { + password, + username, + accountPaytoUri: paytoUri, + }; + } + + async createRandomBankUser(): Promise<BankUser> { + const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); + // FIXME: This is just a temporary workaround, because demobank is running out of short IBANs + const iban = generateIban("DE", 15); + return await this.registerAccount(username, password, { + iban, + }); + } + + async createWithdrawalOperation( + user: string, + amount: string, + ): Promise<WithdrawalOperationInfo> { + const url = new URL(`accounts/${user}/withdrawals`, this.baseUrl); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: { + amount, + }, + headers: this.makeAuthHeader(), + }); + return readSuccessResponseJsonOrThrow( + resp, + codecForWithdrawalOperationInfo(), + ); + } + + async confirmWithdrawalOperation( + username: string, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${username}/withdrawals/${wopi.withdrawal_id}/confirm`, + this.baseUrl, + ); + logger.info(`confirming withdrawal operation via ${url.href}`); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: {}, + headers: this.makeAuthHeader(), + }); + + logger.info(`response status ${resp.status}`); + const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny()); + + // FIXME: We don't check the status here! + } + + async abortWithdrawalOperation( + accountName: string, + wopi: WithdrawalOperationInfo, + ): Promise<void> { + const url = new URL( + `accounts/${accountName}/withdrawals/${wopi.withdrawal_id}/abort`, + this.baseUrl, + ); + const resp = await this.httpLib.fetch(url.href, { + method: "POST", + body: {}, + headers: this.makeAuthHeader(), + }); + await readSuccessResponseJsonOrThrow(resp, codecForAny()); + } +} diff --git a/packages/taler-util/src/bitcoin.ts b/packages/taler-util/src/bitcoin.ts index 8c22ba522..37b7ae6b9 100644 --- a/packages/taler-util/src/bitcoin.ts +++ b/packages/taler-util/src/bitcoin.ts @@ -69,10 +69,10 @@ export function generateFakeSegwitAddress( addr[0] === "t" && addr[1] == "b" ? "tb" : addr[0] === "b" && addr[1] == "c" && addr[2] === "r" && addr[3] == "t" - ? "bcrt" - : addr[0] === "b" && addr[1] == "c" - ? "bc" - : undefined; + ? "bcrt" + : addr[0] === "b" && addr[1] == "c" + ? "bc" + : undefined; if (prefix === undefined) throw new Error("unknown bitcoin net"); const addr1 = segwit.default.encode(prefix, 0, first_part); diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts index 93cf9bba0..f25705545 100644 --- a/packages/taler-util/src/http-common.ts +++ b/packages/taler-util/src/http-common.ts @@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL3.0-or-later */ -import { CancellationToken } from "./CancellationToken.js"; +import type { CancellationToken } from "./CancellationToken.js"; import { Codec } from "./codec.js"; import { j2s } from "./helpers.js"; import { @@ -436,7 +436,10 @@ export function getExpiry( export interface HttpLibArgs { enableThrottling?: boolean; - allowHttp?: boolean; + /** + * Only allow HTTPS connections, not plain http. + */ + requireTls?: boolean; } export function encodeBody(body: any): ArrayBuffer { diff --git a/packages/taler-util/src/http-impl.node.ts b/packages/taler-util/src/http-impl.node.ts index 07648a28d..528d303be 100644 --- a/packages/taler-util/src/http-impl.node.ts +++ b/packages/taler-util/src/http-impl.node.ts @@ -63,11 +63,11 @@ const textDecoder = new TextDecoder(); export class HttpLibImpl implements HttpRequestLibrary { private throttle = new RequestThrottler(); private throttlingEnabled = true; - private allowHttp = false; + private requireTls = false; constructor(args?: HttpLibArgs) { this.throttlingEnabled = args?.enableThrottling ?? false; - this.allowHttp = args?.allowHttp ?? false; + this.requireTls = args?.requireTls ?? false; } /** @@ -94,7 +94,7 @@ export class HttpLibImpl implements HttpRequestLibrary { `request to origin ${parsedUrl.origin} was throttled`, ); } - if (!this.allowHttp && parsedUrl.protocol !== "https:") { + if (this.requireTls && parsedUrl.protocol !== "https:") { throw TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, { diff --git a/packages/taler-util/src/http-impl.qtart.ts b/packages/taler-util/src/http-impl.qtart.ts index 3e076e96d..fb642ac89 100644 --- a/packages/taler-util/src/http-impl.qtart.ts +++ b/packages/taler-util/src/http-impl.qtart.ts @@ -41,11 +41,11 @@ const textDecoder = new TextDecoder(); export class HttpLibImpl implements HttpRequestLibrary { private throttle = new RequestThrottler(); private throttlingEnabled = true; - private allowHttp = false; + private requireTls = false; constructor(args?: HttpLibArgs) { this.throttlingEnabled = args?.enableThrottling ?? false; - this.allowHttp = args?.allowHttp ?? false; + this.requireTls = args?.requireTls ?? false; } /** @@ -72,7 +72,7 @@ export class HttpLibImpl implements HttpRequestLibrary { `request to origin ${parsedUrl.origin} was throttled`, ); } - if (!this.allowHttp && parsedUrl.protocol !== "https:") { + if (this.requireTls && parsedUrl.protocol !== "https:") { throw TalerError.fromDetail( TalerErrorCode.WALLET_NETWORK_ERROR, { diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts index cfd0f7c47..568e2f438 100644 --- a/packages/taler-util/src/index.ts +++ b/packages/taler-util/src/index.ts @@ -39,3 +39,6 @@ export * from "./merchant-api-types.js"; export * from "./errors.js"; export * from "./iban.js"; export * from "./transaction-test-data.js"; +export * from "./libeufin-api-types.js"; +export * from "./MerchantApiClient.js"; +export * from "./bank-api-client.js"; diff --git a/packages/taler-util/src/libeufin-api-types.ts b/packages/taler-util/src/libeufin-api-types.ts new file mode 100644 index 000000000..aa3d0cb7a --- /dev/null +++ b/packages/taler-util/src/libeufin-api-types.ts @@ -0,0 +1,31 @@ +/* + This file is part of GNU Taler + (C) 2023 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +export type FacadeCredentials = + | NoFacadeCredentials + | BasicAuthFacadeCredentials; +export interface NoFacadeCredentials { + type: "none"; +} +export interface BasicAuthFacadeCredentials { + type: "basic"; + + // Username to use to authenticate + username: string; + + // Password to use to authenticate + password: string; +} diff --git a/packages/taler-util/src/merchant-api-types.ts b/packages/taler-util/src/merchant-api-types.ts index 9f00173f2..9933b93dc 100644 --- a/packages/taler-util/src/merchant-api-types.ts +++ b/packages/taler-util/src/merchant-api-types.ts @@ -47,6 +47,7 @@ import { WireAccount, codecForWireAccount, codecForList, + FacadeCredentials, } from "@gnu-taler/taler-util"; export interface MerchantPostOrderRequest { @@ -384,3 +385,19 @@ export const codecForMerchantReserveCreateConfirmation = .property("accounts", codecForList(codecForWireAccount())) .property("reserve_pub", codecForString()) .build("MerchantReserveCreateConfirmation"); + +export interface AccountAddDetails { + // payto:// URI of the account. + payto_uri: string; + + // URL from where the merchant can download information + // about incoming wire transfers to this account. + credit_facade_url?: string; + + // Credentials to use when accessing the credit facade. + // Never returned on a GET (as this may be somewhat + // sensitive data). Can be set in POST + // or PATCH requests to update (or delete) credentials. + // To really delete credentials, set them to the type: "none". + credit_facade_credentials?: FacadeCredentials; +} diff --git a/packages/taler-util/src/payto.ts b/packages/taler-util/src/payto.ts index 2b0af4cc2..60c4ba838 100644 --- a/packages/taler-util/src/payto.ts +++ b/packages/taler-util/src/payto.ts @@ -24,7 +24,7 @@ export type PaytoUri = | PaytoUriBitcoin; export interface PaytoUriGeneric { - targetType: string; + targetType: PaytoType | string; targetPath: string; params: { [name: string]: string }; } @@ -55,6 +55,8 @@ export interface PaytoUriBitcoin extends PaytoUriGeneric { const paytoPfx = "payto://"; +export type PaytoType = "iban" | "bitcoin" | "x-taler-bank" + export function buildPayto( type: "iban", iban: string, @@ -71,7 +73,7 @@ export function buildPayto( account: string, ): PaytoUriTalerBank; export function buildPayto( - type: "iban" | "bitcoin" | "x-taler-bank", + type: PaytoType, first: string, second?: string, ): PaytoUriGeneric { diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts index 55cda08a5..46ed37637 100644 --- a/packages/taler-util/src/time.ts +++ b/packages/taler-util/src/time.ts @@ -154,13 +154,27 @@ export interface TalerProtocolDuration { readonly d_us: number | "forever"; } +/** + * Timeshift in milliseconds. + */ let timeshift = 0; +/** + * Set timetravel offset in milliseconds. + * + * Use carefully and only for testing. + */ export function setDangerousTimetravel(dt: number): void { timeshift = dt; } export namespace Duration { + export function toMilliseconds(d: Duration): number { + if (d.d_ms === "forever") { + return Number.MAX_VALUE; + } + return d.d_ms; + } export function getRemaining( deadline: AbsoluteTime, now = AbsoluteTime.now(), diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts index 6331bc731..304183ceb 100644 --- a/packages/taler-util/src/transactions-types.ts +++ b/packages/taler-util/src/transactions-types.ts @@ -528,22 +528,6 @@ export interface OrderShortInfo { summary_i18n?: InternationalizedString; /** - * List of products that are part of the order - */ - products: Product[] | undefined; - - /** - * Time indicating when the order should be delivered. - * May be overwritten by individual products. - */ - delivery_date?: TalerProtocolTimestamp; - - /** - * Delivery location for (all!) products. - */ - delivery_location?: Location; - - /** * URL of the fulfillment, given by the merchant */ fulfillmentUrl?: string; @@ -724,7 +708,6 @@ export const codecForOrderShortInfo = (): Codec<OrderShortInfo> => .property("fulfillmentUrl", codecOptional(codecForString())) .property("merchant", codecForMerchantInfo()) .property("orderId", codecForString()) - .property("products", codecOptional(codecForList(codecForProduct()))) .property("summary", codecForString()) .property("summary_i18n", codecOptional(codecForInternationalizedString())) .build("OrderShortInfo"); diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index accab746f..c6f19c73f 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -1076,13 +1076,6 @@ export interface KnownBankAccounts { accounts: KnownBankAccountsInfo[]; } -export interface ExchangeTosStatusDetails { - acceptedVersion?: string; - currentVersion?: string; - contentType?: string; - content?: string; -} - /** * Wire fee for one wire method */ @@ -1253,7 +1246,6 @@ export interface ExchangeFullDetails { exchangeBaseUrl: string; currency: string; paytoUris: string[]; - tos: ExchangeTosStatusDetails; auditors: ExchangeAuditor[]; wireInfo: WireInfo; denomFees: DenomOperationMap<FeeDescription[]>; @@ -1317,14 +1309,6 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> => .property("denomination_keys", codecForList(codecForAuditorDenomSig())) .build("codecForExchangeAuditor"); -const codecForExchangeTos = (): Codec<ExchangeTosStatusDetails> => - buildCodecForObject<ExchangeTosStatusDetails>() - .property("acceptedVersion", codecOptional(codecForString())) - .property("currentVersion", codecOptional(codecForString())) - .property("contentType", codecOptional(codecForString())) - .property("content", codecOptional(codecForString())) - .build("ExchangeTos"); - export const codecForFeeDescriptionPair = (): Codec<FeeDescriptionPair> => buildCodecForObject<FeeDescriptionPair>() .property("group", codecForString()) @@ -1357,7 +1341,6 @@ export const codecForExchangeFullDetails = (): Codec<ExchangeFullDetails> => .property("currency", codecForString()) .property("exchangeBaseUrl", codecForString()) .property("paytoUris", codecForList(codecForString())) - .property("tos", codecForExchangeTos()) .property("auditors", codecForList(codecForExchangeAuditor())) .property("wireInfo", codecForWireInfo()) .property("denomFees", codecForFeesByOperations()) @@ -1465,22 +1448,11 @@ export interface ExchangeWithdrawalDetails { selectedDenoms: DenomSelectionState; /** - * Does the wallet know about an auditor for - * the exchange that the reserve. - */ - isAudited: boolean; - - /** * Did the user already accept the current terms of service for the exchange? */ termsOfServiceAccepted: boolean; /** - * The exchange is trusted directly. - */ - isTrusted: boolean; - - /** * The earliest deposit expiration of the selected coins. */ earliestDepositExpiration: TalerProtocolTimestamp; @@ -2466,7 +2438,7 @@ export interface PreparePeerPushCreditResponse { amount: AmountString; amountRaw: AmountString; amountEffective: AmountString; - peerPushPaymentIncomingId: string; + peerPushCreditId: string; transactionId: string; } @@ -2481,7 +2453,7 @@ export interface PreparePeerPullDebitResponse { amountRaw: AmountString; amountEffective: AmountString; - peerPullPaymentIncomingId: string; + peerPullDebitId: string; transactionId: string; } @@ -2504,7 +2476,7 @@ export interface ConfirmPeerPushCreditRequest { * * @deprecated specify transactionId instead! */ - peerPushPaymentIncomingId?: string; + peerPushCreditId?: string; transactionId?: string; } @@ -2519,7 +2491,7 @@ export interface AcceptPeerPullPaymentResponse { export const codecForConfirmPeerPushPaymentRequest = (): Codec<ConfirmPeerPushCreditRequest> => buildCodecForObject<ConfirmPeerPushCreditRequest>() - .property("peerPushPaymentIncomingId", codecOptional(codecForString())) + .property("peerPushCreditId", codecOptional(codecForString())) .property("transactionId", codecOptional(codecForString())) .build("ConfirmPeerPushCreditRequest"); @@ -2529,7 +2501,7 @@ export interface ConfirmPeerPullDebitRequest { * * @deprecated use transactionId instead */ - peerPullPaymentIncomingId?: string; + peerPullDebitId?: string; transactionId?: string; } @@ -2547,7 +2519,7 @@ export const codecForApplyDevExperiment = export const codecForAcceptPeerPullPaymentRequest = (): Codec<ConfirmPeerPullDebitRequest> => buildCodecForObject<ConfirmPeerPullDebitRequest>() - .property("peerPullPaymentIncomingId", codecOptional(codecForString())) + .property("peerPullDebitId", codecOptional(codecForString())) .property("transactionId", codecOptional(codecForString())) .build("ConfirmPeerPullDebitRequest"); @@ -2673,3 +2645,25 @@ export interface RecoverStoredBackupRequest { export interface DeleteStoredBackupRequest { name: string; } + +export const codecForDeleteStoredBackupRequest = + (): Codec<DeleteStoredBackupRequest> => + buildCodecForObject<DeleteStoredBackupRequest>() + .property("name", codecForString()) + .build("DeleteStoredBackupRequest"); + +export const codecForRecoverStoredBackupRequest = + (): Codec<RecoverStoredBackupRequest> => + buildCodecForObject<RecoverStoredBackupRequest>() + .property("name", codecForString()) + .build("RecoverStoredBackupRequest"); + +export interface TestingSetTimetravelRequest { + offsetMs: number; +} + +export const codecForTestingSetTimetravelRequest = + (): Codec<TestingSetTimetravelRequest> => + buildCodecForObject<TestingSetTimetravelRequest>() + .property("offsetMs", codecForNumber()) + .build("TestingSetTimetravelRequest"); diff --git a/packages/taler-util/tsconfig.json b/packages/taler-util/tsconfig.json index 34f35d253..2e97142ce 100644 --- a/packages/taler-util/tsconfig.json +++ b/packages/taler-util/tsconfig.json @@ -5,8 +5,8 @@ "declaration": true, "declarationMap": false, "target": "ES2020", - "module": "ES2020", - "moduleResolution": "node16", + "module": "Node16", + "moduleResolution": "Node16", "sourceMap": true, "lib": ["ES2020"], "types": ["node"], |