allow changing the wallet device ID
This commit is contained in:
parent
9acd4a4060
commit
6e11b69cf5
@ -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();
|
||||||
|
@ -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" }),
|
||||||
{},
|
{},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
@ -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";
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user