diff --git a/README.md b/README.md new file mode 100644 index 0000000..23179e1 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +![Holo Key Manager Architecture Diagram](./diagram.png) + +The diagram above depicts the architecture of the Holo Key Manager. It demonstrates the client's interaction with the extension and outlines the communication schema between the webapp and the extension API. diff --git a/diagram.png b/diagram.png new file mode 100644 index 0000000..9659e06 Binary files /dev/null and b/diagram.png differ diff --git a/holo-key-manager-extension/build-scripts/replaceForFirefox.cjs b/holo-key-manager-extension/build-scripts/replaceForFirefox.cjs index 713f8bc..981456d 100644 --- a/holo-key-manager-extension/build-scripts/replaceForFirefox.cjs +++ b/holo-key-manager-extension/build-scripts/replaceForFirefox.cjs @@ -26,7 +26,6 @@ fs.readFile(manifestPath, 'utf8', (err, data) => { scripts: ['scripts/background.js'], type: 'module' }, - optional_permissions: ['*://localhost/*'], browser_specific_settings: { ...manifest.browser_specific_settings, gecko: { diff --git a/holo-key-manager-extension/scripts/background.ts b/holo-key-manager-extension/scripts/background.ts index d8f283e..5e86381 100644 --- a/holo-key-manager-extension/scripts/background.ts +++ b/holo-key-manager-extension/scripts/background.ts @@ -1,4 +1,5 @@ -import { SENDER_EXTENSION } from '@sharedConst'; +import { GENERIC_ERROR, NEEDS_SETUP, SENDER_EXTENSION, SIGN_UP, SUCCESS } from '@sharedConst'; +import { isSetupComplete } from '@sharedServices'; import { type ActionPayload, type Message, MessageWithIdSchema } from '@sharedTypes'; let windowId: number | undefined; @@ -7,9 +8,8 @@ type SendResponse = (response?: Message) => void; type SendResponseWithSender = (response: ActionPayload) => void; const handleError = (error: string, sendResponse: SendResponseWithSender) => { - console.error(error); windowId = undefined; - sendResponse({ action: 'GenericError' }); + sendResponse({ action: GENERIC_ERROR }); }; const createAndFocusWindow = async (sendResponse: SendResponseWithSender) => { @@ -18,7 +18,7 @@ const createAndFocusWindow = async (sendResponse: SendResponseWithSender) => { if (chrome.runtime.lastError) { handleError('Error focusing window: ' + chrome.runtime.lastError.message, sendResponse); } else { - sendResponse({ action: 'Success' }); + sendResponse({ action: SUCCESS }); } }); return true; @@ -46,40 +46,36 @@ const createWindow = () => { ); }; -const handleSignIn = async (sendResponse: SendResponseWithSender) => { +const handleAction = async ( + actionType: typeof SUCCESS | typeof NEEDS_SETUP, + sendResponse: SendResponseWithSender +) => { if (await createAndFocusWindow(sendResponse)) return; createWindow(); - sendResponse({ action: 'Success' }); + sendResponse({ action: actionType }); }; chrome.runtime.onMessage.addListener((message: Message, sender, sendResponse: SendResponse) => { - const sendResponseWithSender = (response: ActionPayload) => - sendResponse({ ...response, sender: SENDER_EXTENSION }); + (async () => { + const sendResponseWithSender = (response: ActionPayload) => + sendResponse({ ...response, sender: SENDER_EXTENSION }); - const parsedMessage = MessageWithIdSchema.safeParse(message); - if (!parsedMessage.success || parsedMessage.data.action !== 'SignIn') return; - try { - handleSignIn(sendResponseWithSender); - } catch (error) { - handleError( - 'Error processing sign in: ' + (error instanceof Error ? error.message : String(error)), - sendResponseWithSender - ); - } - return true; -}); + const parsedMessage = MessageWithIdSchema.safeParse(message); + if (!parsedMessage.success) return; + if (parsedMessage.data.action !== SIGN_UP) return; -chrome.runtime.onInstalled.addListener(function () { - chrome.permissions.request( - { - origins: ['*://localhost/*'] - }, - function (granted) { - if (granted) { - console.log('Permission to access localhost granted'); - } else { - console.log('Permission to access localhost denied'); - } + const setupComplete = await isSetupComplete(); + const actionType = setupComplete ? SUCCESS : NEEDS_SETUP; + + try { + handleAction(actionType, sendResponseWithSender); + } catch (error) { + handleError( + `Error processing sign in: ${error instanceof Error ? error.message : String(error)}`, + sendResponseWithSender + ); } - ); + })(); + + return true; }); diff --git a/holo-key-manager-extension/scripts/content.ts b/holo-key-manager-extension/scripts/content.ts index 6a79b7e..1ff9f42 100644 --- a/holo-key-manager-extension/scripts/content.ts +++ b/holo-key-manager-extension/scripts/content.ts @@ -1,20 +1,26 @@ +import { HOLO_KEY_MANAGER_EXTENSION_MARKER_ID, SENDER_EXTENSION } from '@sharedConst'; import { responseToMessage, sendMessage } from '@sharedServices'; import { MessageSchema, MessageWithIdSchema } from '@sharedTypes'; -const isMessageFromSelf = (event: MessageEvent) => event.source === window; const parseAndHandleMessage = async (event: MessageEvent) => { - if (!isMessageFromSelf(event)) return; const parsedResult = MessageWithIdSchema.safeParse(event.data); - if (!parsedResult.success || parsedResult.data.action !== 'SignIn') return; - + if (!parsedResult.success || parsedResult.data.sender === SENDER_EXTENSION) return; try { const response = await sendMessage(parsedResult.data); const parsedMessageSchema = MessageSchema.safeParse(response); if (!parsedMessageSchema.success) throw new Error('Invalid response format'); window.postMessage(responseToMessage(parsedMessageSchema.data, parsedResult.data.id), '*'); } catch (error) { - console.error(error); + window.postMessage( + responseToMessage({ action: 'GenericError', sender: SENDER_EXTENSION }, parsedResult.data.id), + '*' + ); } }; window.addEventListener('message', parseAndHandleMessage); + +const markerDiv = document.createElement('div'); +markerDiv.id = HOLO_KEY_MANAGER_EXTENSION_MARKER_ID; +markerDiv.style.display = 'none'; +document.body.appendChild(markerDiv); diff --git a/holo-key-manager-extension/src/lib/helpers/index.ts b/holo-key-manager-extension/src/lib/helpers/index.ts index c1184c4..45eaad0 100644 --- a/holo-key-manager-extension/src/lib/helpers/index.ts +++ b/holo-key-manager-extension/src/lib/helpers/index.ts @@ -1,4 +1,3 @@ export * from './encryption'; export * from './navigation'; export * from './queries'; -export * from './storage'; diff --git a/holo-key-manager-extension/src/lib/helpers/queries.ts b/holo-key-manager-extension/src/lib/helpers/queries.ts index f8331c8..24ce52d 100644 --- a/holo-key-manager-extension/src/lib/helpers/queries.ts +++ b/holo-key-manager-extension/src/lib/helpers/queries.ts @@ -1,4 +1,16 @@ import type { QueryClient } from '@tanstack/svelte-query'; +import { LOCAL, PASSWORD } from '$sharedConst'; +import { storageService } from '$sharedServices'; +import { HashSaltSchema } from '$sharedTypes'; + export const handleSuccess = (queryClient: QueryClient, queryKey: string[]) => () => queryClient.invalidateQueries({ queryKey }); + +export const getPassword = async () => { + const data = await storageService.getWithoutCallback({ + key: PASSWORD, + area: LOCAL + }); + return HashSaltSchema.safeParse(data); +}; diff --git a/holo-key-manager-extension/src/lib/helpers/storage.ts b/holo-key-manager-extension/src/lib/helpers/storage.ts deleted file mode 100644 index 5067c4c..0000000 --- a/holo-key-manager-extension/src/lib/helpers/storage.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { LOCAL, PASSWORD } from '$sharedConst'; -import { storageService } from '$sharedServices'; -import { HashSaltSchema } from '$sharedTypes'; - -export const getPassword = async () => { - const data = await storageService.getWithoutCallback({ - key: PASSWORD, - area: LOCAL - }); - return HashSaltSchema.safeParse(data); -}; diff --git a/holo-key-manager-extension/src/lib/queries/password.ts b/holo-key-manager-extension/src/lib/queries/password.ts index 7a44d6d..09b3e84 100644 --- a/holo-key-manager-extension/src/lib/queries/password.ts +++ b/holo-key-manager-extension/src/lib/queries/password.ts @@ -14,8 +14,8 @@ import { SETUP_PASSWORD } from '$sharedConst'; import { storageService } from '$sharedServices'; +import { EncryptedDeviceKeySchema } from '$sharedTypes'; import { deviceKeyContentStore, passphraseStore, passwordStore } from '$stores'; -import { EncryptedDeviceKeySchema } from '$types'; const storePassword = async (password: string) => { const hashSalt = await hashPassword(password); diff --git a/holo-key-manager-extension/src/lib/queries/sessionAndKey.ts b/holo-key-manager-extension/src/lib/queries/sessionAndKey.ts index 7bb8d2b..03230bd 100644 --- a/holo-key-manager-extension/src/lib/queries/sessionAndKey.ts +++ b/holo-key-manager-extension/src/lib/queries/sessionAndKey.ts @@ -11,10 +11,9 @@ import { SESSION_DATA_KEY, SETUP_KEY } from '$sharedConst'; -import { storageService } from '$sharedServices'; -import { HashSaltSchema, SessionStateSchema } from '$sharedTypes'; +import { isSetupComplete, storageService } from '$sharedServices'; +import { EncryptedDeviceKeySchema, HashSaltSchema, SessionStateSchema } from '$sharedTypes'; import { deviceKeyContentStore, passphraseStore } from '$stores'; -import { EncryptedDeviceKeySchema } from '$types'; export function createSessionQuery() { return createQuery({ @@ -33,15 +32,7 @@ export function createSessionQuery() { export function createSetupDeviceKeyQuery() { return createQuery({ queryKey: [SETUP_KEY], - queryFn: async () => { - const data = await storageService.getWithoutCallback({ - key: DEVICE_KEY, - area: LOCAL - }); - - const parsedData = EncryptedDeviceKeySchema.safeParse(data); - return parsedData.success; - } + queryFn: isSetupComplete }); } diff --git a/holo-key-manager-extension/src/lib/types/keys.ts b/holo-key-manager-extension/src/lib/types/keys.ts index ed85ff2..1c815ad 100644 --- a/holo-key-manager-extension/src/lib/types/keys.ts +++ b/holo-key-manager-extension/src/lib/types/keys.ts @@ -1,5 +1,3 @@ -import { z } from 'zod'; - export type SetSecret = 'set' | 'confirm'; export type GeneratedKeys = { @@ -13,7 +11,3 @@ export type KeysState = { keys: GeneratedKeys; loading: boolean; }; - -export const EncryptedDeviceKeySchema = z.string(); - -export type EncryptedDeviceKey = z.infer; diff --git a/holo-key-manager-extension/src/routes/+page.svelte b/holo-key-manager-extension/src/routes/+page.svelte index 1355d96..377f754 100644 --- a/holo-key-manager-extension/src/routes/+page.svelte +++ b/holo-key-manager-extension/src/routes/+page.svelte @@ -21,9 +21,9 @@

Holo Key Manager

- + {:else if $setupDeviceKeyQuery.data} diff --git a/holo-key-manager-extension/src/routes/setup-pass/import-key/+page.svelte b/holo-key-manager-extension/src/routes/setup-pass/import-key/+page.svelte index 584ab51..8740ddc 100644 --- a/holo-key-manager-extension/src/routes/setup-pass/import-key/+page.svelte +++ b/holo-key-manager-extension/src/routes/setup-pass/import-key/+page.svelte @@ -6,7 +6,7 @@ import { AppParagraph, Button, Title } from '$components'; import { dismissWindow } from '$helpers'; import { sessionStorageQueries } from '$queries'; - import { EncryptedDeviceKeySchema } from '$types'; + import { EncryptedDeviceKeySchema } from '$sharedTypes'; const { recoverDeviceKeyMutation } = sessionStorageQueries(); diff --git a/holo-key-manager-extension/static/manifest.json b/holo-key-manager-extension/static/manifest.json index 989d78c..b554978 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.37", + "version": "0.0.38", "manifest_version": 3, "action": { "default_title": "Holo key manager", diff --git a/holo-key-manager-js-client/package.json b/holo-key-manager-js-client/package.json index 1960ec3..1ea2b2c 100644 --- a/holo-key-manager-js-client/package.json +++ b/holo-key-manager-js-client/package.json @@ -6,7 +6,7 @@ "types": "lib/holo-key-manager-js-client/src/index.d.ts", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "buildPack": "rollup -c && npm pack" + "buildPack": "rm -rf lib/* && rollup -c && npm pack" }, "keywords": [ "holo", diff --git a/holo-key-manager-js-client/src/helpers.ts b/holo-key-manager-js-client/src/helpers.ts index 93ee9cb..8d5bc48 100644 --- a/holo-key-manager-js-client/src/helpers.ts +++ b/holo-key-manager-js-client/src/helpers.ts @@ -1,4 +1,4 @@ -import { SENDER_WEBAPP } from '@sharedConst'; +import { HOLO_KEY_MANAGER_EXTENSION_MARKER_ID, SENDER_WEBAPP } from '@sharedConst'; import { createMessageWithId } from '@sharedServices'; import { type Message, type MessageWithId, MessageWithIdSchema } from '@sharedTypes'; @@ -17,13 +17,10 @@ export const sendMessage = (message: Message): Promise => const responseHandler = (event: MessageEvent) => { const parseResult = MessageWithIdSchema.safeParse(event.data); if (!parseResult.success) { - console.error(event.data); - console.error('Invalid message format:', parseResult.error); return; } const responseData = parseResult.data; if (responseData.id !== messageWithId.id || responseData.sender === SENDER_WEBAPP) { - console.error('Invalid message id or sender:', responseData.id, responseData.sender); return; } resolve(responseData); @@ -38,3 +35,14 @@ export const sendMessage = (message: Message): Promise => reject(new Error('Response timeout')); }, 30000); }); + +const isFirefox = () => navigator.userAgent.indexOf('Firefox') !== -1; + +export const checkContentScriptAndBrowser = () => { + if (!document.getElementById(HOLO_KEY_MANAGER_EXTENSION_MARKER_ID)) { + const errorMessage = + 'Holo Key Manager extension is not installed' + + (isFirefox() ? ' or permissions are not granted' : ''); + throw new Error(errorMessage); + } +}; diff --git a/holo-key-manager-js-client/src/index.ts b/holo-key-manager-js-client/src/index.ts index 7170d3c..a66be98 100644 --- a/holo-key-manager-js-client/src/index.ts +++ b/holo-key-manager-js-client/src/index.ts @@ -1,7 +1,7 @@ -import { SENDER_WEBAPP } from '@sharedConst'; +import { SENDER_WEBAPP, SIGN_IN, SIGN_UP } from '@sharedConst'; import type { Message } from '@sharedTypes'; -import { sendMessage } from './helpers'; +import { checkContentScriptAndBrowser, sendMessage } from './helpers'; import type { IHoloKeyManager } from './types'; const createHoloKeyManager = ({ @@ -13,38 +13,30 @@ const createHoloKeyManager = ({ }: { happId: string; happName: string; - happLogo: URL; - happUiUrl: URL; + happLogo: string; + happUiUrl: string; requireRegistrationCode: boolean; }): IHoloKeyManager => { const signUp = async () => { - try { - const message: Message = { - action: 'SignUp', - payload: { - happId, - happName, - happLogo, - happUiUrl, - requireRegistrationCode - }, - sender: SENDER_WEBAPP - }; - return sendMessage(message); - } catch (error) { - console.error('Failed to signUp:', error); - throw error; - } + checkContentScriptAndBrowser(); + const message: Message = { + action: SIGN_UP, + payload: { + happId, + happName, + happLogo, + happUiUrl, + requireRegistrationCode + }, + sender: SENDER_WEBAPP + }; + return sendMessage(message); }; const signIn = async () => { - try { - const message: Message = { action: 'SignIn', payload: { happId }, sender: SENDER_WEBAPP }; - return sendMessage(message); - } catch (error) { - console.error('Failed to signIn:', error); - throw error; - } + checkContentScriptAndBrowser(); + const message: Message = { action: SIGN_IN, payload: { happId }, sender: SENDER_WEBAPP }; + return sendMessage(message); }; return { signUp, signIn }; diff --git a/package.json b/package.json index 2f3bc96..d1cc218 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "buildExtension": "cd holo-key-manager-extension && pnpm build && cd ..", "buildExtensionDev": "cd holo-key-manager-extension && pnpm buildDev && cd ..", "buildClient": "cd holo-key-manager-js-client && pnpm buildPack && cd ..", + "buildDev": "concurrently \"pnpm buildExtensionDev\" \"pnpm buildClient\"", "lint": "prettier --check '**/*.{js,ts,svelte,json}' && eslint --fix '**/*.{js,ts,svelte}'", "format": "prettier --write '**/*.{js,ts,svelte,json}'", "check": "cd holo-key-manager-extension && pnpm check", @@ -20,6 +21,7 @@ "@types/chrome": "^0.0.263", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", + "concurrently": "^8.2.2", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-simple-import-sort": "^12.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39ef6f4..0a24126 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,6 +24,9 @@ importers: '@typescript-eslint/parser': specifier: ^7.2.0 version: 7.2.0(eslint@8.57.0)(typescript@5.4.2) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 eslint: specifier: ^8.57.0 version: 8.57.0 @@ -163,6 +166,13 @@ packages: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + /@babel/runtime@7.24.0: + resolution: {integrity: sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.1 + dev: true + /@esbuild/aix-ppc64@0.19.12: resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} engines: {node: '>=12'} @@ -1170,6 +1180,15 @@ packages: string-width: 7.1.0 dev: true + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: true + /clsx@2.1.0: resolution: {integrity: sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==} engines: {node: '>=6'} @@ -1231,6 +1250,22 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.1 + shell-quote: 1.8.1 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + dev: true + /cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -1273,6 +1308,13 @@ packages: engines: {node: '>=4'} hasBin: true + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.24.0 + dev: true + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -1712,6 +1754,11 @@ packages: /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + dev: true + /get-east-asian-width@1.2.0: resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} engines: {node: '>=18'} @@ -2673,6 +2720,15 @@ packages: dependencies: picomatch: 2.3.1 + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: true + + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + dev: true + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2768,6 +2824,12 @@ packages: dependencies: queue-microtask: 1.2.3 + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + dependencies: + tslib: 2.6.2 + dev: true + /sade@1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -2822,6 +2884,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + /shell-quote@1.8.1: + resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + dev: true + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true @@ -2874,6 +2940,10 @@ packages: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} + /spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + dev: true + /streamx@2.16.1: resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} dependencies: @@ -2974,6 +3044,13 @@ packages: has-flag: 4.0.0 dev: true + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + dependencies: + has-flag: 4.0.0 + dev: true + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -3183,6 +3260,11 @@ packages: engines: {node: '>=6'} dev: true + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true + /ts-api-utils@1.3.0(typescript@5.4.2): resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -3349,6 +3431,11 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + dev: true + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true @@ -3369,6 +3456,24 @@ packages: hasBin: true dev: false + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + dev: true + + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.2 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: true + /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} diff --git a/shared/const/app.ts b/shared/const/app.ts index 3937fcd..7c558ab 100644 --- a/shared/const/app.ts +++ b/shared/const/app.ts @@ -1,5 +1,5 @@ -import { z } from 'zod'; +export const HOLO_KEY_MANAGER_APP_ID = 'holo-key-manager'; +export const SENDER_WEBAPP = 'webapp'; +export const SENDER_EXTENSION = 'extension'; -export const HOLO_KEY_MANAGER_APP_ID = z.literal('holo-key-manager').value; -export const SENDER_WEBAPP = z.literal('webapp').value; -export const SENDER_EXTENSION = z.literal('extension').value; +export const HOLO_KEY_MANAGER_EXTENSION_MARKER_ID = 'user-holo-key-manager-extension-marker'; diff --git a/shared/const/index.ts b/shared/const/index.ts index 104c765..c9961ce 100644 --- a/shared/const/index.ts +++ b/shared/const/index.ts @@ -1,3 +1,4 @@ export * from './app'; +export * from './messages'; export * from './query-keys'; export * from './secure-store'; diff --git a/shared/const/messages.ts b/shared/const/messages.ts new file mode 100644 index 0000000..0a23615 --- /dev/null +++ b/shared/const/messages.ts @@ -0,0 +1,7 @@ +export const SIGN_UP = 'SignUp'; +export const SIGN_IN = 'SignIn'; +export const NO_KEY_FOR_HAPP = 'NoKeyForHapp'; +export const GENERIC_ERROR = 'GenericError'; +export const SUCCESS = 'Success'; +export const SUCCESS_WITH_PAYLOAD = 'SuccessWithPayload'; +export const NEEDS_SETUP = 'NeedsSetup'; diff --git a/shared/services/storage.ts b/shared/services/storage.ts index 67cc63f..c5cc440 100644 --- a/shared/services/storage.ts +++ b/shared/services/storage.ts @@ -1,5 +1,11 @@ +import { DEVICE_KEY, LOCAL } from '../const'; import { isChromeStorageSafe } from '../helpers'; -import type { AreaName, ChangesType, StorageService } from '../types'; +import { + type AreaName, + type ChangesType, + EncryptedDeviceKeySchema, + type StorageService +} from '../types'; export const storageService: StorageService = { set: ({ key, value, area }) => { @@ -38,3 +44,13 @@ export const storageService: StorageService = { } } }; + +export const isSetupComplete = async () => { + const data = await storageService.getWithoutCallback({ + key: DEVICE_KEY, + area: LOCAL + }); + + const parsedData = EncryptedDeviceKeySchema.safeParse(data); + return parsedData.success; +}; diff --git a/shared/types/message.ts b/shared/types/message.ts index 115bdd0..9d90108 100644 --- a/shared/types/message.ts +++ b/shared/types/message.ts @@ -1,5 +1,14 @@ import { z } from 'zod'; +import { + GENERIC_ERROR, + NEEDS_SETUP, + NO_KEY_FOR_HAPP, + SIGN_IN, + SIGN_UP, + SUCCESS, + SUCCESS_WITH_PAYLOAD +} from '../const'; import { HOLO_KEY_MANAGER_APP_ID, SENDER_EXTENSION, SENDER_WEBAPP } from '../const'; const BasePayloadSchema = z.object({ @@ -8,8 +17,8 @@ const BasePayloadSchema = z.object({ const SignUpPayloadSchema = BasePayloadSchema.extend({ happName: z.string(), - happLogo: z.instanceof(URL), - happUiUrl: z.instanceof(URL), + happLogo: z.string(), + happUiUrl: z.string(), requireRegistrationCode: z.boolean() }); @@ -18,12 +27,13 @@ const MessageBaseSchema = z.object({ }); const ActionPayloadSchema = z.union([ - z.object({ action: z.literal('SignUp'), payload: SignUpPayloadSchema }), - z.object({ action: z.literal('SignIn'), payload: BasePayloadSchema }), - z.object({ action: z.literal('NoKeyForHapp') }), - z.object({ action: z.literal('GenericError') }), - z.object({ action: z.literal('Success') }), - z.object({ action: z.literal('SuccessWithPayload'), payload: z.string() }) + z.object({ action: z.literal(SIGN_UP), payload: SignUpPayloadSchema }), + z.object({ action: z.literal(SIGN_IN), payload: BasePayloadSchema }), + z.object({ action: z.literal(NO_KEY_FOR_HAPP) }), + z.object({ action: z.literal(GENERIC_ERROR) }), + z.object({ action: z.literal(NEEDS_SETUP) }), + z.object({ action: z.literal(SUCCESS) }), + z.object({ action: z.literal(SUCCESS_WITH_PAYLOAD), payload: z.string() }) ]); export type ActionPayload = z.infer; diff --git a/shared/types/storage-service.ts b/shared/types/storage-service.ts index b5c2193..36fb7cf 100644 --- a/shared/types/storage-service.ts +++ b/shared/types/storage-service.ts @@ -2,6 +2,10 @@ import { z } from 'zod'; import type { DEVICE_KEY, LOCAL, PASSWORD, SESSION, SESSION_DATA } from '../const'; +export const EncryptedDeviceKeySchema = z.string(); + +export type EncryptedDeviceKey = z.infer; + export const HashSaltSchema = z.object({ salt: z.string(), hash: z.string()