Skip to content

Commit

Permalink
Use tanstack query
Browse files Browse the repository at this point in the history
  • Loading branch information
mrruby committed Nov 19, 2023
1 parent 8831f72 commit c4e0a4d
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 243 deletions.
15 changes: 6 additions & 9 deletions src/lib/components/Login.svelte
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
<script>
import { Button, AppParagraph } from '$components';
import { passwordExistStore, sessionStore } from '$stores';
import { dismissWindow } from '$lib/helpers';
import { sessionStorageQueries } from '$queries';
let password = '';
async function login() {
const isValid = await passwordExistStore.validate(password);
const { signInMutation } = sessionStorageQueries();
if (isValid) {
sessionStore.set(true);
location.reload();
} else {
alert('Invalid password');
}
async function login() {
$signInMutation.mutate(password, {
onError: () => alert('Invalid password')
});
}
</script>

Expand Down
3 changes: 2 additions & 1 deletion src/lib/const/query-keys.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const GENERATED_KEYS = 'generatedKeys';
export const SESSION_DATA_KEY = 'sessionDataKey';
export const SETUP_KEY = 'setupKey';
8 changes: 0 additions & 8 deletions src/lib/helpers/auth.ts

This file was deleted.

63 changes: 27 additions & 36 deletions src/lib/helpers/encryption.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,59 @@
import type { SecureData } from '$types';

function hexStringToArrayBuffer(hexString: string) {
const hexStringToArrayBuffer = (hexString: string) => {
if (hexString.length % 2 !== 0) {
throw new Error('Invalid hexString length. It must be even.');
}
const bytes = new Uint8Array(hexString.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hexString.slice(i * 2, i * 2 + 2), 16);
const hexBytes = hexString.match(/.{1,2}/g);
if (hexBytes === null) {
throw new Error('Invalid hexString format. It must contain only hexadecimal characters.');
}
return bytes.buffer;
}
return new Uint8Array(hexBytes.map((byte) => parseInt(byte, 16))).buffer;
};

function arrayBufferToBase64(buffer: ArrayBuffer): string {
return btoa(String.fromCharCode(...new Uint8Array(buffer)));
}
const arrayBufferToBase64 = (buffer: ArrayBuffer): string =>
btoa(String.fromCharCode(...new Uint8Array(buffer)));

function base64ToArrayBuffer(base64: string): ArrayBuffer {
const binary_string = atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
const base64ToArrayBuffer = (base64: string): ArrayBuffer =>
new Uint8Array(
atob(base64)
.split('')
.map((char) => char.charCodeAt(0))
).buffer;

function arrayBufferToHexString(buffer: ArrayBuffer) {
const byteArray = new Uint8Array(buffer);
let hexString = '';
byteArray.forEach(function (byte) {
hexString += ('0' + byte.toString(16)).slice(-2);
});
return hexString;
}
const arrayBufferToHexString = (buffer: ArrayBuffer) =>
Array.prototype.map
.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2))
.join('');

export async function hashPassword(password: string): Promise<string> {
export const hashPassword = async (password: string): Promise<string> => {
const encoder = new TextEncoder();
const data = encoder.encode(password);
const algo = { name: 'SHA-256' };
const hashBuffer = await crypto.subtle.digest(algo, data);
return arrayBufferToHexString(hashBuffer); // Convert ArrayBuffer to hex string
}
};

export async function encryptData(
export const encryptData = async (
secretData: Uint8Array,
passwordHashHex: string // Accept hex string
): Promise<SecureData> {
passwordHashHex: string
): Promise<SecureData> => {
const iv = crypto.getRandomValues(new Uint8Array(12));
const algo: AesGcmParams = { name: 'AES-GCM', iv };
const passwordHash = hexStringToArrayBuffer(passwordHashHex);
console.log('Imported key length (bytes):', passwordHash.byteLength);
const key = await crypto.subtle.importKey('raw', passwordHash, algo, false, ['encrypt']);

const encrypted = await crypto.subtle.encrypt(algo, key, secretData);
return {
encryptedData: arrayBufferToBase64(encrypted),
iv: arrayBufferToBase64(iv)
};
}
};

export async function decryptData(
export const decryptData = async (
encryptedData: SecureData,
passwordHashHex: string // Accept hex string
): Promise<Uint8Array> {
): Promise<Uint8Array> => {
const iv = base64ToArrayBuffer(encryptedData.iv);
const algo = { name: 'AES-GCM', iv };
const passwordHash = hexStringToArrayBuffer(passwordHashHex); // Convert hex string to ArrayBuffer
Expand All @@ -74,4 +65,4 @@ export async function decryptData(
base64ToArrayBuffer(encryptedData.encryptedData)
);
return new Uint8Array(decrypted);
}
};
1 change: 0 additions & 1 deletion src/lib/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './navigation';
export * from './other';
export * from './encryption';
export * from './auth';
101 changes: 84 additions & 17 deletions src/lib/queries/app-queries.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,85 @@
// import { createMutation, useQueryClient, createQuery } from '@tanstack/svelte-query';
// import { generateKeys } from '$services';
// import type { GeneratedKeys } from '$types';
// import { generatedKeys } from './query-keys';

export function appQueries() {
// const queryClient = useQueryClient();
// const generateKeysMutation = createMutation({
// mutationFn: (passphrase: string) => generateKeys(passphrase),
// onSuccess: (data) => {
// queryClient.setQueryData([generatedKeys], data);
// }
// });
// const generatedKeysQuery = createQuery<GeneratedKeys>({
// queryKey: [generatedKeys]
// });
// return { generateKeysMutation, generatedKeysQuery };
import { createMutation, useQueryClient, createQuery } from '@tanstack/svelte-query';
import { storageService } from '$services';
import { PasswordAndSecureDataSchema, SessionStateSchema } from '$types';
import {
LOCAL,
PASSWORD_WITH_DEVICE_KEY,
SESSION,
SESSION_DATA,
SESSION_DATA_KEY,
SETUP_KEY
} from '$const';
import { encryptData, hashPassword } from '$helpers';

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_WITH_DEVICE_KEY,
area: LOCAL
});

const validatedResult = PasswordAndSecureDataSchema.safeParse(result);

if (
validatedResult.success &&
(await hashPassword(password)) === validatedResult.data.password
) {
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 setupQuery = createQuery({
queryKey: [SETUP_KEY],
queryFn: async () => {
const data = await storageService.getWithoutCallback({
key: PASSWORD_WITH_DEVICE_KEY,
area: LOCAL
});

const validatedData = PasswordAndSecureDataSchema.safeParse(data);
return validatedData.success;
}
});
const passwordWithDeviceKeyMutation = createMutation({
mutationFn: async (mutationData: { password: string; secretData: Uint8Array }) => {
const hash = await hashPassword(mutationData.password);
storageService.set({
key: PASSWORD_WITH_DEVICE_KEY,
value: {
password: hash,
secureData: await encryptData(mutationData.secretData, hash)
},
area: LOCAL
});
},

onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [SETUP_KEY] });
}
});

return { setupQuery, passwordWithDeviceKeyMutation, sessionQuery, signInMutation };
}
2 changes: 0 additions & 2 deletions src/lib/stores/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
export * from './passphrase';
export * from './password-exist';
export * from './keys-store';
export * from './session';
3 changes: 2 additions & 1 deletion src/lib/stores/keys-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const initKeysStore = () => {
master: null,
revocation: null
}
}))
})),
resetAll: () => update(() => initialState)
};
};

Expand Down
53 changes: 0 additions & 53 deletions src/lib/stores/password-exist.ts

This file was deleted.

47 changes: 0 additions & 47 deletions src/lib/stores/session.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/types/storage-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from 'zod';

export type AreaName = typeof SESSION | typeof LOCAL | 'sync' | 'managed';

export const SessionStateSchema = z.boolean().nullable();
export const SessionStateSchema = z.boolean();

export type SessionState = z.infer<typeof SessionStateSchema>;

Expand Down
Loading

0 comments on commit c4e0a4d

Please sign in to comment.