mirror of
https://github.com/kmc7468/arkvault.git
synced 2025-12-14 22:08:45 +00:00
파일 다운로드 임시 구현
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { encodeToBase64, decodeFromBase64 } from "./util";
|
import { encodeString, decodeString, encodeToBase64, decodeFromBase64 } from "./util";
|
||||||
|
|
||||||
export const generateMasterKey = async () => {
|
export const generateMasterKey = async () => {
|
||||||
return {
|
return {
|
||||||
@@ -81,3 +81,12 @@ export const decryptData = async (ciphertext: BufferSource, iv: string, dataKey:
|
|||||||
ciphertext,
|
ciphertext,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const encryptString = async (plaintext: string, dataKey: CryptoKey) => {
|
||||||
|
const { ciphertext, iv } = await encryptData(encodeString(plaintext), dataKey);
|
||||||
|
return { ciphertext: encodeToBase64(ciphertext), iv };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decryptString = async (ciphertext: string, iv: string, dataKey: CryptoKey) => {
|
||||||
|
return decodeString(await decryptData(decodeFromBase64(ciphertext), iv, dataKey));
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
const textEncoder = new TextEncoder();
|
||||||
|
const textDecoder = new TextDecoder();
|
||||||
|
|
||||||
|
export const encodeString = (data: string) => {
|
||||||
|
return textEncoder.encode(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decodeString = (data: ArrayBuffer) => {
|
||||||
|
return textDecoder.decode(data);
|
||||||
|
};
|
||||||
|
|
||||||
export const encodeToBase64 = (data: ArrayBuffer) => {
|
export const encodeToBase64 = (data: ArrayBuffer) => {
|
||||||
return btoa(String.fromCharCode(...new Uint8Array(data)));
|
return btoa(String.fromCharCode(...new Uint8Array(data)));
|
||||||
};
|
};
|
||||||
|
|||||||
10
src/lib/services/file.ts
Normal file
10
src/lib/services/file.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { unwrapDataKey, decryptString } from "$lib/modules/crypto";
|
||||||
|
import type { FileInfoResponse } from "$lib/server/schemas";
|
||||||
|
|
||||||
|
export const decryptFileMetadata = async (metadata: FileInfoResponse, masterKey: CryptoKey) => {
|
||||||
|
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
||||||
|
return {
|
||||||
|
dataKey,
|
||||||
|
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
||||||
|
};
|
||||||
|
};
|
||||||
42
src/routes/(fullscreen)/file/[id]/+page.svelte
Normal file
42
src/routes/(fullscreen)/file/[id]/+page.svelte
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FileSaver from "file-saver";
|
||||||
|
import { TopBar } from "$lib/components";
|
||||||
|
import { masterKeyStore } from "$lib/stores";
|
||||||
|
import { decryptFileMetadata, requestFileDownload } from "./service";
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
let metadata = $state<Awaited<ReturnType<typeof decryptFileMetadata>> | undefined>();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if ($masterKeyStore) {
|
||||||
|
decryptFileMetadata(data.metadata, $masterKeyStore.get(data.metadata.mekVersion)!.key).then(
|
||||||
|
async (_metadata) => {
|
||||||
|
metadata = _metadata;
|
||||||
|
|
||||||
|
const file = await requestFileDownload(
|
||||||
|
data.id,
|
||||||
|
data.metadata.contentIv,
|
||||||
|
_metadata.dataKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: Preview
|
||||||
|
|
||||||
|
const blob = new Blob([file]);
|
||||||
|
|
||||||
|
FileSaver.saveAs(blob, metadata.name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>파일</title>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
{#if metadata}
|
||||||
|
<TopBar title={metadata.name} />
|
||||||
|
{:else}
|
||||||
|
<TopBar />
|
||||||
|
{/if}
|
||||||
24
src/routes/(fullscreen)/file/[id]/+page.ts
Normal file
24
src/routes/(fullscreen)/file/[id]/+page.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { error } from "@sveltejs/kit";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { callGetApi } from "$lib/hooks";
|
||||||
|
import type { FileInfoResponse } from "$lib/server/schemas";
|
||||||
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
|
export const load: PageLoad = async ({ params, fetch }) => {
|
||||||
|
const zodRes = z
|
||||||
|
.object({
|
||||||
|
id: z.coerce.number().int().positive(),
|
||||||
|
})
|
||||||
|
.safeParse(params);
|
||||||
|
if (!zodRes.success) error(404, "Not found");
|
||||||
|
const { id } = zodRes.data;
|
||||||
|
|
||||||
|
const res = await callGetApi(`/api/file/${id}`, fetch);
|
||||||
|
if (!res.ok) error(404, "Not found");
|
||||||
|
|
||||||
|
const fileInfo: FileInfoResponse = await res.json();
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
metadata: fileInfo,
|
||||||
|
};
|
||||||
|
};
|
||||||
33
src/routes/(fullscreen)/file/[id]/service.ts
Normal file
33
src/routes/(fullscreen)/file/[id]/service.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { decryptData } from "$lib/modules/crypto";
|
||||||
|
|
||||||
|
export { decryptFileMetadata } from "$lib/services/file";
|
||||||
|
|
||||||
|
export const requestFileDownload = (
|
||||||
|
fileId: number,
|
||||||
|
fileEncryptedIv: string,
|
||||||
|
dataKey: CryptoKey,
|
||||||
|
) => {
|
||||||
|
return new Promise<ArrayBuffer>((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.responseType = "arraybuffer";
|
||||||
|
|
||||||
|
xhr.addEventListener("load", async () => {
|
||||||
|
if (xhr.status !== 200) {
|
||||||
|
reject(new Error("Failed to download file"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileDecrypted = await decryptData(
|
||||||
|
xhr.response as ArrayBuffer,
|
||||||
|
fileEncryptedIv,
|
||||||
|
dataKey,
|
||||||
|
);
|
||||||
|
resolve(fileDecrypted);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Progress, ...
|
||||||
|
|
||||||
|
xhr.open("GET", `/api/file/${fileId}/download`);
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { callSignedPostApi } from "$lib/hooks";
|
import { callSignedPostApi } from "$lib/hooks";
|
||||||
import {
|
import {
|
||||||
encodeToBase64,
|
encodeToBase64,
|
||||||
decodeFromBase64,
|
|
||||||
generateDataKey,
|
generateDataKey,
|
||||||
wrapDataKey,
|
wrapDataKey,
|
||||||
unwrapDataKey,
|
unwrapDataKey,
|
||||||
encryptData,
|
encryptData,
|
||||||
decryptData,
|
encryptString,
|
||||||
|
decryptString,
|
||||||
digestMessage,
|
digestMessage,
|
||||||
signRequestBody,
|
signRequestBody,
|
||||||
} from "$lib/modules/crypto";
|
} from "$lib/modules/crypto";
|
||||||
@@ -14,28 +14,18 @@ import type {
|
|||||||
DirectroyInfoResponse,
|
DirectroyInfoResponse,
|
||||||
DirectoryCreateRequest,
|
DirectoryCreateRequest,
|
||||||
FileUploadRequest,
|
FileUploadRequest,
|
||||||
FileInfoResponse,
|
|
||||||
} from "$lib/server/schemas";
|
} from "$lib/server/schemas";
|
||||||
import type { MasterKey } from "$lib/stores";
|
import type { MasterKey } from "$lib/stores";
|
||||||
|
|
||||||
|
export { decryptFileMetadata } from "$lib/services/file";
|
||||||
|
|
||||||
export const decryptDirectroyMetadata = async (
|
export const decryptDirectroyMetadata = async (
|
||||||
metadata: NonNullable<DirectroyInfoResponse["metadata"]>,
|
metadata: NonNullable<DirectroyInfoResponse["metadata"]>,
|
||||||
masterKey: CryptoKey,
|
masterKey: CryptoKey,
|
||||||
) => {
|
) => {
|
||||||
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
||||||
return {
|
return {
|
||||||
name: new TextDecoder().decode(
|
name: await decryptString(metadata.name, metadata.nameIv, dataKey),
|
||||||
await decryptData(decodeFromBase64(metadata.name), metadata.nameIv, dataKey),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const decryptFileMetadata = async (metadata: FileInfoResponse, masterKey: CryptoKey) => {
|
|
||||||
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
|
|
||||||
return {
|
|
||||||
name: new TextDecoder().decode(
|
|
||||||
await decryptData(decodeFromBase64(metadata.name), metadata.nameIv, dataKey),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -69,7 +59,7 @@ export const requestFileUpload = async (
|
|||||||
const { dataKey } = await generateDataKey();
|
const { dataKey } = await generateDataKey();
|
||||||
const fileEncrypted = await encryptData(await file.arrayBuffer(), dataKey);
|
const fileEncrypted = await encryptData(await file.arrayBuffer(), dataKey);
|
||||||
const fileEncryptedHash = await digestMessage(fileEncrypted.ciphertext);
|
const fileEncryptedHash = await digestMessage(fileEncrypted.ciphertext);
|
||||||
const nameEncrypted = await encryptData(new TextEncoder().encode(file.name), dataKey);
|
const nameEncrypted = await encryptString(file.name, dataKey);
|
||||||
|
|
||||||
const form = new FormData();
|
const form = new FormData();
|
||||||
form.set(
|
form.set(
|
||||||
@@ -81,7 +71,7 @@ export const requestFileUpload = async (
|
|||||||
dek: await wrapDataKey(dataKey, masterKey.key),
|
dek: await wrapDataKey(dataKey, masterKey.key),
|
||||||
contentHash: encodeToBase64(fileEncryptedHash),
|
contentHash: encodeToBase64(fileEncryptedHash),
|
||||||
contentIv: fileEncrypted.iv,
|
contentIv: fileEncrypted.iv,
|
||||||
name: encodeToBase64(nameEncrypted.ciphertext),
|
name: nameEncrypted.ciphertext,
|
||||||
nameIv: nameEncrypted.iv,
|
nameIv: nameEncrypted.iv,
|
||||||
},
|
},
|
||||||
signKey,
|
signKey,
|
||||||
|
|||||||
Reference in New Issue
Block a user