mirror of
https://github.com/kmc7468/arkvault.git
synced 2026-11-04 08:10:28 +09:00
키 관리 로직을 Keyring으로 통합하여 하드코딩된 MEK 및 HSK 버전을 제거하고 Svelte Store를 Rune으로 대체
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
>
|
||||
<DirectoryEntryLabel
|
||||
type="file"
|
||||
thumbnail={$thumbnail}
|
||||
thumbnail={thumbnail?.url}
|
||||
name={info.name}
|
||||
subtext={formatDateTime(info.createdAt ?? info.lastModifiedAt)}
|
||||
isFavorite
|
||||
|
||||
@@ -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 } }),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
Reference in new issue
Block a user