From 8618dc7bafa7a3f87f64e5cb618efe3fab3b4479 Mon Sep 17 00:00:00 2001 From: Dawid Urbas Date: Fri, 19 Jul 2024 08:15:54 +0200 Subject: [PATCH 01/16] Test push --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 662b3f5..ff0fd83 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Holo Key Manager is a browser extension for generating and managing Holochain ap ## Usage +test + A javascript client library is available at `@holo-host/holo-key-manager-js-client`. See client documentation [here](holo-key-manager-js-client/README.md) ## Architecture From 1b4d44ad9ee7cf2aa650de107c62ea1b571fd809 Mon Sep 17 00:00:00 2001 From: Dawid Urbas Date: Fri, 19 Jul 2024 10:14:49 +0200 Subject: [PATCH 02/16] Fix before audit --- holo-key-manager-extension/README.md | 39 +++++++- holo-key-manager-extension/scripts/content.ts | 8 ++ .../src/lib/queries/extensionQueries.ts | 15 ++- .../src/lib/queries/index.ts | 5 +- .../src/lib/services/manage-keys.ts | 29 +++--- .../src/lib/stores/keys-store.ts | 26 ++--- .../src/lib/types/keys.ts | 5 - .../src/routes/change-password/+page.svelte | 95 ++++++++++--------- .../routes/setup-keys/download/+page.svelte | 15 ++- .../setup-keys/generate-keys/+page.svelte | 28 +++--- holo-key-manager-js-client/src/helpers.ts | 7 ++ holo-key-manager-js-client/src/index.ts | 22 +++++ tests/clientInteraction.ts | 2 - tests/helpers.ts | 2 +- tests/index.test.ts | 2 +- 15 files changed, 189 insertions(+), 111 deletions(-) diff --git a/holo-key-manager-extension/README.md b/holo-key-manager-extension/README.md index 0ccc181..b118aea 100644 --- a/holo-key-manager-extension/README.md +++ b/holo-key-manager-extension/README.md @@ -1,3 +1,38 @@ -# Holo Key Manager +# Holo Key Manager Extension -Holo Key Manager is a secure key manager for creating and managing Holochain keys. Get easy access to all your keys. You can set up and manager one or more keys for each application. +Holo Key Manager is a secure key manager for creating and managing Holochain keys. Get easy access to all your keys. You can set up and manage one or more keys for each application. + +## Overview + +The Holo Key Manager Extension is a browser extension designed to manage Holochain keys securely. It facilitates communication between web applications and the Holo Key Manager, ensuring that keys are handled safely and efficiently. + +### Features + +- Secure key management for Holochain applications. +- Cross-browser compatibility. +- Easy integration with web applications. +- Secure communication between content scripts and web pages. + +### Installation + +To install the Holo Key Manager Extension, follow the instructions for your browser: + +- Chrome: [Install from Chrome Web Store](https://chrome.google.com/webstore/detail/holo-key-manager/eggfhkdnfdhdpmkfpihjjbnncgmhihce) +- Edge: [Install from Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/detail/jfecdgefjljjfcflgbhgfkbeofjenceh) +- Mozilla: [Install from Firefox Browser Add-ons](https://addons.mozilla.org/en-US/firefox/addon/holo-key-manager/) + +### Usage + +Once installed, the extension will automatically handle key management tasks for your Holochain applications. You can interact with the extension through the provided API. + +### Development + +For development instructions, please refer to the [README.md](../README.md) file. + +### Contributing + +Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before submitting a pull request. + +### License + +This project is licensed under the ISC License. diff --git a/holo-key-manager-extension/scripts/content.ts b/holo-key-manager-extension/scripts/content.ts index edfbf89..09358e6 100644 --- a/holo-key-manager-extension/scripts/content.ts +++ b/holo-key-manager-extension/scripts/content.ts @@ -9,6 +9,14 @@ const parseAndHandleMessage = async (event: MessageEvent) => { try { const response = await sendMessage({ ...parsedResult.data, origin: event.origin }); const parsedMessageSchema = parseMessageSchema(response); + + // Using '*' as the target origin is necessary for cross-browser extension compatibility. + // This allows communication between the content script and the webpage across different browsers. + // Security note: We have a dedicated test in @preventSignatureFromOtherOrigin.ts that verifies + // the security of this communication. Additionally, we check the origin in the background script + // to ensure that only authenticated origins can sign messages. + // TODO: Consider alternatives to postMessage(..., '*') in the future, as discussed in: + // https://github.com/w3c/webextensions/issues/78 window.postMessage(responseToMessage(parsedMessageSchema.data, parsedResult.data.id), '*'); } catch (error) { window.postMessage( diff --git a/holo-key-manager-extension/src/lib/queries/extensionQueries.ts b/holo-key-manager-extension/src/lib/queries/extensionQueries.ts index 6867e5b..543691f 100644 --- a/holo-key-manager-extension/src/lib/queries/extensionQueries.ts +++ b/holo-key-manager-extension/src/lib/queries/extensionQueries.ts @@ -1,11 +1,11 @@ import { createMutation, createQuery, QueryClient } from '@tanstack/svelte-query'; import { handleSuccess } from '$helpers'; -import { unlockKey } from '$services'; +import { generateKeys, unlockKey } from '$services'; import { DEVICE_KEY, LOCAL, PASSWORD, SETUP_KEY } from '$shared/const'; import { isSetupComplete, storageService } from '$shared/services'; import { EncryptedDeviceKeySchema, HashSaltSchema } from '$shared/types'; -import { deviceKeyContentStore, passphraseStore } from '$stores'; +import { deviceKeyContentStore, keysStore, passphraseStore } from '$stores'; export function createSetupDeviceKeyQuery() { return createQuery({ @@ -37,6 +37,17 @@ export function createStoreDeviceKey(queryClient: QueryClient) { }); } +export function createGenerateKeys() { + return createMutation({ + mutationFn: async (mutationData: { passphrase: string; password: string }) => { + // Promise that resolves automatically to prevent the window from freezing before the mutation enters the loading state + await new Promise((resolve) => setTimeout(resolve, 100)); + const keys = await generateKeys(mutationData.passphrase, mutationData.password); + keysStore.set(keys); + } + }); +} + export function createRecoverDeviceKeyMutation() { return createMutation({ mutationFn: async (mutationData: { deviceKey: string; passphrase: string }) => { diff --git a/holo-key-manager-extension/src/lib/queries/index.ts b/holo-key-manager-extension/src/lib/queries/index.ts index 163f893..8b14b19 100644 --- a/holo-key-manager-extension/src/lib/queries/index.ts +++ b/holo-key-manager-extension/src/lib/queries/index.ts @@ -6,6 +6,7 @@ import { createSignInWithKeyMutation } from './applicationQueries'; import { + createGenerateKeys, createRecoverDeviceKeyMutation, createSetupDeviceKeyQuery, createStoreDeviceKey @@ -32,6 +33,7 @@ export function appQueries() { const applicationsListQuery = createApplicationsListQuery(); const storeDeviceKey = createStoreDeviceKey(queryClient); const recoverDeviceKeyMutation = createRecoverDeviceKeyMutation(); + const generateKeysMutation = createGenerateKeys(); const applicationKeyMutation = createApplicationKeyMutation(queryClient); const passwordAndStoreDeviceKeyMutation = createPasswordAndStoreDeviceKeyMutation(queryClient); @@ -47,6 +49,7 @@ export function appQueries() { applicationKeyMutation, recoverDeviceKeyMutation, passwordAndStoreDeviceKeyMutation, - signInWithKeyMutation + signInWithKeyMutation, + generateKeysMutation }; } diff --git a/holo-key-manager-extension/src/lib/services/manage-keys.ts b/holo-key-manager-extension/src/lib/services/manage-keys.ts index 3224941..3dd834f 100644 --- a/holo-key-manager-extension/src/lib/services/manage-keys.ts +++ b/holo-key-manager-extension/src/lib/services/manage-keys.ts @@ -9,8 +9,11 @@ import { import { base64ToUint8Array, uint8ArrayToBase64 } from '$shared/helpers'; import type { GeneratedKeys } from '$types'; +const createSeedCipherPwHash = (password: string) => + new SeedCipherPwHash(parseSecret(new TextEncoder().encode(password)), 'moderate'); + const lock = (root: UnlockedSeedBundle, password: string) => - root.lock([new SeedCipherPwHash(parseSecret(new TextEncoder().encode(password)), 'minimum')]); + root.lock([createSeedCipherPwHash(password)]); const deriveAndLock = ( master: UnlockedSeedBundle, @@ -18,27 +21,27 @@ const deriveAndLock = ( bundleType: string, passphrase: string ) => { - const root = master.derive(derivationPath, { - bundle_type: bundleType - }); - + const root = master.derive(derivationPath, { bundle_type: bundleType }); const encodedBytes = lock(root, passphrase); root.zero(); - return encodedBytes; }; +/** + * Generates keys using the provided passphrase and extension password. + * The 'minimum' setting for argon2id is used for the extensionPassword because it is already + * PBKDF2 hashed. This is acceptable since the additional argon2id hashing provides sufficient + * security for the derived keys. + */ export async function generateKeys( passphrase: string, extensionPassword: string ): Promise { await seedBundleReady; - const master = UnlockedSeedBundle.newRandom({ - bundle_type: 'master' - }); + const master = UnlockedSeedBundle.newRandom({ bundle_type: 'master' }); - const encodedMasterBytes: Uint8Array = lock(master, passphrase); + const encodedMasterBytes = lock(master, passphrase); const encodedRevocationBytes = deriveAndLock(master, 0, 'revocationRoot', passphrase); const encodedDeviceBytes = deriveAndLock(master, 1, 'deviceRoot', passphrase); const encodedDeviceBytesWithExtensionPassword = deriveAndLock( @@ -60,12 +63,8 @@ export async function generateKeys( export const lockKey = async (key: UnlockedSeedBundle, password: string) => { await seedBundleReady; - - const pw = new TextEncoder().encode(password); - const encodedBytes = key.lock([new SeedCipherPwHash(parseSecret(pw), 'moderate')]); - + const encodedBytes = lock(key, password); key.zero(); - return uint8ArrayToBase64(encodedBytes); }; diff --git a/holo-key-manager-extension/src/lib/stores/keys-store.ts b/holo-key-manager-extension/src/lib/stores/keys-store.ts index bd0e0fb..1ad0394 100644 --- a/holo-key-manager-extension/src/lib/stores/keys-store.ts +++ b/holo-key-manager-extension/src/lib/stores/keys-store.ts @@ -1,17 +1,13 @@ import { writable } from 'svelte/store'; -import { generateKeys } from '$services'; -import type { KeysState } from '$types'; +import type { GeneratedKeys } from '$types'; const initKeysStore = () => { - const initialState: KeysState = { - keys: { - encodedMaster: null, - encodedDeviceWithExtensionPassword: null, - encodedDevice: null, - encodedRevocation: null - }, - loading: false + const initialState: GeneratedKeys = { + encodedMaster: null, + encodedDeviceWithExtensionPassword: null, + encodedDevice: null, + encodedRevocation: null }; const { subscribe, set, update } = writable(initialState); @@ -20,15 +16,7 @@ const initKeysStore = () => { isInitialState: () => subscribe((state) => JSON.stringify(state) === JSON.stringify(initialState)), subscribe, - generate: async (passphrase: string, password: string) => { - update((state) => ({ ...state, loading: true })); - try { - const keys = await generateKeys(passphrase, password); - set({ keys, loading: false }); - } catch (error) { - set(initialState); - } - }, + set, resetAll: () => update(() => initialState) }; }; diff --git a/holo-key-manager-extension/src/lib/types/keys.ts b/holo-key-manager-extension/src/lib/types/keys.ts index abcafa7..7048d63 100644 --- a/holo-key-manager-extension/src/lib/types/keys.ts +++ b/holo-key-manager-extension/src/lib/types/keys.ts @@ -9,11 +9,6 @@ export type GeneratedKeys = { encodedRevocation: Uint8Array | null; }; -export type KeysState = { - keys: GeneratedKeys; - loading: boolean; -}; - const requiredString = (fieldName: string) => z.string().min(1, `${fieldName} is required`); const nonNegativeNumber = (fieldName: string) => z.number().nonnegative(`${fieldName} must be a non-negative number`); diff --git a/holo-key-manager-extension/src/routes/change-password/+page.svelte b/holo-key-manager-extension/src/routes/change-password/+page.svelte index 5e74c2e..f8f62c2 100644 --- a/holo-key-manager-extension/src/routes/change-password/+page.svelte +++ b/holo-key-manager-extension/src/routes/change-password/+page.svelte @@ -2,7 +2,7 @@ import { onMount } from 'svelte'; import { goto } from '$app/navigation'; - import { AppContainer, Button, Input, Title } from '$components'; + import { AppContainer, Button, Input, Loading, Title } from '$components'; import { dismissWindow } from '$helpers'; import { appQueries } from '$queries'; import { passwordStore } from '$stores'; @@ -21,49 +21,56 @@ - - Change Password - {#if $changePasswordWithDeviceKeyMutation.error} - {$changePasswordWithDeviceKeyMutation.error.message} - {/if} -
- - - -
+ {#if $changePasswordWithDeviceKeyMutation.isPending} +
+ +

Changing password...

+
+ {:else} + + Change Password + {#if $changePasswordWithDeviceKeyMutation.error} + {$changePasswordWithDeviceKeyMutation.error.message} + {/if} +
+ + + +
-
-
+ )} + /> + + {/if}
diff --git a/holo-key-manager-extension/src/routes/setup-keys/download/+page.svelte b/holo-key-manager-extension/src/routes/setup-keys/download/+page.svelte index ca7aceb..1da381d 100644 --- a/holo-key-manager-extension/src/routes/setup-keys/download/+page.svelte +++ b/holo-key-manager-extension/src/routes/setup-keys/download/+page.svelte @@ -10,9 +10,9 @@ onMount(() => { if ( - $keysStore.keys.encodedMaster === null || - $keysStore.keys.encodedRevocation === null || - $keysStore.keys.encodedDevice === null + $keysStore.encodedMaster === null || + $keysStore.encodedRevocation === null || + $keysStore.encodedDevice === null ) { goto('/setup-pass/start'); } @@ -20,20 +20,19 @@ async function download(): Promise { const { saveAs } = fileSaver; - const keys = $keysStore?.keys; - if (keys?.encodedMaster && keys?.encodedDevice && keys?.encodedRevocation) { + if ($keysStore.encodedMaster && $keysStore.encodedDevice && $keysStore.encodedRevocation) { const files = [ { name: 'master.txt', - data: uint8ArrayToBase64(keys.encodedMaster) + data: uint8ArrayToBase64($keysStore.encodedMaster) }, { name: 'device.txt', - data: uint8ArrayToBase64(keys.encodedDevice) + data: uint8ArrayToBase64($keysStore.encodedDevice) }, { name: 'revocation.txt', - data: uint8ArrayToBase64(keys.encodedRevocation) + data: uint8ArrayToBase64($keysStore.encodedRevocation) } ]; const zip = new JSZip(); diff --git a/holo-key-manager-extension/src/routes/setup-keys/generate-keys/+page.svelte b/holo-key-manager-extension/src/routes/setup-keys/generate-keys/+page.svelte index 667216d..417b01a 100644 --- a/holo-key-manager-extension/src/routes/setup-keys/generate-keys/+page.svelte +++ b/holo-key-manager-extension/src/routes/setup-keys/generate-keys/+page.svelte @@ -6,7 +6,7 @@ import { appQueries } from '$queries'; import { keysStore, passphraseStore, passwordStore } from '$stores'; - const { storeDeviceKey } = appQueries(); + const { storeDeviceKey, generateKeysMutation } = appQueries(); onMount(() => { if ($passphraseStore === '') { @@ -15,25 +15,31 @@ }); async function generate() { - await keysStore.generate($passphraseStore, $passwordStore); + try { + await $generateKeysMutation.mutateAsync({ + passphrase: $passphraseStore, + password: $passwordStore + }); - if ($keysStore.keys) { passphraseStore.clean(); passwordStore.reset(); - if ($keysStore.keys.encodedDeviceWithExtensionPassword) { - $storeDeviceKey.mutate($keysStore.keys.encodedDeviceWithExtensionPassword, { - onSuccess: () => { - goto('download'); - } - }); + const encodedDeviceKey = $keysStore?.encodedDeviceWithExtensionPassword; + if (encodedDeviceKey) { + await $storeDeviceKey.mutateAsync(encodedDeviceKey); + goto('download'); } + } catch (error) { + console.error(error); } } -{#if $keysStore.loading} - +{#if $generateKeysMutation.isPending || $storeDeviceKey.isPending} +
+ +

Generating keys...

+
{:else} Download Generate seed and key files diff --git a/holo-key-manager-js-client/src/helpers.ts b/holo-key-manager-js-client/src/helpers.ts index 9a63bf7..d109502 100644 --- a/holo-key-manager-js-client/src/helpers.ts +++ b/holo-key-manager-js-client/src/helpers.ts @@ -52,6 +52,13 @@ export const sendMessage = (message: Message): Promise => window.removeEventListener('message', responseHandler); }; + // Using '*' as the target origin is necessary for cross-browser extension compatibility. + // This allows communication between the content script and the webpage across different browsers. + // Security note: We have a dedicated test in @preventSignatureFromOtherOrigin.ts that verifies + // the security of this communication. Additionally, we check the origin in the background script + // to ensure that only authenticated origins can sign messages. + // TODO: Consider alternatives to postMessage(..., '*') in the future, as discussed in: + // https://github.com/w3c/webextensions/issues/78 window.postMessage(messageWithId, '*'); window.addEventListener('message', responseHandler); }); diff --git a/holo-key-manager-js-client/src/index.ts b/holo-key-manager-js-client/src/index.ts index 2bba0f4..b9e7c56 100644 --- a/holo-key-manager-js-client/src/index.ts +++ b/holo-key-manager-js-client/src/index.ts @@ -20,6 +20,11 @@ import { } from './helpers'; import type { HoloKeyManagerConfig, IHoloKeyManager } from './types'; +/** + * Creates a Holo Key Manager instance with the provided configuration. + * @param config - The configuration for the Holo Key Manager. + * @returns An object containing methods for key management. + */ const createHoloKeyManager = ({ happId, happName, @@ -28,6 +33,10 @@ const createHoloKeyManager = ({ requireRegistrationCode, requireEmail }: HoloKeyManagerConfig): IHoloKeyManager => { + /** + * Performs the sign-up action. + * @returns An object containing the public key, email, and registration code. + */ const performSignUpAction = async () => { checkContentScriptAndBrowser(); const response = await sendMessage({ @@ -55,6 +64,10 @@ const createHoloKeyManager = ({ }; }; + /** + * Performs the sign-in action. + * @returns An object containing the public key. + */ const performSignInAction = async () => { checkContentScriptAndBrowser(); const response = await sendMessage({ @@ -72,6 +85,11 @@ const createHoloKeyManager = ({ }; }; + /** + * Performs the sign message action. + * @param messageToSign - The message to be signed. + * @returns The signed message. + */ const performSignMessageAction = async (messageToSign: Uint8Array) => { checkContentScriptAndBrowser(); const encodedMessage = uint8ArrayToBase64(messageToSign); @@ -89,6 +107,9 @@ const createHoloKeyManager = ({ return base64ToUint8Array(signature); }; + /** + * Performs the sign-out action. + */ const performSignOutAction = async () => { checkContentScriptAndBrowser(); const response = await sendMessage({ @@ -109,4 +130,5 @@ const createHoloKeyManager = ({ signMessage: performSignMessageAction }; }; + export default createHoloKeyManager; diff --git a/tests/clientInteraction.ts b/tests/clientInteraction.ts index f8b4c6c..81e6399 100644 --- a/tests/clientInteraction.ts +++ b/tests/clientInteraction.ts @@ -83,8 +83,6 @@ export default async function clientInteractionTest(browser: Browser) { await signMessageBtn.click(); - await new Promise((resolve) => setTimeout(resolve, 2000)); - const signedMessage = await extractUint8ArrayFromResult( appPage, 'signMessageBtnResult', diff --git a/tests/helpers.ts b/tests/helpers.ts index f0ce401..31f4570 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -114,7 +114,7 @@ export const extractUint8ArrayFromResult = async ( id: string, key: string ): Promise => { - const resultLocator = page.locator(`#${id}`); + const resultLocator = page.locator(`#${id}:not(:empty)`); await resultLocator.wait(); const resultText = await resultLocator.map((element) => element.textContent).wait(); diff --git a/tests/index.test.ts b/tests/index.test.ts index 778163a..9eaeb8e 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -49,7 +49,7 @@ describe('End-to-End Tests for Extension and Client', () => { it('should allow the client to interact with the extension after setup', async () => { await clientInteractionTest(browser); - }); + }, 10000); it('should prevent the malicious page from signing messages', async () => { await preventSignatureFromOtherOrigin(browser); From 015764218b9bf17f72d0c25f6f6a083eb2e8eb76 Mon Sep 17 00:00:00 2001 From: Dawid Urbas Date: Fri, 19 Jul 2024 10:22:44 +0200 Subject: [PATCH 03/16] Fix unnecessary code --- README.md | 2 -- holo-key-manager-extension/src/lib/services/manage-keys.ts | 6 ------ 2 files changed, 8 deletions(-) diff --git a/README.md b/README.md index ff0fd83..662b3f5 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,6 @@ Holo Key Manager is a browser extension for generating and managing Holochain ap ## Usage -test - A javascript client library is available at `@holo-host/holo-key-manager-js-client`. See client documentation [here](holo-key-manager-js-client/README.md) ## Architecture diff --git a/holo-key-manager-extension/src/lib/services/manage-keys.ts b/holo-key-manager-extension/src/lib/services/manage-keys.ts index 3dd834f..eac0227 100644 --- a/holo-key-manager-extension/src/lib/services/manage-keys.ts +++ b/holo-key-manager-extension/src/lib/services/manage-keys.ts @@ -27,12 +27,6 @@ const deriveAndLock = ( return encodedBytes; }; -/** - * Generates keys using the provided passphrase and extension password. - * The 'minimum' setting for argon2id is used for the extensionPassword because it is already - * PBKDF2 hashed. This is acceptable since the additional argon2id hashing provides sufficient - * security for the derived keys. - */ export async function generateKeys( passphrase: string, extensionPassword: string From 41ce9e7e25242b30dd933fa6fac6ede347705b0a Mon Sep 17 00:00:00 2001 From: Dawid Urbas Date: Fri, 19 Jul 2024 10:29:23 +0200 Subject: [PATCH 04/16] Bump manifest --- holo-key-manager-extension/static/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holo-key-manager-extension/static/manifest.json b/holo-key-manager-extension/static/manifest.json index 559ab70..4c10149 100644 --- a/holo-key-manager-extension/static/manifest.json +++ b/holo-key-manager-extension/static/manifest.json @@ -1,7 +1,7 @@ { "name": "Holo key manager", "description": "A browser extension to manage holo keys", - "version": "0.0.75", + "version": "0.0.76", "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiAtKvbHNTN3O2BLRZH7RkLczaMLenSeZu+YP+KomPQPZ18nt4DY9boIN/+GWts7gCzEeQq59l8edGdF2P7xAbsRxYR88+zFEbxMtIyfyqJZIlzXwnvPJkwGu/S6arNtX48K7q1+xnJEE7VyeYSj6/i2LR+LmPigCzY9JCP7+SmWVeYbdm3kZmReK0ecfh15RXSNjZpXJUgrbea/RVxweggYKnmhhOUBmuJSCLoWTXIuJPBMwGQK1O2GKBqHOq94bPVSF7j+4WzSpPan70ZZJX/reFsOFE/idfFN6wbizjR1Ne50Po03kudEmfQgoqUhVpd0wP8A3YbqE7ODdZcCPPwIDAQAB", "manifest_version": 3, "action": { From 3eb5ed62b6fd8b734962199b4f0714f3e8368545 Mon Sep 17 00:00:00 2001 From: Dawid Urbas Date: Fri, 19 Jul 2024 10:34:35 +0200 Subject: [PATCH 05/16] Add loading state when sign up --- .../webapp-extension/create-key/+page.svelte | 106 +++++++++--------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/holo-key-manager-extension/src/routes/webapp-extension/create-key/+page.svelte b/holo-key-manager-extension/src/routes/webapp-extension/create-key/+page.svelte index b24613c..c602072 100644 --- a/holo-key-manager-extension/src/routes/webapp-extension/create-key/+page.svelte +++ b/holo-key-manager-extension/src/routes/webapp-extension/create-key/+page.svelte @@ -1,5 +1,5 @@ -
- Holo Logo -

Sign up

- -
+{#if $applicationKeyMutation.isPending} + +{:else} +
+ Holo Logo +

Sign up

+ +
-{#if $extractDetailsFromUrl.requireEmail} - -{/if} -{#if $extractDetailsFromUrl.requireRegistrationCode} - -{/if} - + {#if $extractDetailsFromUrl.requireEmail} + + {/if} + {#if $extractDetailsFromUrl.requireRegistrationCode} + + {/if} + -{#if $applicationKeyMutation.error} -
-

Error: {$applicationKeyMutation.error.message}

-
-{/if} + {#if $applicationKeyMutation.error} +
+

Error: {$applicationKeyMutation.error.message}

+
+ {/if} -