summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/backupTypes.ts2
-rw-r--r--packages/taler-util/src/walletTypes.ts10
-rw-r--r--packages/taler-wallet-core/src/db.ts63
-rw-r--r--packages/taler-wallet-core/src/operations/backup/export.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts20
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts45
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts29
-rw-r--r--packages/taler-wallet-core/src/wallet.ts89
8 files changed, 170 insertions, 93 deletions
diff --git a/packages/taler-util/src/backupTypes.ts b/packages/taler-util/src/backupTypes.ts
index a1506e90f..0270f2586 100644
--- a/packages/taler-util/src/backupTypes.ts
+++ b/packages/taler-util/src/backupTypes.ts
@@ -1165,8 +1165,6 @@ export interface BackupExchange {
currency: string;
- protocol_version_range: string;
-
/**
* Time when the pointer to the exchange details
* was last updated.
diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts
index c6063597d..0b2ef1d5f 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -620,7 +620,7 @@ export interface KnownBankAccounts {
accounts: KnownBankAccountsInfo[];
}
-export interface ExchangeTos {
+export interface ExchangeTosStatusDetails {
acceptedVersion?: string;
currentVersion?: string;
contentType?: string;
@@ -805,7 +805,7 @@ export interface ExchangeFullDetails {
exchangeBaseUrl: string;
currency: string;
paytoUris: string[];
- tos: ExchangeTos;
+ tos: ExchangeTosStatusDetails;
auditors: ExchangeAuditor[];
wireInfo: WireInfo;
denomFees: DenomOperationMap<FeeDescription[]>;
@@ -817,7 +817,7 @@ export interface ExchangeListItem {
exchangeBaseUrl: string;
currency: string;
paytoUris: string[];
- tos: ExchangeTos;
+ tos: ExchangeTosStatusDetails;
}
const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
@@ -833,8 +833,8 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> =>
.property("denomination_keys", codecForList(codecForAuditorDenomSig()))
.build("codecForExchangeAuditor");
-const codecForExchangeTos = (): Codec<ExchangeTos> =>
- buildCodecForObject<ExchangeTos>()
+const codecForExchangeTos = (): Codec<ExchangeTosStatusDetails> =>
+ buildCodecForObject<ExchangeTosStatusDetails>()
.property("acceptedVersion", codecOptional(codecForString()))
.property("currentVersion", codecOptional(codecForString()))
.property("contentType", codecOptional(codecForString()))
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 6dfb06c15..304efd852 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -80,7 +80,7 @@ import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
* for all previous versions must be written, which should be
* avoided.
*/
-export const TALER_DB_NAME = "taler-wallet-main-v6";
+export const TALER_DB_NAME = "taler-wallet-main-v7";
/**
* Name of the metadata database. This database is used
@@ -99,7 +99,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
* backwards-compatible way or object stores and indices
* are added.
*/
-export const WALLET_DB_MINOR_VERSION = 2;
+export const WALLET_DB_MINOR_VERSION = 1;
/**
* Ranges for operation status fields.
@@ -451,39 +451,40 @@ export interface ExchangeDetailsRecord {
signingKeys: ExchangeSignKeyJson[];
/**
- * Terms of service text or undefined if not downloaded yet.
- *
- * This is just used as a cache of the last downloaded ToS.
- *
- * FIXME: Put in separate object store!
+ * Etag of the current ToS of the exchange.
*/
- termsOfServiceText: string | undefined;
+ tosCurrentEtag: string;
/**
- * content-type of the last downloaded termsOfServiceText.
- *
- * * FIXME: Put in separate object store!
+ * Information about ToS acceptance from the user.
*/
- termsOfServiceContentType: string | undefined;
+ tosAccepted:
+ | {
+ etag: string;
+ timestamp: TalerProtocolTimestamp;
+ }
+ | undefined;
- /**
- * ETag for last terms of service download.
- */
- termsOfServiceLastEtag: string | undefined;
+ wireInfo: WireInfo;
+}
- /**
- * ETag for last terms of service accepted.
- */
- termsOfServiceAcceptedEtag: string | undefined;
+export interface ExchangeTosRecord {
+ exchangeBaseUrl: string;
+
+ etag: string;
/**
- * Timestamp when the ToS was accepted.
+ * Terms of service text or undefined if not downloaded yet.
+ *
+ * This is just used as a cache of the last downloaded ToS.
*
- * Used during backup merging.
*/
- termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined;
+ termsOfServiceText: string | undefined;
- wireInfo: WireInfo;
+ /**
+ * Content-type of the last downloaded termsOfServiceText.
+ */
+ termsOfServiceContentType: string | undefined;
}
export interface ExchangeDetailsPointer {
@@ -492,11 +493,6 @@ export interface ExchangeDetailsPointer {
currency: string;
/**
- * Last observed protocol version range offered by the exchange.
- */
- protocolVersionRange: string;
-
- /**
* Timestamp when the (masterPublicKey, currency) pointer
* has been updated.
*/
@@ -1899,6 +1895,14 @@ export const WalletStoresV1 = {
byReservePub: describeIndex("byReservePub", "reservePub", {}),
},
),
+ exchangeTos: describeStore(
+ "exchangeTos",
+ describeContents<ExchangeTosRecord>({
+ keyPath: ["exchangeBaseUrl", "etag"],
+ autoIncrement: true,
+ }),
+ {},
+ ),
config: describeStore(
"config",
describeContents<ConfigRecord>({ keyPath: "key" }),
@@ -2116,7 +2120,6 @@ export const WalletStoresV1 = {
"bankAccounts",
describeContents<BankAccountsRecord>({
keyPath: "uri",
- versionAdded: 2,
}),
{},
),
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts
index 3ba0d85e6..30e61e382 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -298,7 +298,6 @@ export async function exportBackup(
currency: dp.currency,
master_public_key: dp.masterPublicKey,
update_clock: dp.updateClock,
- protocol_version_range: dp.protocolVersionRange,
});
});
@@ -358,8 +357,8 @@ export async function exportBackup(
purseTimeout: x.purseTimeout,
startDate: x.startDate,
})),
- tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
- tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
+ tos_accepted_etag: ex.tosAccepted?.etag,
+ tos_accepted_timestamp: ex.tosAccepted?.timestamp,
denominations:
backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
});
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
index 59fd3c398..599b02dea 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -351,7 +351,6 @@ export async function importBackup(
currency: backupExchange.currency,
masterPublicKey: backupExchange.master_public_key,
updateClock: backupExchange.update_clock,
- protocolVersionRange: backupExchange.protocol_version_range,
},
permanent: true,
lastUpdate: undefined,
@@ -388,14 +387,18 @@ export async function importBackup(
wadFee: Amounts.parseOrThrow(fee.wad_fee),
});
}
+ let tosAccepted = undefined;
+ if (
+ backupExchangeDetails.tos_accepted_etag &&
+ backupExchangeDetails.tos_accepted_timestamp
+ ) {
+ tosAccepted = {
+ etag: backupExchangeDetails.tos_accepted_etag,
+ timestamp: backupExchangeDetails.tos_accepted_timestamp,
+ };
+ }
await tx.exchangeDetails.put({
exchangeBaseUrl: backupExchangeDetails.base_url,
- termsOfServiceAcceptedEtag: backupExchangeDetails.tos_accepted_etag,
- termsOfServiceText: undefined,
- termsOfServiceLastEtag: undefined,
- termsOfServiceContentType: undefined,
- termsOfServiceAcceptedTimestamp:
- backupExchangeDetails.tos_accepted_timestamp,
wireInfo,
currency: backupExchangeDetails.currency,
auditors: backupExchangeDetails.auditors.map((x) => ({
@@ -406,6 +409,8 @@ export async function importBackup(
masterPublicKey: backupExchangeDetails.master_public_key,
protocolVersionRange: backupExchangeDetails.protocol_version,
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
+ tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
+ tosAccepted,
globalFees: backupExchangeDetails.global_fees.map((x) => ({
accountFee: Amounts.parseOrThrow(x.accountFee),
historyFee: Amounts.parseOrThrow(x.historyFee),
@@ -419,7 +424,6 @@ export async function importBackup(
purseTimeout: x.purseTimeout,
startDate: x.startDate,
})),
-
signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
key: x.key,
master_sig: x.master_sig,
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index d3905b74b..6569cb394 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -174,24 +174,40 @@ export async function getExchangeDetails(
getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
db.mktx((x) => [x.exchanges, x.exchangeDetails]);
+/**
+ * Update the database based on the download of the terms of service.
+ */
export async function updateExchangeTermsOfService(
ws: InternalWalletState,
exchangeBaseUrl: string,
tos: ExchangeTosDownloadResult,
): Promise<void> {
await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails])
+ .mktx((x) => [x.exchanges, x.exchangeTos, x.exchangeDetails])
.runReadWrite(async (tx) => {
const d = await getExchangeDetails(tx, exchangeBaseUrl);
+ let tosRecord = await tx.exchangeTos.get([exchangeBaseUrl, tos.tosEtag]);
+ if (!tosRecord) {
+ tosRecord = {
+ etag: tos.tosEtag,
+ exchangeBaseUrl,
+ termsOfServiceContentType: tos.tosContentType,
+ termsOfServiceText: tos.tosText,
+ };
+ await tx.exchangeTos.put(tosRecord);
+ }
if (d) {
- d.termsOfServiceText = tos.tosText;
- d.termsOfServiceContentType = tos.tosContentType;
- d.termsOfServiceLastEtag = tos.tosEtag;
+ d.tosCurrentEtag = tos.tosEtag;
await tx.exchangeDetails.put(d);
}
});
}
+/**
+ * Mark a ToS version as accepted by the user.
+ *
+ * @param etag version of the ToS to accept, or current ToS version of not given
+ */
export async function acceptExchangeTermsOfService(
ws: InternalWalletState,
exchangeBaseUrl: string,
@@ -202,7 +218,10 @@ export async function acceptExchangeTermsOfService(
.runReadWrite(async (tx) => {
const d = await getExchangeDetails(tx, exchangeBaseUrl);
if (d) {
- d.termsOfServiceAcceptedEtag = etag;
+ d.tosAccepted = {
+ etag: etag || d.tosCurrentEtag,
+ timestamp: TalerProtocolTimestamp.now(),
+ };
await tx.exchangeDetails.put(d);
}
});
@@ -611,7 +630,8 @@ export async function updateExchangeFromUrlHandler(
["text/plain"],
);
const tosHasBeenAccepted =
- exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag;
+ exchangeDetails?.tosAccepted &&
+ exchangeDetails.tosAccepted.etag === tosDownload.tosEtag;
let recoupGroupId: string | undefined;
@@ -647,13 +667,13 @@ export async function updateExchangeFromUrlHandler(
globalFees,
exchangeBaseUrl: r.baseUrl,
wireInfo,
- termsOfServiceText: tosDownload.tosText,
- termsOfServiceAcceptedEtag: tosHasBeenAccepted
- ? tosDownload.tosEtag
+ tosCurrentEtag: tosDownload.tosContentType,
+ tosAccepted: tosHasBeenAccepted
+ ? {
+ etag: tosDownload.tosEtag,
+ timestamp: TalerProtocolTimestamp.now(),
+ }
: undefined,
- termsOfServiceContentType: tosDownload.tosContentType,
- termsOfServiceLastEtag: tosDownload.tosEtag,
- termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
};
// FIXME: only update if pointer got updated
r.lastUpdate = TalerProtocolTimestamp.now();
@@ -665,7 +685,6 @@ export async function updateExchangeFromUrlHandler(
masterPublicKey: details.masterPublicKey,
// FIXME: only change if pointer really changed
updateClock: TalerProtocolTimestamp.now(),
- protocolVersionRange: keysInfo.protocolVersion,
};
await tx.exchanges.put(r);
await tx.exchangeDetails.put(details);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index 145a2d9c7..700c4620c 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -69,6 +69,7 @@ import {
CoinStatus,
DenominationRecord,
DenominationVerificationStatus,
+ ExchangeTosRecord,
PlanchetRecord,
PlanchetStatus,
WalletStoresV1,
@@ -1278,12 +1279,8 @@ export async function getExchangeWithdrawalInfo(
}
let tosAccepted = false;
-
- if (exchangeDetails.termsOfServiceLastEtag) {
- if (
- exchangeDetails.termsOfServiceAcceptedEtag ===
- exchangeDetails.termsOfServiceLastEtag
- ) {
+ if (exchangeDetails.tosAccepted?.timestamp) {
+ if (exchangeDetails.tosAccepted.etag === exchangeDetails.tosCurrentEtag) {
tosAccepted = true;
}
}
@@ -1357,7 +1354,12 @@ export async function getWithdrawalDetailsForUri(
const exchanges: ExchangeListItem[] = [];
await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+ .mktx((x) => [
+ x.exchanges,
+ x.exchangeDetails,
+ x.exchangeTos,
+ x.denominations,
+ ])
.runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) {
@@ -1366,14 +1368,19 @@ export async function getWithdrawalDetailsForUri(
.iter(r.baseUrl)
.toArray();
if (details && denominations) {
+ const tosRecord = await tx.exchangeTos.get([
+ details.exchangeBaseUrl,
+ details.tosCurrentEtag,
+ ]);
exchanges.push({
exchangeBaseUrl: details.exchangeBaseUrl,
currency: details.currency,
+ // FIXME: We probably don't want to include the full ToS here!
tos: {
- acceptedVersion: details.termsOfServiceAcceptedEtag,
- currentVersion: details.termsOfServiceLastEtag,
- contentType: details.termsOfServiceContentType,
- content: details.termsOfServiceText,
+ acceptedVersion: details.tosAccepted?.etag,
+ currentVersion: details.tosCurrentEtag,
+ contentType: tosRecord?.termsOfServiceContentType ?? "",
+ content: tosRecord?.termsOfServiceText ?? "",
},
paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
});
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 22cbeb4b8..ef7a745ab 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -94,6 +94,7 @@ import {
WalletCoreVersion,
WalletNotification,
codecForSetDevModeRequest,
+ ExchangeTosStatusDetails,
} from "@gnu-taler/taler-util";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import {
@@ -107,6 +108,8 @@ import {
CoinStatus,
ConfigRecordKey,
DenominationRecord,
+ ExchangeDetailsRecord,
+ ExchangeTosRecord,
exportDb,
importDb,
WalletStoresV1,
@@ -228,7 +231,11 @@ import {
OpenedPromise,
openPromise,
} from "./util/promiseUtils.js";
-import { DbAccess, GetReadWriteAccess } from "./util/query.js";
+import {
+ DbAccess,
+ GetReadOnlyAccess,
+ GetReadWriteAccess,
+} from "./util/query.js";
import { OperationAttemptResult } from "./util/retries.js";
import { TimerAPI, TimerGroup } from "./util/timer.js";
import {
@@ -461,6 +468,10 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
});
}
+/**
+ * Get the exchange ToS in the requested format.
+ * Try to download in the accepted format not cached.
+ */
async function getExchangeTos(
ws: InternalWalletState,
exchangeBaseUrl: string,
@@ -468,9 +479,14 @@ async function getExchangeTos(
): Promise<GetExchangeTosResult> {
// FIXME: download ToS in acceptable format if passed!
const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
- const content = exchangeDetails.termsOfServiceText;
- const currentEtag = exchangeDetails.termsOfServiceLastEtag;
- const contentType = exchangeDetails.termsOfServiceContentType;
+ const tosDetails = await ws.db
+ .mktx((x) => [x.exchangeTos])
+ .runReadOnly(async (tx) => {
+ return await getExchangeTosStatusDetails(tx, exchangeDetails);
+ });
+ const content = tosDetails.content;
+ const currentEtag = tosDetails.currentVersion;
+ const contentType = tosDetails.contentType;
if (
content === undefined ||
currentEtag === undefined ||
@@ -483,7 +499,7 @@ async function getExchangeTos(
acceptedFormat.findIndex((f) => f === contentType) !== -1
) {
return {
- acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
+ acceptedEtag: exchangeDetails.tosAccepted?.etag,
currentEtag,
content,
contentType,
@@ -499,16 +515,17 @@ async function getExchangeTos(
if (tosDownload.tosContentType === contentType) {
return {
- acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
+ acceptedEtag: exchangeDetails.tosAccepted?.etag,
currentEtag,
content,
contentType,
};
}
+
await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
return {
- acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
+ acceptedEtag: exchangeDetails.tosAccepted?.etag,
currentEtag: tosDownload.tosEtag,
content: tosDownload.tosText,
contentType: tosDownload.tosContentType,
@@ -585,12 +602,43 @@ async function forgetKnownBankAccounts(
return;
}
+async function getExchangeTosStatusDetails(
+ tx: GetReadOnlyAccess<{ exchangeTos: typeof WalletStoresV1.exchangeTos }>,
+ exchangeDetails: ExchangeDetailsRecord,
+): Promise<ExchangeTosStatusDetails> {
+ let exchangeTos = await tx.exchangeTos.get([
+ exchangeDetails.exchangeBaseUrl,
+ exchangeDetails.tosCurrentEtag,
+ ]);
+
+ if (!exchangeTos) {
+ exchangeTos = {
+ etag: "not-available",
+ termsOfServiceContentType: "text/plain",
+ termsOfServiceText: "terms of service unavailable",
+ exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
+ };
+ }
+
+ return {
+ acceptedVersion: exchangeDetails.tosAccepted?.etag,
+ content: exchangeTos.termsOfServiceContentType,
+ contentType: exchangeTos.termsOfServiceContentType,
+ currentVersion: exchangeTos.etag,
+ };
+}
+
async function getExchanges(
ws: InternalWalletState,
): Promise<ExchangesListResponse> {
const exchanges: ExchangeListItem[] = [];
await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+ .mktx((x) => [
+ x.exchanges,
+ x.exchangeDetails,
+ x.exchangeTos,
+ x.denominations,
+ ])
.runReadOnly(async (tx) => {
const exchangeRecords = await tx.exchanges.iter().toArray();
for (const r of exchangeRecords) {
@@ -612,15 +660,12 @@ async function getExchanges(
continue;
}
+ const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
+
exchanges.push({
exchangeBaseUrl: r.baseUrl,
currency,
- tos: {
- acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
- currentVersion: exchangeDetails.termsOfServiceLastEtag,
- contentType: exchangeDetails.termsOfServiceContentType,
- content: exchangeDetails.termsOfServiceText,
- },
+ tos,
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
});
}
@@ -634,7 +679,12 @@ async function getExchangeDetailedInfo(
): Promise<ExchangeFullDetails> {
//TODO: should we use the forceUpdate parameter?
const exchange = await ws.db
- .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+ .mktx((x) => [
+ x.exchanges,
+ x.exchangeTos,
+ x.exchangeDetails,
+ x.denominations,
+ ])
.runReadOnly(async (tx) => {
const ex = await tx.exchanges.get(exchangeBaseurl);
const dp = ex?.detailsPointer;
@@ -656,6 +706,8 @@ async function getExchangeDetailedInfo(
return;
}
+ const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
+
const denominations: DenominationInfo[] = denominationRecords.map((x) =>
DenominationRecord.toDenomInfo(x),
);
@@ -664,12 +716,7 @@ async function getExchangeDetailedInfo(
info: {
exchangeBaseUrl: ex.baseUrl,
currency,
- tos: {
- acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
- currentVersion: exchangeDetails.termsOfServiceLastEtag,
- contentType: exchangeDetails.termsOfServiceContentType,
- content: exchangeDetails.termsOfServiceText,
- },
+ tos,
paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
auditors: exchangeDetails.auditors,
wireInfo: exchangeDetails.wireInfo,