diff --git a/src/lib/queries/app-queries.ts b/src/lib/queries/app-queries.ts index 10921c9..53d6bd2 100644 --- a/src/lib/queries/app-queries.ts +++ b/src/lib/queries/app-queries.ts @@ -1,173 +1,34 @@ -import { createMutation, useQueryClient, createQuery } from '@tanstack/svelte-query'; -import { storageService } from '$services'; -import { Password, SecureDataSchema, SessionStateSchema } from '$types'; +import { useQueryClient } from '@tanstack/svelte-query'; + +import { + createSessionQuery, + createSetupDeviceKeyQuery, + createStoreDeviceKey +} from './sessionAndKey'; import { - LOCAL, - PASSWORD, - DEVICE_KEY, - SESSION, - SESSION_DATA, - SESSION_DATA_KEY, - SETUP_KEY, - SETUP_PASSWORD -} from '$const'; -import { decryptData, encryptData, hashPassword } from '$helpers'; + createChangePasswordWithDeviceKeyMutation, + createPasswordMutation, + createSetupPasswordQuery, + createSignInMutation +} from './password'; export function sessionStorageQueries() { const queryClient = useQueryClient(); - const sessionQuery = createQuery({ - queryKey: [SESSION_DATA_KEY], - queryFn: async () => { - const data = await storageService.getWithoutCallback({ - key: SESSION_DATA, - area: SESSION - }); - - const validatedData = SessionStateSchema.safeParse(data); - return validatedData.success ? validatedData.data : false; - } - }); - const signInMutation = createMutation({ - mutationFn: async (password: string) => { - const result = await storageService.getWithoutCallback({ - key: PASSWORD, - area: LOCAL - }); - - const validatedResult = Password.safeParse(result); - - if (validatedResult.success && (await hashPassword(password)) === validatedResult.data) { - return storageService.set({ - key: SESSION_DATA, - value: true, - area: SESSION - }); - } - - throw new Error('Invalid password or data'); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [SESSION_DATA_KEY] }); - } - }); - const setupPasswordQuery = createQuery({ - queryKey: [SETUP_PASSWORD], - queryFn: async () => { - const data = await storageService.getWithoutCallback({ - key: PASSWORD, - area: LOCAL - }); - - const validatedData = Password.safeParse(data); - return validatedData.success; - } - }); - const setupDeviceKeyQuery = createQuery({ - queryKey: [SETUP_KEY], - queryFn: async () => { - const data = await storageService.getWithoutCallback({ - key: DEVICE_KEY, - area: LOCAL - }); - - const validatedData = SecureDataSchema.safeParse(data); - return validatedData.success; - } - }); - const createPassword = createMutation({ - mutationFn: async (password: string) => { - const hash = await hashPassword(password); - storageService.set({ - key: PASSWORD, - value: hash, - area: LOCAL - }); - }, - - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [SETUP_PASSWORD] }); - } - }); - const storeDeviceKey = createMutation({ - mutationFn: async (deviceKey: Uint8Array) => { - const result = await storageService.getWithoutCallback({ - key: PASSWORD, - area: LOCAL - }); - - const validatePassword = Password.safeParse(result); - - if (validatePassword.success) { - storageService.set({ - key: DEVICE_KEY, - value: await encryptData(deviceKey, validatePassword.data), - area: LOCAL - }); - } - }, - - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [SETUP_KEY] }); - } - }); - - const changePasswordWithDeviceKeyMutation = createMutation({ - mutationFn: async (mutationData: { newPassword: string; oldPassword: string }) => { - const oldHash = await hashPassword(mutationData.oldPassword); - - const result = await storageService.getWithoutCallback({ - key: PASSWORD, - area: LOCAL - }); - - const validatePassword = Password.safeParse(result); - - if (!validatePassword.success || oldHash !== validatePassword.data) { - throw new Error('Invalid password'); - } - - const deviceKey = await storageService.getWithoutCallback({ - key: DEVICE_KEY, - area: LOCAL - }); - - const validatedDeviceKey = SecureDataSchema.safeParse(deviceKey); - - if (!validatedDeviceKey.success) { - throw new Error('Invalid device key'); - } - - const decryptedData = await decryptData(validatedDeviceKey.data, oldHash); - const newHash = await hashPassword(mutationData.newPassword); - - storageService.set({ - key: PASSWORD, - value: newHash, - area: LOCAL - }); - storageService.set({ - key: DEVICE_KEY, - value: await encryptData(decryptedData, newHash), - area: LOCAL - }); - storageService.set({ - key: SESSION_DATA, - value: false, - area: SESSION - }); - }, - - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: [SESSION_DATA_KEY] }); - } - }); + const changePasswordWithDeviceKeyMutation = + createChangePasswordWithDeviceKeyMutation(queryClient); + const createPassword = createPasswordMutation(queryClient); + const sessionQuery = createSessionQuery(); + const setupDeviceKeyQuery = createSetupDeviceKeyQuery(); + const setupPasswordQuery = createSetupPasswordQuery(); + const signInMutation = createSignInMutation(queryClient); + const storeDeviceKey = createStoreDeviceKey(queryClient); return { + changePasswordWithDeviceKeyMutation, createPassword, + sessionQuery, setupDeviceKeyQuery, setupPasswordQuery, - changePasswordWithDeviceKeyMutation, - sessionQuery, signInMutation, storeDeviceKey }; diff --git a/src/lib/queries/password.ts b/src/lib/queries/password.ts new file mode 100644 index 0000000..4ad84d4 --- /dev/null +++ b/src/lib/queries/password.ts @@ -0,0 +1,113 @@ +import { + DEVICE_KEY, + LOCAL, + PASSWORD, + SESSION, + SESSION_DATA, + SESSION_DATA_KEY, + SETUP_PASSWORD +} from '$const'; +import { decryptData, encryptData, hashPassword } from '$helpers'; +import { storageService } from '$services'; +import { Password, SecureDataSchema } from '$types'; +import { createMutation, createQuery, type QueryClient } from '@tanstack/svelte-query'; + +const getPassword = async () => { + const data = await storageService.getWithoutCallback({ key: PASSWORD, area: LOCAL }); + return Password.safeParse(data); +}; + +export function createSetupPasswordQuery() { + return createQuery({ + queryKey: [SETUP_PASSWORD], + queryFn: async () => { + const validatedResult = await getPassword(); + return validatedResult.success; + } + }); +} + +export function createPasswordMutation(queryClient: QueryClient) { + return createMutation({ + mutationFn: async (password: string) => { + const hash = await hashPassword(password); + storageService.set({ + key: PASSWORD, + value: hash, + area: LOCAL + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [SETUP_PASSWORD] }); + } + }); +} + +export function createSignInMutation(queryClient: QueryClient) { + return createMutation({ + mutationFn: async (password: string) => { + const validatedResult = await getPassword(); + + if (validatedResult.success && (await hashPassword(password)) === validatedResult.data) { + return storageService.set({ + key: SESSION_DATA, + value: true, + area: SESSION + }); + } + + throw new Error('Invalid password or data'); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [SESSION_DATA_KEY] }); + } + }); +} + +export function createChangePasswordWithDeviceKeyMutation(queryClient: QueryClient) { + return createMutation({ + mutationFn: async (mutationData: { newPassword: string; oldPassword: string }) => { + const oldHash = await hashPassword(mutationData.oldPassword); + + const validatePassword = await getPassword(); + + if (!validatePassword.success || oldHash !== validatePassword.data) { + throw new Error('Invalid password'); + } + + const deviceKey = await storageService.getWithoutCallback({ + key: DEVICE_KEY, + area: LOCAL + }); + + const validatedDeviceKey = SecureDataSchema.safeParse(deviceKey); + + if (!validatedDeviceKey.success) { + throw new Error('Invalid device key'); + } + + const decryptedData = await decryptData(validatedDeviceKey.data, oldHash); + const newHash = await hashPassword(mutationData.newPassword); + + storageService.set({ + key: PASSWORD, + value: newHash, + area: LOCAL + }); + storageService.set({ + key: DEVICE_KEY, + value: await encryptData(decryptedData, newHash), + area: LOCAL + }); + storageService.set({ + key: SESSION_DATA, + value: false, + area: SESSION + }); + }, + + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [SESSION_DATA_KEY] }); + } + }); +} diff --git a/src/lib/queries/sessionAndKey.ts b/src/lib/queries/sessionAndKey.ts new file mode 100644 index 0000000..618a9ee --- /dev/null +++ b/src/lib/queries/sessionAndKey.ts @@ -0,0 +1,67 @@ +import { + DEVICE_KEY, + LOCAL, + PASSWORD, + SESSION, + SESSION_DATA, + SESSION_DATA_KEY, + SETUP_KEY +} from '$const'; +import { encryptData } from '$helpers'; +import { storageService } from '$services'; +import { Password, SecureDataSchema, SessionStateSchema } from '$types'; +import { QueryClient, createMutation, createQuery } from '@tanstack/svelte-query'; + +export function createSessionQuery() { + return createQuery({ + queryKey: [SESSION_DATA_KEY], + queryFn: async () => { + const data = await storageService.getWithoutCallback({ + key: SESSION_DATA, + area: SESSION + }); + const validatedData = SessionStateSchema.safeParse(data); + return validatedData.success ? validatedData.data : false; + } + }); +} + +export function createSetupDeviceKeyQuery() { + return createQuery({ + queryKey: [SETUP_KEY], + queryFn: async () => { + const data = await storageService.getWithoutCallback({ + key: DEVICE_KEY, + area: LOCAL + }); + const validatedData = SecureDataSchema.safeParse(data); + return validatedData.success; + } + }); +} + +export function createStoreDeviceKey(queryClient: QueryClient) { + return createMutation({ + mutationFn: async (deviceKey: Uint8Array) => { + const result = await storageService.getWithoutCallback({ + key: PASSWORD, + area: LOCAL + }); + const validatePassword = Password.safeParse(result); + + if (validatePassword.success) { + storageService.set({ + key: DEVICE_KEY, + value: await encryptData(deviceKey, validatePassword.data), + area: LOCAL + }); + } + + throw new Error('Something went wrong'); + }, + + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: [SETUP_KEY] }); + } + }); +} diff --git a/src/lib/services/storage.ts b/src/lib/services/storage.ts index 133634e..69b91f9 100644 --- a/src/lib/services/storage.ts +++ b/src/lib/services/storage.ts @@ -1,34 +1,5 @@ import { isChromeStorageSafe } from '$helpers'; -import type { AreaName, ChangesType, SecureData } from '$types'; -import type { LOCAL, SESSION, SESSION_DATA, PASSWORD, DEVICE_KEY } from '$const'; - -type SetSession = { key: typeof SESSION_DATA; value: boolean; area: typeof SESSION }; -type GetSession = { key: typeof SESSION_DATA; area: typeof SESSION }; - -type SetPassword = { - key: typeof PASSWORD; - value: string; - area: typeof LOCAL; -}; -type GetPassword = { key: typeof PASSWORD; area: typeof LOCAL }; - -type SetDeviceKey = { - key: typeof DEVICE_KEY; - value: SecureData; - area: typeof LOCAL; -}; -type GetDeviceKey = { key: typeof DEVICE_KEY; area: typeof LOCAL }; - -type StorageSetItem = SetSession | SetPassword | SetDeviceKey; -type StorageGetItem = GetSession | GetPassword | GetDeviceKey; - -type StorageService = { - set: (item: StorageSetItem) => void; - get: (item: StorageGetItem, callback: (value: unknown) => void) => void; - getWithoutCallback: (item: StorageGetItem) => Promise; - addListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => void; - removeListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => void; -}; +import type { AreaName, ChangesType, StorageService } from '$types'; export const storageService: StorageService = { set: ({ key, value, area }) => { diff --git a/src/lib/types/storage-service.ts b/src/lib/types/storage-service.ts index 8e09302..19a0943 100644 --- a/src/lib/types/storage-service.ts +++ b/src/lib/types/storage-service.ts @@ -1,5 +1,6 @@ -import type { LOCAL, SESSION } from '$const'; +import type { LOCAL, SESSION, SESSION_DATA, PASSWORD, DEVICE_KEY } from '$const'; import { z } from 'zod'; +import type { SecureData } from './keys'; export type AreaName = typeof SESSION | typeof LOCAL | 'sync' | 'managed'; @@ -10,3 +11,31 @@ export type SessionState = z.infer; export type ChangesType = { [key: string]: unknown; }; + +type SetSession = { key: typeof SESSION_DATA; value: boolean; area: typeof SESSION }; +type GetSession = { key: typeof SESSION_DATA; area: typeof SESSION }; + +type SetPassword = { + key: typeof PASSWORD; + value: string; + area: typeof LOCAL; +}; +type GetPassword = { key: typeof PASSWORD; area: typeof LOCAL }; + +type SetDeviceKey = { + key: typeof DEVICE_KEY; + value: SecureData; + area: typeof LOCAL; +}; +type GetDeviceKey = { key: typeof DEVICE_KEY; area: typeof LOCAL }; + +type StorageSetItem = SetSession | SetPassword | SetDeviceKey; +type StorageGetItem = GetSession | GetPassword | GetDeviceKey; + +export type StorageService = { + set: (item: StorageSetItem) => void; + get: (item: StorageGetItem, callback: (value: unknown) => void) => void; + getWithoutCallback: (item: StorageGetItem) => Promise; + addListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => void; + removeListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => void; +}; diff --git a/static/manifest.json b/static/manifest.json index 80c6e2e..b5ea814 100644 --- a/static/manifest.json +++ b/static/manifest.json @@ -1,7 +1,7 @@ { "name": "Holo key manager", "description": "A browser extension to manage holo keys", - "version": "0.0.22", + "version": "0.0.23", "manifest_version": 3, "browser_specific_settings": { "gecko": {