Skip to content

Commit

Permalink
Add login
Browse files Browse the repository at this point in the history
  • Loading branch information
mrruby committed Nov 17, 2023
1 parent 5af57dc commit 5123f8c
Show file tree
Hide file tree
Showing 25 changed files with 283 additions and 135 deletions.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "vite build --watch",
"build": "vite build && tsc --p scripts/tsconfig.json && node removeInlineScript.cjs",
"build": "vite build && node removeInlineScript.cjs",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
Expand Down Expand Up @@ -42,6 +42,7 @@
"jszip": "^3.10.1",
"svelte": "^4.0.5",
"tailwindcss": "^3.3.3",
"tiny-glob": "^0.2.9"
"tiny-glob": "^0.2.9",
"zod": "^3.22.4"
}
}
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 0 additions & 33 deletions scripts/background.ts

This file was deleted.

15 changes: 0 additions & 15 deletions scripts/tsconfig.json

This file was deleted.

4 changes: 2 additions & 2 deletions src/lib/components/EnterSecretComponent.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Button from '$components/Button.svelte';
import Tooltip from './Tooltip.svelte';
import Title from '$components/Title.svelte';
import { dismissExtensionWindow } from '$lib/helpers';
import { dismissWindow } from '$lib/helpers';
import clsx from 'clsx';
export let inputValue: string;
Expand Down Expand Up @@ -43,6 +43,6 @@
</div>

<div class="grid grid-cols-2 gap-5 w-full p-6">
<Button label="Cancel" onClick={dismissExtensionWindow} color="secondary" />
<Button label="Cancel" onClick={dismissWindow} color="secondary" />
<Button disabled={isDisabled} label={nextLabel} onClick={next} />
</div>
29 changes: 29 additions & 0 deletions src/lib/components/Init.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<script>
import { Button, AppParagraph } from '$components';
import { dismissWindow } from '$lib/helpers';
function redirectToSetup() {
window.open('setup/start.html', '_blank');
}
</script>

<div class="m-8">
<div class="flex justify-between items-center mb-4">
<img src="/img/holo_logo.svg" alt="Holo Key Manager Logo" />
<button on:click={dismissWindow} class="bg-transparent border-none">
<img src="/img/close.svg" alt="Close" />
</button>
</div>

<div class="flex flex-col justify-center items-center">
<img src="/img/setup.svg" alt="Setup" class="max-w-[48px]" />
<h1 class="font-bold text-2xl mt-4">Setup Required</h1>
<AppParagraph
extraProps="my-4 text-center max-w-xs"
text="You are yet to setup Holo Key Manager. Click “start setup” to begin."
/>
</div>

<Button label="Start setup" onClick={redirectToSetup} extraBottomMargin />
<Button label="Cancel" onClick={dismissWindow} color="secondary" />
</div>
39 changes: 39 additions & 0 deletions src/lib/components/Login.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<script>
import { Button, AppParagraph } from '$components';
import { passwordExistStore, sessionStore } from '$stores';
import { dismissWindow } from '$lib/helpers';
let password = '';
async function login() {
const isValid = await passwordExistStore.validate(password);
if (isValid) {
sessionStore.set({ session: true });
dismissWindow();
} else {
alert('Invalid password');
}
}
</script>

<div class="m-8">
<div class="flex justify-between items-center mb-4">
<img src="/img/holo_logo.svg" alt="Holo Key Manager Logo" />
<button on:click={dismissWindow} class="bg-transparent border-none">
<img src="/img/close.svg" alt="Close" />
</button>
</div>

<div class="flex flex-col justify-center items-center">
<img src="/img/logo.svg" alt="Login" class="max-w-[48px]" />
<h1 class="font-bold text-2xl mt-4">Login Required</h1>
<AppParagraph
extraProps="my-4 text-center max-w-xs"
text="Please enter your password to login."
/>
<input bind:value={password} type="password" class="my-4" placeholder="Enter password" />
</div>

<Button label="Login" onClick={login} extraBottomMargin />
<Button label="Cancel" onClick={dismissWindow} color="secondary" />
</div>
2 changes: 2 additions & 0 deletions src/lib/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { default as AppParagraph } from './AppParagraph.svelte';
export { default as Button } from './Button.svelte';
export { default as Title } from './Title.svelte';
export { default as EnterSecretComponent } from './EnterSecretComponent.svelte';
export { default as Init } from './Init.svelte';
export { default as Login } from './Login.svelte';
8 changes: 8 additions & 0 deletions src/lib/helpers/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { passwordExistStore, sessionStore } from '$stores';
import { derived, type Readable } from 'svelte/store';

export function createIsAuthenticated(): Readable<boolean> {
return derived([sessionStore, passwordExistStore], ([$sessionStore, $passwordExistStore]) =>
Boolean($sessionStore.session && $passwordExistStore)
);
}
2 changes: 2 additions & 0 deletions src/lib/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './navigation';
export * from './other';
export * from './password';
export * from './auth';
2 changes: 1 addition & 1 deletion src/lib/helpers/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function dismissExtensionWindow() {
export function dismissWindow() {
return window.close();
}
8 changes: 8 additions & 0 deletions src/lib/helpers/password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export async function hashPassword(password: string): Promise<string> {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, '0')).join('');
return hashHex;
}
1 change: 1 addition & 0 deletions src/lib/services/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './generate-keys';
export * from './storage';
48 changes: 48 additions & 0 deletions src/lib/services/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { isChromeStorageSafe } from '$helpers';
import type { AreaName, ChangesType, StorageKey } from '$types';

type StorageService = {
set(key: StorageKey, value: unknown, area: 'local' | 'session'): void;
get(key: StorageKey, callback: (value: unknown) => void, area: 'local' | 'session'): void;
getWithoutCallback: (key: StorageKey, area: 'local' | 'session') => Promise<unknown>;
addListener(listener: (changes: ChangesType, namespace: AreaName) => void): void;
removeListener(listener: (changes: ChangesType, namespace: AreaName) => void): void;
};

export const storageService: StorageService = {
set: (key: StorageKey, value: unknown, area: 'local' | 'session') => {
if (isChromeStorageSafe()) {
chrome.storage[area].set({ [key]: value });
}
},
get: (key: StorageKey, callback: (value: unknown) => void, area: 'local' | 'session') => {
if (isChromeStorageSafe()) {
chrome.storage[area].get([key], (result: ChangesType) => {
callback(result[key]);
});
} else {
callback(null);
}
},
getWithoutCallback: (key: StorageKey, area: 'local' | 'session') => {
if (isChromeStorageSafe()) {
return new Promise((resolve) => {
chrome.storage[area].get([key], (result: ChangesType) => {
resolve(result[key]);
});
});
} else {
return Promise.resolve(null);
}
},
addListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => {
if (isChromeStorageSafe()) {
chrome.storage.onChanged.addListener(listener);
}
},
removeListener: (listener: (changes: ChangesType, namespace: AreaName) => void) => {
if (isChromeStorageSafe()) {
chrome.storage.onChanged.removeListener(listener);
}
}
};
1 change: 1 addition & 0 deletions src/lib/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './passphrase';
export * from './password-exist';
export * from './password';
export * from './const';
export * from './keys-store';
Expand Down
41 changes: 41 additions & 0 deletions src/lib/stores/password-exist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { z } from 'zod';
import { writable } from 'svelte/store';
import { storageService } from '$services';
import type { AreaName, ChangesType } from '$types';
import { hashPassword } from '$helpers';

const createPasswordExistStore = () => {
const { subscribe, set, update } = writable<boolean | null>(null, () => {
const listener = (changes: ChangesType, namespace: AreaName) => {
if (namespace === 'local' && 'password' in changes) {
update(() => true);
}
};

storageService.get('password', (result) => set(!!result), 'local');

storageService.addListener(listener);

return () => {
storageService.removeListener(listener);
};
});

return {
subscribe,
set: (value: boolean) => {
set(value);
storageService.set('password', value, 'local');
},
validate: async (password: string) => {
const result = await storageService.getWithoutCallback('password', 'local');
const validatedResult = z.string().safeParse(result);

return validatedResult.success && (await hashPassword(password)) === validatedResult.data;
}
};
};

const passwordExistStore = createPasswordExistStore();

export { passwordExistStore };
42 changes: 23 additions & 19 deletions src/lib/stores/session.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,43 @@
import { isChromeStorageSafe } from '$helpers';
import { STORAGE_KEY, type ChangesType, type SessionState } from '$types';
import { writable } from 'svelte/store';
import { type SessionState, SessionStateSchema, type AreaName, type ChangesType } from '$types';
import { storageService } from '$services';

const createSessionStore = () => {
const { subscribe, set, update } = writable<SessionState>({ session: false }, () => {
const listener = (changes: ChangesType, namespace: string) => {
if (namespace === 'session' && changes[STORAGE_KEY]?.newValue) {
console.log('Session store updated from chrome.storage.session');
update(() => changes[STORAGE_KEY]?.newValue ?? { session: false });
const { subscribe, set, update } = writable<SessionState>({ session: null }, () => {
const listener = (changes: ChangesType, namespace: AreaName) => {
if (namespace === 'session') {
const newValue = SessionStateSchema.safeParse(changes['sessionData']);
update(() => (newValue.success ? newValue.data : { session: false }));
}
};

// When the store is first created, try to load existing state from chrome.storage.session
isChromeStorageSafe() &&
chrome.storage.session.get([STORAGE_KEY], (result) => {
result[STORAGE_KEY] && set(result[STORAGE_KEY]);
});
storageService.get(
'sessionData',
(result) => {
const validatedResult = SessionStateSchema.safeParse(result);
if (validatedResult.success) {
set(validatedResult.data);
} else {
set({ session: false });
}
},
'session'
);

isChromeStorageSafe() && chrome.storage.onChanged.addListener(listener);
storageService.addListener(listener);

// Return a function that can be called to unsubscribe from the store
return () => {
isChromeStorageSafe() && chrome.storage.onChanged.removeListener(listener);
storageService.removeListener(listener);
};
});

return {
subscribe,
set: (value: SessionState) => {
set(value);
isChromeStorageSafe() && chrome.storage.session.set({ [STORAGE_KEY]: value });
storageService.set('sessionData', value, 'session');
}
};
};

const sessionStore = createSessionStore();

export { sessionStore };
export const sessionStore = createSessionStore();
3 changes: 2 additions & 1 deletion src/lib/types/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './password';
export * from './keys';
export * from './storage-service';
12 changes: 0 additions & 12 deletions src/lib/types/password.ts → src/lib/types/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,3 @@ export type KeysState = {
keys: GeneratedKeys;
loading: boolean;
};

export type SessionState = {
session: boolean;
};

export const STORAGE_KEY = 'sessionData';

export type ChangesType = {
[STORAGE_KEY]?: {
newValue: SessionState;
};
};
Loading

0 comments on commit 5123f8c

Please sign in to comment.