Skip to content

Commit

Permalink
Finish flow
Browse files Browse the repository at this point in the history
  • Loading branch information
mrruby committed Nov 27, 2023
1 parent 0a98744 commit e31809e
Show file tree
Hide file tree
Showing 20 changed files with 274 additions and 115 deletions.
2 changes: 1 addition & 1 deletion src/lib/components/Init.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { dismissWindow } from '$lib/helpers';
function redirectToSetup() {
window.open('setup/start.html', '_blank');
window.open('setup-pass/start.html', '_blank');
}
</script>

Expand Down
1 change: 1 addition & 0 deletions src/lib/const/query-keys.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const SESSION_DATA_KEY = 'sessionDataKey';
export const SETUP_KEY = 'setupKey';
export const SETUP_PASSWORD = 'setupPassword';
3 changes: 2 additions & 1 deletion src/lib/const/secure-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export const SESSION = 'session';
export const LOCAL = 'local';

export const SESSION_DATA = 'sessionData';
export const PASSWORD_WITH_DEVICE_KEY = 'passwordWithDeviceKey';
export const PASSWORD = 'password';
export const DEVICE_KEY = 'deviceKey';
133 changes: 111 additions & 22 deletions src/lib/queries/app-queries.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { createMutation, useQueryClient, createQuery } from '@tanstack/svelte-query';
import { storageService } from '$services';
import { PasswordAndSecureDataSchema, SessionStateSchema } from '$types';
import { Password, SecureDataSchema, SessionStateSchema } from '$types';
import {
LOCAL,
PASSWORD_WITH_DEVICE_KEY,
PASSWORD,
DEVICE_KEY,
SESSION,
SESSION_DATA,
SESSION_DATA_KEY,
SETUP_KEY
SETUP_KEY,
SETUP_PASSWORD
} from '$const';
import { encryptData, hashPassword } from '$helpers';
import { decryptData, encryptData, hashPassword } from '$helpers';

export function sessionStorageQueries() {
const queryClient = useQueryClient();
Expand All @@ -28,16 +30,13 @@ export function sessionStorageQueries() {
const signInMutation = createMutation({
mutationFn: async (password: string) => {
const result = await storageService.getWithoutCallback({
key: PASSWORD_WITH_DEVICE_KEY,
key: PASSWORD,
area: LOCAL
});

const validatedResult = PasswordAndSecureDataSchema.safeParse(result);
const validatedResult = Password.safeParse(result);

if (
validatedResult.success &&
(await hashPassword(password)) === validatedResult.data.password
) {
if (validatedResult.success && (await hashPassword(password)) === validatedResult.data) {
return storageService.set({
key: SESSION_DATA,
value: true,
Expand All @@ -51,35 +50,125 @@ export function sessionStorageQueries() {
queryClient.invalidateQueries({ queryKey: [SESSION_DATA_KEY] });
}
});
const setupQuery = createQuery({
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: PASSWORD_WITH_DEVICE_KEY,
key: DEVICE_KEY,
area: LOCAL
});

const validatedData = PasswordAndSecureDataSchema.safeParse(data);
const validatedData = SecureDataSchema.safeParse(data);
return validatedData.success;
}
});
const passwordWithDeviceKeyMutation = createMutation({
mutationFn: async (mutationData: { password: string; secretData: Uint8Array }) => {
const hash = await hashPassword(mutationData.password);
const createPassword = createMutation({
mutationFn: async (password: string) => {
const hash = await hashPassword(password);
storageService.set({
key: PASSWORD_WITH_DEVICE_KEY,
value: {
password: hash,
secureData: await encryptData(mutationData.secretData, hash)
},
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] });
}
});

return { setupQuery, passwordWithDeviceKeyMutation, sessionQuery, signInMutation };
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] });
}
});

return {
createPassword,
setupDeviceKeyQuery,
setupPasswordQuery,
changePasswordWithDeviceKeyMutation,
sessionQuery,
signInMutation,
storeDeviceKey
};
}
34 changes: 23 additions & 11 deletions src/lib/services/storage.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { isChromeStorageSafe } from '$helpers';
import type { AreaName, ChangesType, PasswordAndSecureData } from '$types';
import type { LOCAL, SESSION, SESSION_DATA, PASSWORD_WITH_DEVICE_KEY } from '$const';
import type { AreaName, ChangesType, SecureData } from '$types';
import type { LOCAL, SESSION, SESSION_DATA, PASSWORD, DEVICE_KEY } from '$const';

type StorageItem =
| { key: typeof SESSION_DATA; value: boolean; area: typeof SESSION }
| { key: typeof PASSWORD_WITH_DEVICE_KEY; value: PasswordAndSecureData; area: typeof LOCAL };
type SetSession = { key: typeof SESSION_DATA; value: boolean; area: typeof SESSION };
type GetSession = { key: typeof SESSION_DATA; area: typeof SESSION };

type GetStorageItem =
| { key: typeof SESSION_DATA; area: typeof SESSION }
| { key: typeof PASSWORD_WITH_DEVICE_KEY; area: typeof LOCAL };
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: StorageItem) => void;
get: (item: GetStorageItem, callback: (value: unknown) => void) => void;
getWithoutCallback: (item: GetStorageItem) => Promise<unknown>;
set: (item: StorageSetItem) => void;
get: (item: StorageGetItem, callback: (value: unknown) => void) => void;
getWithoutCallback: (item: StorageGetItem) => Promise<unknown>;
addListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => void;
removeListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => void;
};
Expand Down
9 changes: 0 additions & 9 deletions src/lib/stores/keys-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,6 @@ const initKeysStore = () => {
set(initialState);
}
},
resetExceptDevice: () =>
update((state) => ({
...initialState,
keys: {
...state.keys,
master: null,
revocation: null
}
})),
resetAll: () => update(() => initialState)
};
};
Expand Down
7 changes: 0 additions & 7 deletions src/lib/types/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,3 @@ export const SecureDataSchema = z.object({
});

export type SecureData = z.infer<typeof SecureDataSchema>;

export const PasswordAndSecureDataSchema = z.object({
password: Password,
secureData: SecureDataSchema
});

export type PasswordAndSecureData = z.infer<typeof PasswordAndSecureDataSchema>;
13 changes: 10 additions & 3 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@
import { dismissWindow } from '$helpers';
import { sessionStorageQueries } from '$queries';
const { sessionQuery, setupQuery } = sessionStorageQueries();
const { sessionQuery, setupDeviceKeyQuery } = sessionStorageQueries();
function redirectToChangePassword() {
window.open('change-password.html', '_blank');
}
</script>

{#if $sessionQuery.isFetching || $setupQuery.isFetching}
{#if $sessionQuery.isFetching || $setupDeviceKeyQuery.isFetching}
<span>Loading</span>
{:else if $sessionQuery.data}
<div class="m-8">
Expand All @@ -17,8 +21,11 @@
</button>
</div>
<h1 class="font-bold text-2xl mt-4">Holo Key Manager</h1>
<button on:click={redirectToChangePassword} class="text-blue-500 hover:text-blue-800"
>Change Password</button
>
</div>
{:else if $setupQuery.data}
{:else if $setupDeviceKeyQuery.data}
<Login />
{:else}
<Init />
Expand Down
62 changes: 62 additions & 0 deletions src/routes/change-password/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { Button, SetupContainer, Title } from '$components';
import { sessionStorageQueries } from '$queries';
import { dismissWindow } from '$helpers';
import InputPassword from '$components/InputPassword.svelte';
const { changePasswordWithDeviceKeyMutation } = sessionStorageQueries();
let oldPassword = '';
let confirmPassword = '';
let password = '';
$: charCount = password.length;
$: isDisabled = charCount < 8 || confirmPassword !== password || oldPassword === '';
</script>

<SetupContainer>
<button class="self-start ml-6 mb-4 flex items-center" on:click={dismissWindow}>
<img src="/img/arrow-left.svg" alt="Arrow" />
<span class="ml-2 text-base">Back</span></button
>
<Title>Manage Password</Title>
{#if $changePasswordWithDeviceKeyMutation.error}
<span class="text-xs text-alert">{$changePasswordWithDeviceKeyMutation.error.message}</span>
{/if}
<div class="p-6 w-full">
<InputPassword bind:value={oldPassword} label="Old Password" extraProps="mb-6" />
<InputPassword
bind:value={password}
label="New Password (8 Characters min)"
extraProps="mb-6"
error={charCount < 8 ? 'Please enter a minimum of 8 Characters' : ''}
/>
<InputPassword
bind:value={confirmPassword}
label="Confirm New Password"
extraProps="mb-4"
error={confirmPassword !== password ? "Password doesn't Match" : ''}
/>
</div>

<div class="grid grid-cols-2 gap-5 w-full p-6">
<Button label="Cancel" onClick={dismissWindow} color="secondary" />
<Button
disabled={isDisabled}
label="Update password"
onClick={() =>
$changePasswordWithDeviceKeyMutation.mutate(
{
newPassword: password,
oldPassword: oldPassword
},
{
onSuccess: () => {
goto('/setup-keys/done');
}
}
)}
/>
</div>
</SetupContainer>
19 changes: 0 additions & 19 deletions src/routes/done/+page.svelte

This file was deleted.

7 changes: 7 additions & 0 deletions src/routes/setup-keys/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script lang="ts">
import { SetupContainer } from '$components';
</script>

<SetupContainer>
<slot />
</SetupContainer>
Loading

0 comments on commit e31809e

Please sign in to comment.