From 6e11b69cf5beb25fec1dfdff281877a76bf195a4 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 14 Jun 2021 11:21:29 +0200 Subject: [PATCH] allow changing the wallet device ID --- packages/taler-wallet-cli/src/index.ts | 12 +++ packages/taler-wallet-core/src/db.ts | 42 ++++++++-- .../src/operations/backup/export.ts | 2 +- .../src/operations/backup/index.ts | 16 ++-- .../src/operations/backup/state.ts | 79 +++++++++++-------- packages/taler-wallet-core/src/wallet.ts | 5 ++ 6 files changed, 107 insertions(+), 49 deletions(-) diff --git a/packages/taler-wallet-cli/src/index.ts b/packages/taler-wallet-cli/src/index.ts index 0f4199d57..a6a3ec666 100644 --- a/packages/taler-wallet-cli/src/index.ts +++ b/packages/taler-wallet-cli/src/index.ts @@ -428,6 +428,18 @@ const backupCli = walletCli.subcommand("backupArgs", "backup", { help: "Subcommands for backups", }); +backupCli + .subcommand("setDeviceId", "set-device-id") + .requiredArgument("deviceId", clk.STRING, { + help: "new device ID", + }) + .action(async (args) => { + await withWallet(args, async (wallet) => { + const backup = await wallet.setDeviceId(args.setDeviceId.deviceId); + console.log(JSON.stringify(backup, undefined, 2)); + }); + }); + backupCli.subcommand("exportPlain", "export-plain").action(async (args) => { await withWallet(args, async (wallet) => { const backup = await wallet.exportBackupPlain(); diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 5e2a3fefa..349a40427 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1358,13 +1358,40 @@ export interface PurchaseRecord { autoRefundDeadline: Timestamp | undefined; } +export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; + /** * Configuration key/value entries to configure * the wallet. + * */ -export interface ConfigRecord { - key: string; - value: T; +export type ConfigRecord = + | { + key: typeof WALLET_BACKUP_STATE_KEY; + value: WalletBackupConfState; + } + | { key: "currencyDefaultsApplied"; value: boolean }; + +export interface WalletBackupConfState { + deviceId: string; + walletRootPub: string; + walletRootPriv: string; + + /** + * Last hash of the canonicalized plain-text backup. + */ + lastBackupPlainHash?: string; + + /** + * Timestamp stored in the last backup. + */ + lastBackupTimestamp?: Timestamp; + + /** + * Last time we tried to do a backup. + */ + lastBackupCheckTimestamp?: Timestamp; + lastBackupNonce?: string; } /** @@ -1671,7 +1698,7 @@ export const WalletStoresV1 = { }, ), config: describeStore( - describeContents>("config", { keyPath: "key" }), + describeContents("config", { keyPath: "key" }), {}, ), auditorTrust: describeStore( @@ -1817,9 +1844,14 @@ export const WalletStoresV1 = { ), }; +export interface MetaConfigRecord { + key: string; + value: any; +} + export const walletMetadataStore = { metaConfig: describeStore( - describeContents>("metaConfig", { keyPath: "key" }), + describeContents("metaConfig", { keyPath: "key" }), {}, ), }; diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index a6b2ff2a7..8d57ecd80 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -53,7 +53,6 @@ import { InternalWalletState } from "../state"; import { provideBackupState, getWalletBackupState, - WALLET_BACKUP_STATE_KEY, } from "./state"; import { Amounts, getTimestampNow } from "@gnu-taler/taler-util"; import { @@ -62,6 +61,7 @@ import { RefundState, AbortStatus, ProposalStatus, + WALLET_BACKUP_STATE_KEY, } from "../../db.js"; import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js"; import { canonicalizeBaseUrl, canonicalJson } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index bb067dfb5..86f1df541 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -35,6 +35,8 @@ import { BackupProviderRecord, BackupProviderTerms, ConfigRecord, + WalletBackupConfState, + WALLET_BACKUP_STATE_KEY, } from "../../db.js"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; import { @@ -85,12 +87,7 @@ import { secretbox, secretbox_open } from "../../crypto/primitives/nacl-fast"; import { checkPaymentByProposalId, confirmPay, preparePayForUri } from "../pay"; import { exportBackup } from "./export"; import { BackupCryptoPrecomputedData, importBackup } from "./import"; -import { - provideBackupState, - WALLET_BACKUP_STATE_KEY, - getWalletBackupState, - WalletBackupConfState, -} from "./state"; +import { provideBackupState, getWalletBackupState } from "./state"; const logger = new Logger("operations/backup.ts"); @@ -720,10 +717,11 @@ async function backupRecoveryTheirs( await ws.db .mktx((x) => ({ config: x.config, backupProviders: x.backupProviders })) .runReadWrite(async (tx) => { - let backupStateEntry: - | ConfigRecord - | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY); + let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + WALLET_BACKUP_STATE_KEY, + ); checkDbInvariant(!!backupStateEntry); + checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY); backupStateEntry.value.lastBackupNonce = undefined; backupStateEntry.value.lastBackupTimestamp = undefined; backupStateEntry.value.lastBackupCheckTimestamp = undefined; diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts index 226880439..1ab0c027e 100644 --- a/packages/taler-wallet-core/src/operations/backup/state.ts +++ b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -14,50 +14,29 @@ GNU Taler; see the file COPYING. If not, see */ -import { Timestamp } from "@gnu-taler/taler-util"; -import { ConfigRecord, WalletStoresV1 } from "../../db.js"; +import { + ConfigRecord, + WalletBackupConfState, + WalletStoresV1, + WALLET_BACKUP_STATE_KEY, +} from "../../db.js"; import { getRandomBytes, encodeCrock } from "../../index.js"; import { checkDbInvariant } from "../../util/invariants"; import { GetReadOnlyAccess } from "../../util/query.js"; -import { Wallet } from "../../wallet.js"; import { InternalWalletState } from "../state"; -export interface WalletBackupConfState { - deviceId: string; - walletRootPub: string; - walletRootPriv: string; - clocks: { [device_id: string]: number }; - - /** - * Last hash of the canonicalized plain-text backup. - */ - lastBackupPlainHash?: string; - - /** - * Timestamp stored in the last backup. - */ - lastBackupTimestamp?: Timestamp; - - /** - * Last time we tried to do a backup. - */ - lastBackupCheckTimestamp?: Timestamp; - lastBackupNonce?: string; -} - -export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; - export async function provideBackupState( ws: InternalWalletState, ): Promise { - const bs: ConfigRecord | undefined = await ws.db + const bs: ConfigRecord | undefined = await ws.db .mktx((x) => ({ config: x.config, })) .runReadOnly(async (tx) => { - return tx.config.get(WALLET_BACKUP_STATE_KEY); + return await tx.config.get(WALLET_BACKUP_STATE_KEY); }); if (bs) { + checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY); return bs.value; } // We need to generate the key outside of the transaction @@ -72,15 +51,14 @@ export async function provideBackupState( config: x.config, })) .runReadWrite(async (tx) => { - let backupStateEntry: - | ConfigRecord - | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY); + let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + WALLET_BACKUP_STATE_KEY, + ); if (!backupStateEntry) { backupStateEntry = { key: WALLET_BACKUP_STATE_KEY, value: { deviceId, - clocks: { [deviceId]: 1 }, walletRootPub: k.pub, walletRootPriv: k.priv, lastBackupPlainHash: undefined, @@ -88,6 +66,7 @@ export async function provideBackupState( }; await tx.config.put(backupStateEntry); } + checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY); return backupStateEntry.value; }); } @@ -98,5 +77,37 @@ export async function getWalletBackupState( ): Promise { const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY); checkDbInvariant(!!bs, "wallet backup state should be in DB"); + checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY); return bs.value; } + +export async function setWalletDeviceId( + ws: InternalWalletState, + deviceId: string, +): Promise { + await provideBackupState(ws); + await ws.db + .mktx((x) => ({ + config: x.config, + })) + .runReadWrite(async (tx) => { + let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + WALLET_BACKUP_STATE_KEY, + ); + if ( + !backupStateEntry || + backupStateEntry.key !== WALLET_BACKUP_STATE_KEY + ) { + return; + } + backupStateEntry.value.deviceId = deviceId; + await tx.config.put(backupStateEntry); + }); +} + +export async function getWalletDeviceId( + ws: InternalWalletState, +): Promise { + const bs = await provideBackupState(ws); + return bs.deviceId; +} diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 854039a8f..900a2e779 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -187,6 +187,7 @@ import { AsyncCondition } from "./util/promiseUtils"; import { TimerGroup } from "./util/timer"; import { getExchangeTrust } from "./operations/currencies.js"; import { DbAccess } from "./util/query.js"; +import { setWalletDeviceId } from "./operations/backup/state.js"; const builtinAuditors: AuditorTrustRecord[] = [ { @@ -586,6 +587,10 @@ export class Wallet { return deleteTransaction(this.ws, req.transactionId); } + async setDeviceId(newDeviceId: string): Promise { + return setWalletDeviceId(this.ws, newDeviceId); + } + /** * Update or add exchange DB entry by fetching the /keys and /wire information. */