allow changing the wallet device ID

This commit is contained in:
Florian Dold 2021-06-14 11:21:29 +02:00
parent 9acd4a4060
commit 6e11b69cf5
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
6 changed files with 107 additions and 49 deletions

View File

@ -428,6 +428,18 @@ const backupCli = walletCli.subcommand("backupArgs", "backup", {
help: "Subcommands for backups", 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) => { backupCli.subcommand("exportPlain", "export-plain").action(async (args) => {
await withWallet(args, async (wallet) => { await withWallet(args, async (wallet) => {
const backup = await wallet.exportBackupPlain(); const backup = await wallet.exportBackupPlain();

View File

@ -1358,13 +1358,40 @@ export interface PurchaseRecord {
autoRefundDeadline: Timestamp | undefined; autoRefundDeadline: Timestamp | undefined;
} }
export const WALLET_BACKUP_STATE_KEY = "walletBackupState";
/** /**
* Configuration key/value entries to configure * Configuration key/value entries to configure
* the wallet. * the wallet.
*
*/ */
export interface ConfigRecord<T> { export type ConfigRecord =
key: string; | {
value: T; 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( config: describeStore(
describeContents<ConfigRecord<any>>("config", { keyPath: "key" }), describeContents<ConfigRecord>("config", { keyPath: "key" }),
{}, {},
), ),
auditorTrust: describeStore( auditorTrust: describeStore(
@ -1817,9 +1844,14 @@ export const WalletStoresV1 = {
), ),
}; };
export interface MetaConfigRecord {
key: string;
value: any;
}
export const walletMetadataStore = { export const walletMetadataStore = {
metaConfig: describeStore( metaConfig: describeStore(
describeContents<ConfigRecord<any>>("metaConfig", { keyPath: "key" }), describeContents<MetaConfigRecord>("metaConfig", { keyPath: "key" }),
{}, {},
), ),
}; };

View File

@ -53,7 +53,6 @@ import { InternalWalletState } from "../state";
import { import {
provideBackupState, provideBackupState,
getWalletBackupState, getWalletBackupState,
WALLET_BACKUP_STATE_KEY,
} from "./state"; } from "./state";
import { Amounts, getTimestampNow } from "@gnu-taler/taler-util"; import { Amounts, getTimestampNow } from "@gnu-taler/taler-util";
import { import {
@ -62,6 +61,7 @@ import {
RefundState, RefundState,
AbortStatus, AbortStatus,
ProposalStatus, ProposalStatus,
WALLET_BACKUP_STATE_KEY,
} from "../../db.js"; } from "../../db.js";
import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js"; import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js";
import { canonicalizeBaseUrl, canonicalJson } from "@gnu-taler/taler-util"; import { canonicalizeBaseUrl, canonicalJson } from "@gnu-taler/taler-util";

View File

@ -35,6 +35,8 @@ import {
BackupProviderRecord, BackupProviderRecord,
BackupProviderTerms, BackupProviderTerms,
ConfigRecord, ConfigRecord,
WalletBackupConfState,
WALLET_BACKUP_STATE_KEY,
} from "../../db.js"; } from "../../db.js";
import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants";
import { import {
@ -85,12 +87,7 @@ import { secretbox, secretbox_open } from "../../crypto/primitives/nacl-fast";
import { checkPaymentByProposalId, confirmPay, preparePayForUri } from "../pay"; import { checkPaymentByProposalId, confirmPay, preparePayForUri } from "../pay";
import { exportBackup } from "./export"; import { exportBackup } from "./export";
import { BackupCryptoPrecomputedData, importBackup } from "./import"; import { BackupCryptoPrecomputedData, importBackup } from "./import";
import { import { provideBackupState, getWalletBackupState } from "./state";
provideBackupState,
WALLET_BACKUP_STATE_KEY,
getWalletBackupState,
WalletBackupConfState,
} from "./state";
const logger = new Logger("operations/backup.ts"); const logger = new Logger("operations/backup.ts");
@ -720,10 +717,11 @@ async function backupRecoveryTheirs(
await ws.db await ws.db
.mktx((x) => ({ config: x.config, backupProviders: x.backupProviders })) .mktx((x) => ({ config: x.config, backupProviders: x.backupProviders }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
let backupStateEntry: let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
| ConfigRecord<WalletBackupConfState> WALLET_BACKUP_STATE_KEY,
| undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY); );
checkDbInvariant(!!backupStateEntry); checkDbInvariant(!!backupStateEntry);
checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY);
backupStateEntry.value.lastBackupNonce = undefined; backupStateEntry.value.lastBackupNonce = undefined;
backupStateEntry.value.lastBackupTimestamp = undefined; backupStateEntry.value.lastBackupTimestamp = undefined;
backupStateEntry.value.lastBackupCheckTimestamp = undefined; backupStateEntry.value.lastBackupCheckTimestamp = undefined;

View File

@ -14,50 +14,29 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
import { Timestamp } from "@gnu-taler/taler-util"; import {
import { ConfigRecord, WalletStoresV1 } from "../../db.js"; ConfigRecord,
WalletBackupConfState,
WalletStoresV1,
WALLET_BACKUP_STATE_KEY,
} from "../../db.js";
import { getRandomBytes, encodeCrock } from "../../index.js"; import { getRandomBytes, encodeCrock } from "../../index.js";
import { checkDbInvariant } from "../../util/invariants"; import { checkDbInvariant } from "../../util/invariants";
import { GetReadOnlyAccess } from "../../util/query.js"; import { GetReadOnlyAccess } from "../../util/query.js";
import { Wallet } from "../../wallet.js";
import { InternalWalletState } from "../state"; 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( export async function provideBackupState(
ws: InternalWalletState, ws: InternalWalletState,
): Promise<WalletBackupConfState> { ): Promise<WalletBackupConfState> {
const bs: ConfigRecord<WalletBackupConfState> | undefined = await ws.db const bs: ConfigRecord | undefined = await ws.db
.mktx((x) => ({ .mktx((x) => ({
config: x.config, config: x.config,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
return tx.config.get(WALLET_BACKUP_STATE_KEY); return await tx.config.get(WALLET_BACKUP_STATE_KEY);
}); });
if (bs) { if (bs) {
checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY);
return bs.value; return bs.value;
} }
// We need to generate the key outside of the transaction // We need to generate the key outside of the transaction
@ -72,15 +51,14 @@ export async function provideBackupState(
config: x.config, config: x.config,
})) }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
let backupStateEntry: let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
| ConfigRecord<WalletBackupConfState> WALLET_BACKUP_STATE_KEY,
| undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY); );
if (!backupStateEntry) { if (!backupStateEntry) {
backupStateEntry = { backupStateEntry = {
key: WALLET_BACKUP_STATE_KEY, key: WALLET_BACKUP_STATE_KEY,
value: { value: {
deviceId, deviceId,
clocks: { [deviceId]: 1 },
walletRootPub: k.pub, walletRootPub: k.pub,
walletRootPriv: k.priv, walletRootPriv: k.priv,
lastBackupPlainHash: undefined, lastBackupPlainHash: undefined,
@ -88,6 +66,7 @@ export async function provideBackupState(
}; };
await tx.config.put(backupStateEntry); await tx.config.put(backupStateEntry);
} }
checkDbInvariant(backupStateEntry.key === WALLET_BACKUP_STATE_KEY);
return backupStateEntry.value; return backupStateEntry.value;
}); });
} }
@ -98,5 +77,37 @@ export async function getWalletBackupState(
): Promise<WalletBackupConfState> { ): Promise<WalletBackupConfState> {
const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY); const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY);
checkDbInvariant(!!bs, "wallet backup state should be in DB"); checkDbInvariant(!!bs, "wallet backup state should be in DB");
checkDbInvariant(bs.key === WALLET_BACKUP_STATE_KEY);
return bs.value; return bs.value;
} }
export async function setWalletDeviceId(
ws: InternalWalletState,
deviceId: string,
): Promise<void> {
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<string> {
const bs = await provideBackupState(ws);
return bs.deviceId;
}

View File

@ -187,6 +187,7 @@ import { AsyncCondition } from "./util/promiseUtils";
import { TimerGroup } from "./util/timer"; import { TimerGroup } from "./util/timer";
import { getExchangeTrust } from "./operations/currencies.js"; import { getExchangeTrust } from "./operations/currencies.js";
import { DbAccess } from "./util/query.js"; import { DbAccess } from "./util/query.js";
import { setWalletDeviceId } from "./operations/backup/state.js";
const builtinAuditors: AuditorTrustRecord[] = [ const builtinAuditors: AuditorTrustRecord[] = [
{ {
@ -586,6 +587,10 @@ export class Wallet {
return deleteTransaction(this.ws, req.transactionId); return deleteTransaction(this.ws, req.transactionId);
} }
async setDeviceId(newDeviceId: string): Promise<void> {
return setWalletDeviceId(this.ws, newDeviceId);
}
/** /**
* Update or add exchange DB entry by fetching the /keys and /wire information. * Update or add exchange DB entry by fetching the /keys and /wire information.
*/ */