From a4aa39692add4eacb00efbb91bfd90dda543a56f Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Sat, 1 Jun 2024 16:41:21 +0200 Subject: [PATCH] clean up and deduplicate --- frontend/src/common/userdata.ts | 73 +++++++++++++++---- frontend/src/components/CreateVault.vue | 6 +- .../src/components/RecoverVaultDialog.vue | 14 ++-- frontend/src/components/VaultDetails.vue | 10 +-- 4 files changed, 71 insertions(+), 32 deletions(-) diff --git a/frontend/src/common/userdata.ts b/frontend/src/common/userdata.ts index 9e451101..97185f40 100644 --- a/frontend/src/common/userdata.ts +++ b/frontend/src/common/userdata.ts @@ -8,6 +8,9 @@ class UserData { #me?: Promise; #browserKeys?: Promise; + /** + * Gets the user DTO representing the currently logged in user. + */ public get me(): Promise { if (!this.#me) { this.#me = backend.users.me(true); @@ -15,6 +18,9 @@ class UserData { return this.#me; } + /** + * Gets the device key pair stored for this user in the currently used browser. + */ public get browserKeys(): Promise { return this.me.then(me => { if (!this.#browserKeys) { @@ -24,23 +30,59 @@ class UserData { }); } + /** + * Gets the device that represents the currently used browser. + */ public get browser(): Promise { return this.me.then(async me => { const browserKeys = await this.browserKeys; - if (browserKeys == null) { - return undefined; + const browserId = await browserKeys?.id(); + return browserId ? me.devices.find(d => d.id === browserId) : undefined; + }); + } + + /** + * Gets the ECDH public key of the user. + * + * @see UserDto.ecdhPublicKey + */ + public get ecdhPublicKey(): Promise { + return this.me.then(me => { + if (!me.ecdhPublicKey) { + throw new Error('User not initialized.'); + } + return base64.parse(me.ecdhPublicKey); + }); + } + + /** + * Gets the ECDSA public key of the user, if available. + * + * @see UserDto.ecdsaPublicKey + */ + public get ecdsaPublicKey(): Promise { + return this.me.then(me => { + if (me.ecdsaPublicKey) { + return base64.parse(me.ecdsaPublicKey); } else { - const browserId = await browserKeys.id(); - return me.devices.find(d => d.id === browserId); + return undefined; } }); } + /** + * Invalidates the cached user data and reloads it in the backend. + */ public async reload() { this.#me = backend.users.me(true); this.#browserKeys = undefined; } + /** + * Creates a new browser key pair for the user. + * This does not change the device DTO stored in the backend. + * @returns A new browser key pair for the user. + */ public async createBrowserKeys(): Promise { const me = await this.me; const browserKeys = await BrowserKeys.create(); @@ -49,23 +91,26 @@ class UserData { return browserKeys; } + /** + * Decrypts the user keys using the setup code. + * @param setupCode the setup code + * @returns The user's key pairs + */ public async decryptUserKeysWithSetupCode(setupCode: string): Promise { const me = await this.me; - if (!me.privateKey || !me.ecdhPublicKey) { + if (!me.privateKey) { throw new Error('User not initialized.'); } - const ecdhPublicKey = base64.parse(me.ecdhPublicKey); - const ecdsaPublicKey = me.ecdsaPublicKey ? base64.parse(me.ecdsaPublicKey) : undefined; - const userKeys = await UserKeys.recover(me.privateKey, setupCode, ecdhPublicKey, ecdsaPublicKey); + const userKeys = await UserKeys.recover(me.privateKey, setupCode, await this.ecdhPublicKey, await this.ecdsaPublicKey); await this.addEcdsaKeyIfMissing(userKeys); return userKeys; } + /** + * Decrypts the user keys using the device key stored in the currently used browser. + * @returns The user's key pairs + */ public async decryptUserKeysWithBrowser(): Promise { - const me = await this.me; - if (!me.ecdhPublicKey) { - throw new Error('User not initialized.'); - } const browserKeys = await this.browserKeys; if (!browserKeys) { throw new Error('Browser keys not found.'); @@ -74,9 +119,7 @@ class UserData { if (!browser) { throw new Error('Device not initialized.'); } - const ecdhPublicKey = base64.parse(me.ecdhPublicKey); - const ecdsaPublicKey = me.ecdsaPublicKey ? base64.parse(me.ecdsaPublicKey) : undefined; - const userKeys = await UserKeys.decryptOnBrowser(browser.userPrivateKey, browserKeys.keyPair.privateKey, ecdhPublicKey, ecdsaPublicKey); + const userKeys = await UserKeys.decryptOnBrowser(browser.userPrivateKey, browserKeys.keyPair.privateKey, await this.ecdhPublicKey, await this.ecdsaPublicKey); await this.addEcdsaKeyIfMissing(userKeys); return userKeys; } diff --git a/frontend/src/components/CreateVault.vue b/frontend/src/components/CreateVault.vue index 2a9e0c82..5b9995f8 100644 --- a/frontend/src/components/CreateVault.vue +++ b/frontend/src/components/CreateVault.vue @@ -182,7 +182,6 @@ import { ClipboardIcon } from '@heroicons/vue/20/solid'; import { ArrowPathIcon, CheckIcon, KeyIcon } from '@heroicons/vue/24/outline'; import { ArrowDownTrayIcon } from '@heroicons/vue/24/solid'; import { saveAs } from 'file-saver'; -import { base64 } from 'rfc4648'; import { onMounted, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import backend, { PaymentRequiredError } from '../common/backend'; @@ -290,12 +289,9 @@ async function createVault() { } processing.value = true; const owner = await userdata.me; - if (!owner.ecdhPublicKey) { - throw new Error('Invalid state'); - } const vaultId = crypto.randomUUID(); vaultConfig.value = await VaultConfig.create(vaultId, vaultKeys.value); - const ownerJwe = await vaultKeys.value.encryptForUser(base64.parse(owner.ecdhPublicKey)); + const ownerJwe = await vaultKeys.value.encryptForUser(await userdata.ecdhPublicKey); await backend.vaults.createOrUpdateVault(vaultId, vaultName.value, false, vaultDescription.value); await backend.vaults.grantAccess(vaultId, { userId: owner.id, token: ownerJwe }); state.value = State.Finished; diff --git a/frontend/src/components/RecoverVaultDialog.vue b/frontend/src/components/RecoverVaultDialog.vue index c3968ee0..b6240c85 100644 --- a/frontend/src/components/RecoverVaultDialog.vue +++ b/frontend/src/components/RecoverVaultDialog.vue @@ -52,12 +52,12 @@