키 관리 로직을 Keyring으로 통합하여 하드코딩된 MEK 및 HSK 버전을 제거하고 Svelte Store를 Rune으로 대체

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
staticandClaude Opus 5.5 committed 2026-10-12 00:10:37 +09:00
1 parent aee87fd9b6
commit 2ce73aa331
50 files changed
+633 -641

No files matched your search

@@ -9,14 +9,14 @@
} from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { ForceLoginModal } from "#lib/components/organisms/index.js";
import { clientKeyStore, masterKeyStore } from "#lib/stores/index.js";
import {
requestLogin,
downloadMasterKeys,
keyring,
requestClientRegistrationAndSessionUpgrade,
requestMasterKeyDownload,
requestDeletedFilesCleanup,
requestLogout,
} from "./service";
} from "#lib/client/keys/index.js";
import { requestLogout } from "#lib/services/auth.js";
import { requestDeletedFilesCleanup } from "#lib/services/file.js";
import { requestLogin } from "./service";
let { data } = $props();
@@ -31,10 +31,7 @@
const upgradeSession = async (force: boolean) => {
try {
const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade(
$clientKeyStore!,
force,
);
const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade(force);
if (!force && upgradeError === "Already logged in") {
isForceLoginModalOpen = true;
return;
@@ -44,10 +41,7 @@
// TODO: Multi-user support
if (
$masterKeyStore ||
(await requestMasterKeyDownload($clientKeyStore!.decryptKey, $clientKeyStore!.verifyKey))
) {
if (keyring.hasMasterKeys || (await downloadMasterKeys())) {
await requestDeletedFilesCleanup();
await goto(data.redirectPath);
} else {
@@ -65,7 +59,7 @@
try {
if (!(await requestLogin(email, password))) throw new Error("Failed to login");
if (!$clientKeyStore) return await redirect("/key/generate");
if (!keyring.clientKeys) return await redirect("/key/generate");
await upgradeSession(false);
} catch (e) {
@@ -1,12 +1,5 @@
import { trpc } from "#trpc/client.js";
export { requestLogout } from "#lib/services/auth.js";
export { requestDeletedFilesCleanup } from "#lib/services/file.js";
export {
requestClientRegistrationAndSessionUpgrade,
requestMasterKeyDownload,
} from "#lib/services/key.js";
export const requestLogin = async (email: string, password: string) => {
try {
await trpc.auth.login.mutate({ email, password });
@@ -3,25 +3,24 @@
import { goto } from "$app/navigation";
import { FullscreenDiv } from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { clientKeyStore, masterKeyStore } from "#lib/stores/index.js";
import { generatePublicKeyFingerprint, requestMasterKeyDownload } from "./service";
import {
downloadMasterKeys,
generatePublicKeyFingerprint,
keyring,
} from "#lib/client/keys/index.js";
import IconFingerprint from "~icons/material-symbols/fingerprint";
let { data } = $props();
let fingerprint = $derived(
$clientKeyStore
? generatePublicKeyFingerprint($clientKeyStore.encryptKey, $clientKeyStore.verifyKey)
keyring.clientKeys
? generatePublicKeyFingerprint(keyring.clientKeys.encryptKey, keyring.clientKeys.verifyKey)
: undefined,
);
onMount(async () => {
if (
$masterKeyStore ||
($clientKeyStore &&
(await requestMasterKeyDownload($clientKeyStore.decryptKey, $clientKeyStore.verifyKey)))
) {
if (keyring.hasMasterKeys || (keyring.clientKeys && (await downloadMasterKeys()))) {
await goto(data.redirectPath, { replace: true });
}
});
@@ -1,15 +0,0 @@
import { concatenateBuffers, exportRSAKey, digestMessage } from "#lib/shared/crypto/index.js";
export { requestMasterKeyDownload } from "#lib/services/key.js";
export const generatePublicKeyFingerprint = async (encryptKey: CryptoKey, verifyKey: CryptoKey) => {
const { key: encryptKeyBuffer } = await exportRSAKey(encryptKey);
const { key: verifyKeyBuffer } = await exportRSAKey(verifyKey);
const digest = await digestMessage(concatenateBuffers(encryptKeyBuffer, verifyKeyBuffer));
return Array.from(new Uint8Array(digest))
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
.toUpperCase()
.match(/.{1,4}/g)!
.join(" ");
};
@@ -8,7 +8,6 @@
import { getFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { captureVideoThumbnail } from "#lib/client/media/thumbnail.js";
import { getFileDownloadState } from "#lib/client/transfer/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
import AddToCategoryBottomSheet from "./AddToCategoryBottomSheet.svelte";
import DownloadStatus from "./DownloadStatus.svelte";
@@ -68,21 +67,21 @@
const addToCategory = async (categoryId: number) => {
await requestFileAdditionToCategory(data.id, categoryId);
isAddToCategoryBottomSheetOpen = false;
void getFileInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getFileInfo(data.id); // TODO: FIXME
};
const removeFromCategory = async (categoryId: number) => {
await requestFileRemovalFromCategory(data.id, categoryId);
void getFileInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getFileInfo(data.id); // TODO: FIXME
};
const toggleFavorite = async () => {
await requestFavoriteToggle(data.id, !!info?.isFavorite);
void getFileInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getFileInfo(data.id); // TODO: FIXME
};
$effect(() => {
HybridPromise.resolve(getFileInfo(data.id, $masterKeyStore?.get(1)?.key!)).then((result) => {
HybridPromise.resolve(getFileInfo(data.id)).then((result) => {
if (data.id === result.id) {
info = result;
}
@@ -3,7 +3,6 @@
import { SubCategories } from "#lib/components/molecules/index.js";
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
import { getCategoryInfo, type MaybeCategoryInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
import { requestCategoryCreation } from "./service";
@@ -20,9 +19,7 @@
$effect(() => {
if (isOpen) {
HybridPromise.resolve(getCategoryInfo("root", $masterKeyStore?.get(1)?.key!)).then(
(result) => (categoryInfo = result),
);
HybridPromise.resolve(getCategoryInfo("root")).then((result) => (categoryInfo = result));
}
});
</script>
@@ -34,9 +31,7 @@
class="py-4"
info={categoryInfo}
onSubCategoryClick={({ id }) =>
HybridPromise.resolve(getCategoryInfo(id, $masterKeyStore?.get(1)?.key!)).then(
(result) => (categoryInfo = result),
)}
HybridPromise.resolve(getCategoryInfo(id)).then((result) => (categoryInfo = result))}
onSubCategoryCreateClick={() => (isCategoryCreateModalOpen = true)}
subCategoryCreatePosition="top"
/>
@@ -54,8 +49,8 @@
<CategoryCreateModal
bind:isOpen={isCategoryCreateModalOpen}
onCreateClick={async (name: string) => {
if (await requestCategoryCreation(name, categoryInfo!.id, $masterKeyStore?.get(1)!)) {
void getCategoryInfo(categoryInfo!.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
if (await requestCategoryCreation(name, categoryInfo!.id)) {
void getCategoryInfo(categoryInfo!.id); // TODO: FIXME
return true;
}
return false;
@@ -8,17 +8,13 @@
type FileDownloadState,
} from "#lib/client/transfer/index.js";
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import File from "./File.svelte";
let downloadingFiles: { info: MaybeFileInfo; state: FileDownloadState }[] = $state([]);
onMount(async () => {
const states = getDownloadingFiles();
const infos = await bulkGetFileInfo(
states.map(({ id }) => id),
$masterKeyStore?.get(1)?.key!,
);
const infos = await bulkGetFileInfo(states.map(({ id }) => id));
downloadingFiles = states.map((state) => ({
info: infos.get(state.id)!,
state,
+1 -2
View File
@@ -12,7 +12,6 @@
type MaybeFileInfo,
type SummarizedFileInfo,
} from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { formatDate, formatDateSortable, SortBy, sortEntries } from "#lib/utils/index.js";
let { data } = $props();
@@ -55,7 +54,7 @@
});
onMount(async () => {
files = Array.from((await bulkGetFileInfo(data.files, $masterKeyStore?.get(1)?.key!)).values());
files = Array.from((await bulkGetFileInfo(data.files)).values());
});
</script>
+11 -34
View File
@@ -3,15 +3,15 @@
import { goto } from "$app/navigation";
import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { serializeClientKeys, storeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "#lib/stores/index.js";
import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
import BeforeContinueModal from "./BeforeContinueModal.svelte";
import {
keyring,
registerInitialMasterKeyAndHmacSecret,
requestClientRegistration,
requestSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration,
} from "./service";
serializeClientKeys,
} from "#lib/client/keys/index.js";
import BeforeContinueBottomSheet from "./BeforeContinueBottomSheet.svelte";
import BeforeContinueModal from "./BeforeContinueModal.svelte";
import IconKey from "~icons/material-symbols/key";
@@ -33,40 +33,17 @@
};
const registerPubKeys = async () => {
if (!$clientKeyStore) {
throw new Error("Failed to find client keys");
}
try {
if (
!(await requestClientRegistration(
data.encryptKeyBase64,
$clientKeyStore.decryptKey,
data.verifyKeyBase64,
$clientKeyStore.signKey,
))
)
throw new Error("Failed to register client");
if (!(await requestClientRegistration())) throw new Error("Failed to register client");
await storeClientKeys($clientKeyStore);
await keyring.persistClientKeys();
if (!(await requestSessionUpgrade())[0]) throw new Error("Failed to upgrade session");
if (
!(
await requestSessionUpgrade(
data.encryptKeyBase64,
$clientKeyStore.decryptKey,
data.verifyKeyBase64,
$clientKeyStore.signKey,
)
)[0]
)
throw new Error("Failed to upgrade session");
if (
!(await requestInitialMasterKeyAndHmacSecretRegistration(
!(await registerInitialMasterKeyAndHmacSecret(
data.masterKeyWrapped,
data.hmacSecretWrapped,
$clientKeyStore.signKey,
))
)
throw new Error("Failed to register initial MEK and HSK");
@@ -1,5 +0,0 @@
export { requestSessionUpgrade } from "#lib/services/auth.js";
export {
requestClientRegistration,
requestInitialMasterKeyAndHmacSecretRegistration,
} from "#lib/services/key.js";
@@ -4,19 +4,18 @@
import { BottomDiv, Button, FullscreenDiv, TextButton } from "#lib/components/atoms/index.js";
import { TitledDiv } from "#lib/components/molecules/index.js";
import { ForceLoginModal } from "#lib/components/organisms/index.js";
import { storeClientKeys } from "#lib/modules/key.js";
import { clientKeyStore } from "#lib/stores/index.js";
import { gotoStateful } from "#lib/utils/index.js";
import Order from "./Order.svelte";
import {
generateClientKeys,
generateInitialMasterKey,
generateInitialHmacSecret,
generateInitialMasterKey,
importClientKeys,
keyring,
registerInitialMasterKeyAndHmacSecret,
requestClientRegistrationAndSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration,
requestDeletedFilesCleanup,
} from "./service";
} from "#lib/client/keys/index.js";
import { requestDeletedFilesCleanup } from "#lib/services/file.js";
import { gotoStateful } from "#lib/utils/index.js";
import Order from "./Order.svelte";
import IconKey from "~icons/material-symbols/key";
@@ -62,10 +61,7 @@
};
const upgradeSession = async (force: boolean) => {
const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade(
$clientKeyStore!,
force,
);
const [upgradeRes, upgradeError] = await requestClientRegistrationAndSessionUpgrade(force);
if (!force && upgradeError === "Already logged in") {
isForceLoginModalOpen = true;
return;
@@ -75,19 +71,13 @@
}
const { masterKey, masterKeyWrapped } = await generateInitialMasterKey(
$clientKeyStore!.encryptKey,
keyring.requireClientKeys().encryptKey,
);
const { hmacSecretWrapped } = await generateInitialHmacSecret(masterKey);
await storeClientKeys($clientKeyStore!);
await keyring.persistClientKeys();
if (
!(await requestInitialMasterKeyAndHmacSecretRegistration(
masterKeyWrapped,
hmacSecretWrapped,
$clientKeyStore!.signKey,
))
) {
if (!(await registerInitialMasterKeyAndHmacSecret(masterKeyWrapped, hmacSecretWrapped))) {
// TODO: Error Handling
return;
}
@@ -110,7 +100,7 @@
};
onMount(async () => {
if ($clientKeyStore) {
if (keyring.clientKeys) {
await goto(data.redirectPath, { replace: true });
}
});
@@ -1,79 +0,0 @@
import { deserializeClientKeys } from "#lib/modules/key.js";
import {
generateEncryptionKeyPair,
generateSigningKeyPair,
exportRSAKeyToBase64,
importEncryptionKeyPairFromBase64,
importSigningKeyPairFromBase64,
makeRSAKeyNonextractable,
wrapMasterKey,
generateMasterKey,
makeAESKeyNonextractable,
wrapHmacSecret,
generateHmacSecret,
} from "#lib/shared/crypto/index.js";
import { clientKeyStore } from "#lib/stores/index.js";
export { requestLogout } from "#lib/services/auth.js";
export { requestDeletedFilesCleanup } from "#lib/services/file.js";
export {
requestClientRegistrationAndSessionUpgrade,
requestInitialMasterKeyAndHmacSecretRegistration,
} from "#lib/services/key.js";
export const generateClientKeys = async () => {
const { encryptKey, decryptKey } = await generateEncryptionKeyPair();
const { signKey, verifyKey } = await generateSigningKeyPair();
clientKeyStore.set({
encryptKey,
decryptKey: await makeRSAKeyNonextractable(decryptKey),
signKey: await makeRSAKeyNonextractable(signKey),
verifyKey,
});
return {
encryptKey,
encryptKeyBase64: await exportRSAKeyToBase64(encryptKey),
decryptKeyBase64: await exportRSAKeyToBase64(decryptKey),
signKeyBase64: await exportRSAKeyToBase64(signKey),
verifyKeyBase64: await exportRSAKeyToBase64(verifyKey),
};
};
export const generateInitialMasterKey = async (encryptKey: CryptoKey) => {
const { masterKey } = await generateMasterKey();
return {
masterKey: await makeAESKeyNonextractable(masterKey),
masterKeyWrapped: await wrapMasterKey(masterKey, encryptKey),
};
};
export const generateInitialHmacSecret = async (masterKey: CryptoKey) => {
const { hmacSecret } = await generateHmacSecret();
return {
hmacSecretWrapped: await wrapHmacSecret(hmacSecret, masterKey),
};
};
export const importClientKeys = async (clientKeysSerialized: string) => {
const clientKeys = deserializeClientKeys(clientKeysSerialized);
if (!clientKeys) return false;
const { encryptKey, decryptKey } = await importEncryptionKeyPairFromBase64(
clientKeys.encryptKeyBase64,
clientKeys.decryptKeyBase64,
);
const { signKey, verifyKey } = await importSigningKeyPairFromBase64(
clientKeys.signKeyBase64,
clientKeys.verifyKeyBase64,
);
clientKeyStore.set({
encryptKey,
decryptKey: await makeRSAKeyNonextractable(decryptKey),
signKey: await makeRSAKeyNonextractable(signKey),
verifyKey,
});
return true;
};
+9 -15
View File
@@ -10,7 +10,6 @@
type LocalCategoryInfo,
type MaybeDirectoryInfo,
} from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise, searchString, sortEntries } from "#lib/utils/index.js";
import Directory from "./Directory.svelte";
import File from "./File.svelte";
@@ -122,12 +121,10 @@
$effect.pre(() => {
if (data.directoryId) {
HybridPromise.resolve(getDirectoryInfo(data.directoryId, $masterKeyStore?.get(1)?.key!)).then(
(res) => {
directoryInfo = res;
filters.searchInDirectory = res.exists;
},
);
HybridPromise.resolve(getDirectoryInfo(data.directoryId)).then((res) => {
directoryInfo = res;
filters.searchInDirectory = res.exists;
});
} else {
directoryInfo = undefined;
filters.searchInDirectory = false;
@@ -151,14 +148,11 @@
}
if (hasAnyFilter) {
requestSearch(
{
ancestorId: filters.searchInDirectory ? data.directoryId! : "root",
inFavorites: filters.searchInFavorites,
categories: filters.categories,
},
$masterKeyStore?.get(1)?.key!,
).then((res) => {
requestSearch({
ancestorId: filters.searchInDirectory ? data.directoryId! : "root",
inFavorites: filters.searchInFavorites,
categories: filters.categories,
}).then((res) => {
serverResult = res;
});
}
+1 -1
View File
@@ -18,7 +18,7 @@
<ActionEntryButton class="h-14" {onclick}>
<DirectoryEntryLabel
type="file"
thumbnail={$thumbnail}
thumbnail={thumbnail?.url}
name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
isFavorite={info.isFavorite}
@@ -6,7 +6,6 @@
type LocalCategoryInfo,
type MaybeCategoryInfo,
} from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
interface Props {
@@ -21,9 +20,7 @@
$effect(() => {
if (isOpen) {
HybridPromise.resolve(getCategoryInfo("root", $masterKeyStore?.get(1)?.key!)).then(
(result) => (categoryInfo = result),
);
HybridPromise.resolve(getCategoryInfo("root")).then((result) => (categoryInfo = result));
}
});
</script>
@@ -35,9 +32,7 @@
class="py-4"
info={categoryInfo}
onSubCategoryClick={({ id }) =>
HybridPromise.resolve(getCategoryInfo(id, $masterKeyStore?.get(1)?.key!)).then(
(result) => (categoryInfo = result),
)}
HybridPromise.resolve(getCategoryInfo(id)).then((result) => (categoryInfo = result))}
/>
{#if categoryInfo.id !== "root"}
<BottomDiv>
+3 -5
View File
@@ -19,7 +19,7 @@ export interface SearchResult {
files: FileInfo[];
}
export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey) => {
export const requestSearch = async (filter: SearchFilter) => {
const { directories: directoriesRaw, files: filesRaw } = await trpc.search.search.query({
ancestor: filter.ancestorId,
inFavorites: filter.inFavorites,
@@ -33,12 +33,10 @@ export const requestSearch = async (filter: SearchFilter, masterKey: CryptoKey)
const [directories, files] = await HybridPromise.all([
HybridPromise.all(
directoriesRaw.map((directory) =>
getDirectoryInfo(directory.id, masterKey, { serverResponse: directory }),
getDirectoryInfo(directory.id, { serverResponse: directory }),
),
),
HybridPromise.all(
filesRaw.map((file) => getFileInfo(file.id, masterKey, { serverResponse: file })),
),
HybridPromise.all(filesRaw.map((file) => getFileInfo(file.id, { serverResponse: file }))),
]);
return { directories, files } as SearchResult;
};
+1 -5
View File
@@ -8,7 +8,6 @@
deleteFileCache as doDeleteFileCache,
} from "#lib/client/cache/index.js";
import { bulkGetFileInfo, type MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { formatFileSize } from "#lib/utils/index.js";
import File from "./File.svelte";
@@ -29,10 +28,7 @@
onMount(async () => {
const indexes = getFileCacheIndex();
const infos = await bulkGetFileInfo(
indexes.map(({ fileId }) => fileId),
$masterKeyStore?.get(1)?.key!,
);
const infos = await bulkGetFileInfo(indexes.map(({ fileId }) => fileId));
fileCache = indexes
.map((index) => ({ index, info: infos.get(index.fileId)! }))
.sort((a, b) => a.index.lastRetrievedAt.getTime() - b.index.lastRetrievedAt.getTime());
@@ -5,7 +5,6 @@
import { IconEntryButton, TopBar } from "#lib/components/molecules/index.js";
import { deleteAllFileThumbnailCaches } from "#lib/client/cache/index.js";
import type { MaybeFileInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { sortEntries } from "#lib/utils/index.js";
import File from "./File.svelte";
import {
@@ -42,9 +41,7 @@
};
onMount(async () => {
fileInfos = sortEntries(
await requestMissingThumbnailFiles(data.files, $masterKeyStore?.get(1)?.key!),
);
fileInfos = sortEntries(await requestMissingThumbnailFiles(data.files));
});
$effect(() => clearThumbnailGenerationStatuses);
@@ -27,10 +27,9 @@ export const clearThumbnailGenerationStatuses = () => {
export const requestMissingThumbnailFiles = async (
filesRaw: RouterOutputs["file"]["listWithoutThumbnail"],
masterKey: CryptoKey,
) => {
const files = await HybridPromise.all(
filesRaw.map((file) => getFileInfo(file.id, masterKey, { serverResponse: file })),
filesRaw.map((file) => getFileInfo(file.id, { serverResponse: file })),
);
return files;
};
+10 -13
View File
@@ -5,7 +5,6 @@
import { CategoryCreateModal } from "#lib/components/organisms/index.js";
import { updateCategoryInfo } from "#lib/client/storage/indexedDB/index.js";
import { getCategoryInfo, type MaybeCategoryInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import { HybridPromise, sortEntries } from "#lib/utils/index.js";
import CategoryDeleteModal from "./CategoryDeleteModal.svelte";
import CategoryMenuBottomSheet from "./CategoryMenuBottomSheet.svelte";
@@ -56,13 +55,11 @@
});
$effect(() => {
HybridPromise.resolve(getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!)).then(
(result) => {
if (data.id === result.id) {
info = result;
}
},
);
HybridPromise.resolve(getCategoryInfo(data.id)).then((result) => {
if (data.id === result.id) {
info = result;
}
});
});
</script>
@@ -114,7 +111,7 @@
onRemoveClick={!details.isRecursive
? async ({ id }) => {
await requestFileRemovalFromCategory(id, data.id as number);
void getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getCategoryInfo(data.id); // TODO: FIXME
}
: undefined}
/>
@@ -132,8 +129,8 @@
<CategoryCreateModal
bind:isOpen={isCategoryCreateModalOpen}
onCreateClick={async (name: string) => {
if (await requestCategoryCreation(name, data.id, $masterKeyStore?.get(1)!)) {
void getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
if (await requestCategoryCreation(name, data.id)) {
void getCategoryInfo(data.id); // TODO: FIXME
return true;
}
return false;
@@ -155,7 +152,7 @@
bind:isOpen={isCategoryRenameModalOpen}
onRenameClick={async (newName: string) => {
if (await requestCategoryRename(context.selectedCategory!, newName)) {
void getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getCategoryInfo(data.id); // TODO: FIXME
return true;
}
return false;
@@ -165,7 +162,7 @@
bind:isOpen={isCategoryDeleteModalOpen}
onDeleteClick={async () => {
if (await requestCategoryDeletion(context.selectedCategory!)) {
void getCategoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getCategoryInfo(data.id); // TODO: FIXME
return true;
}
return false;
@@ -27,7 +27,7 @@
>
<DirectoryEntryLabel
type="file"
thumbnail={$thumbnail}
thumbnail={thumbnail?.url}
name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
isFavorite={info.isFavorite}
+12 -23
View File
@@ -1,11 +1,9 @@
<script lang="ts">
import { onMount } from "svelte";
import { goto } from "$app/navigation";
import { page } from "$app/state";
import { FloatingButton } from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import { getDirectoryInfo, type MaybeDirectoryInfo } from "#lib/modules/filesystem/index.js";
import { masterKeyStore, hmacSecretStore } from "#lib/stores/index.js";
import { HybridPromise } from "#lib/utils/index.js";
import DirectoryCreateModal from "./DirectoryCreateModal.svelte";
import DirectoryEntries from "./DirectoryEntries";
@@ -18,7 +16,6 @@
import UploadStatusCard from "./UploadStatusCard.svelte";
import {
createContext,
requestHmacSecretDownload,
requestDirectoryCreation,
requestFileUpload,
requestEntryRename,
@@ -64,7 +61,7 @@
if (!files || files.length === 0) return;
for (const file of files) {
requestFileUpload(file, data.id, $masterKeyStore?.get(1)!, $hmacSecretStore?.get(1)!, () => {
requestFileUpload(file, data.id, () => {
return new Promise((resolve) => {
duplicatedFile = file;
resolveForDuplicateFileModal = resolve;
@@ -74,7 +71,7 @@
.then((res) => {
if (!res) return;
// TODO: FIXME
void getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!);
void getDirectoryInfo(data.id);
})
.catch((e: Error) => {
// TODO: FIXME
@@ -85,20 +82,12 @@
fileInput!.value = "";
};
onMount(async () => {
if (!$hmacSecretStore && !(await requestHmacSecretDownload($masterKeyStore?.get(1)?.key!))) {
throw new Error("Failed to download hmac secrets");
}
});
$effect(() => {
HybridPromise.resolve(getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!)).then(
(result) => {
if (data.id === result.id) {
info = result;
}
},
);
HybridPromise.resolve(getDirectoryInfo(data.id)).then((result) => {
if (data.id === result.id) {
info = result;
}
});
});
</script>
@@ -162,8 +151,8 @@
<DirectoryCreateModal
bind:isOpen={isDirectoryCreateModalOpen}
onCreateClick={async (name) => {
if (await requestDirectoryCreation(name, data.id, $masterKeyStore?.get(1)!)) {
void getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
if (await requestDirectoryCreation(name, data.id)) {
void getDirectoryInfo(data.id); // TODO: FIXME
return true;
}
return false;
@@ -195,7 +184,7 @@
onFavoriteClick={async () => {
if (await requestFavoriteToggle(context.selectedEntry!)) {
isEntryMenuBottomSheetOpen = false;
void getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getDirectoryInfo(data.id); // TODO: FIXME
}
}}
/>
@@ -203,7 +192,7 @@
bind:isOpen={isEntryRenameModalOpen}
onRenameClick={async (newName: string) => {
if (await requestEntryRename(context.selectedEntry!, newName)) {
void getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getDirectoryInfo(data.id); // TODO: FIXME
return true;
}
return false;
@@ -213,7 +202,7 @@
bind:isOpen={isEntryDeleteModalOpen}
onDeleteClick={async () => {
if (await requestEntryDeletion(context.selectedEntry!)) {
void getDirectoryInfo(data.id, $masterKeyStore?.get(1)?.key!); // TODO: FIXME
void getDirectoryInfo(data.id); // TODO: FIXME
return true;
}
return false;
@@ -37,7 +37,7 @@
>
<DirectoryEntryLabel
type="file"
thumbnail={$thumbnail}
thumbnail={thumbnail?.url}
name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
isFavorite={info.isFavorite}
@@ -5,16 +5,10 @@ import {
storeFileThumbnailCache,
deleteFileThumbnailCache,
} from "#lib/client/cache/index.js";
import { storeHmacSecrets } from "#lib/client/storage/indexedDB/index.js";
import { keyring } from "#lib/client/keys/index.js";
import { uploadFile } from "#lib/client/transfer/index.js";
import type { DataKey } from "#lib/modules/filesystem/index.js";
import {
generateDataKey,
wrapDataKey,
unwrapHmacSecret,
encryptString,
} from "#lib/shared/crypto/index.js";
import { hmacSecretStore, type MasterKey, type HmacSecret } from "#lib/stores/index.js";
import { generateDataKey, wrapDataKey, encryptString } from "#lib/shared/crypto/index.js";
import { trpc } from "#trpc/client.js";
export interface SelectedEntry {
@@ -36,35 +30,8 @@ export const useContext = () => {
return getContext<ReturnType<typeof createContext>>("context");
};
export const requestHmacSecretDownload = async (masterKey: CryptoKey) => {
// TODO: MEK rotation
let hmacSecretsWrapped;
try {
hmacSecretsWrapped = await trpc.hsk.list.query();
} catch {
// TODO: Error Handling
return false;
}
const hmacSecrets = await Promise.all(
hmacSecretsWrapped.map(async ({ version, state, hsk: hmacSecretWrapped }) => {
const { hmacSecret } = await unwrapHmacSecret(hmacSecretWrapped, masterKey);
return { version, state, secret: hmacSecret };
}),
);
await storeHmacSecrets(hmacSecrets);
hmacSecretStore.set(new Map(hmacSecrets.map((hmacSecret) => [hmacSecret.version, hmacSecret])));
return true;
};
export const requestDirectoryCreation = async (
name: string,
parentId: "root" | number,
masterKey: MasterKey,
) => {
export const requestDirectoryCreation = async (name: string, parentId: "root" | number) => {
const masterKey = keyring.activeMasterKey;
const { dataKey, dataKeyVersion } = await generateDataKey();
const nameEncrypted = await encryptString(name, dataKey);
@@ -87,11 +54,9 @@ export const requestDirectoryCreation = async (
export const requestFileUpload = async (
file: File,
parentId: "root" | number,
masterKey: MasterKey,
hmacSecret: HmacSecret,
onDuplicate: () => Promise<boolean>,
) => {
const res = await uploadFile(file, parentId, masterKey, hmacSecret, onDuplicate);
const res = await uploadFile(file, parentId, onDuplicate);
if (!res) return false;
if (res.fileBuffer) {
+1 -5
View File
@@ -3,7 +3,6 @@
import { goto } from "$app/navigation";
import { RowVirtualizer } from "#lib/components/atoms/index.js";
import { TopBar } from "#lib/components/molecules/index.js";
import { masterKeyStore } from "#lib/stores/index.js";
import Directory from "./Directory.svelte";
import File from "./File.svelte";
import { requestFavoriteEntries, requestRemoveFavorite, type FavoriteEntry } from "./service";
@@ -16,10 +15,7 @@
let isLoading = $state(true);
onMount(async () => {
const masterKey = $masterKeyStore?.get(1)?.key;
if (masterKey) {
entries = await requestFavoriteEntries(data.favorites, masterKey);
}
entries = await requestFavoriteEntries(data.favorites);
isLoading = false;
});
+1 -1
View File
@@ -26,7 +26,7 @@
>
<DirectoryEntryLabel
type="file"
thumbnail={$thumbnail}
thumbnail={thumbnail?.url}
name={info.name}
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
isFavorite
+3 -6
View File
@@ -13,21 +13,18 @@ export type FavoriteEntry =
| { type: "directory"; name: string; details: SubDirectoryInfo }
| { type: "file"; name: string; details: SummarizedFileInfo };
export const requestFavoriteEntries = async (
favorites: RouterOutputs["favorites"]["get"],
masterKey: CryptoKey,
) => {
export const requestFavoriteEntries = async (favorites: RouterOutputs["favorites"]["get"]) => {
const [directories, files] = await HybridPromise.all([
HybridPromise.all(
favorites.directories.map((directory) =>
getDirectoryInfo(directory.id, masterKey, {
getDirectoryInfo(directory.id, {
serverResponse: { ...directory, isFavorite: true },
}),
),
),
HybridPromise.all(
favorites.files.map((file) =>
getFileInfo(file.id, masterKey, { serverResponse: { ...file, isFavorite: true } }),
getFileInfo(file.id, { serverResponse: { ...file, isFavorite: true } }),
),
),
]);
+3 -4
View File
@@ -1,6 +1,5 @@
import { redirect } from "@sveltejs/kit";
import { get } from "svelte/store";
import { clientKeyStore, masterKeyStore } from "#lib/stores/index.js";
import { keyring } from "#lib/client/keys/index.js";
import type { LayoutLoad } from "./$types";
export const ssr = false;
@@ -11,9 +10,9 @@ export const load: LayoutLoad = ({ url }) => {
if (KEYLESS_PATHS.some((path) => url.pathname.startsWith(path))) return;
const redirectQuery = `?redirect=${encodeURIComponent(url.pathname + url.search)}`;
if (!get(clientKeyStore)) {
if (!keyring.clientKeys) {
redirect(307, "/key/generate" + redirectQuery);
} else if (!get(masterKeyStore)) {
} else if (!keyring.hasMasterKeys) {
redirect(307, "/client/pending" + redirectQuery);
}
};