diff --git a/src/lib/modules/crypto/aes.ts b/src/lib/modules/crypto/aes.ts
index e1e917a..7301373 100644
--- a/src/lib/modules/crypto/aes.ts
+++ b/src/lib/modules/crypto/aes.ts
@@ -1,4 +1,4 @@
-import { encodeToBase64, decodeFromBase64 } from "./util";
+import { encodeString, decodeString, encodeToBase64, decodeFromBase64 } from "./util";
export const generateMasterKey = async () => {
return {
@@ -81,3 +81,12 @@ export const decryptData = async (ciphertext: BufferSource, iv: string, dataKey:
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));
+};
diff --git a/src/lib/modules/crypto/util.ts b/src/lib/modules/crypto/util.ts
index 9aeeb9d..a3e3bc0 100644
--- a/src/lib/modules/crypto/util.ts
+++ b/src/lib/modules/crypto/util.ts
@@ -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) => {
return btoa(String.fromCharCode(...new Uint8Array(data)));
};
diff --git a/src/lib/services/file.ts b/src/lib/services/file.ts
new file mode 100644
index 0000000..adaa524
--- /dev/null
+++ b/src/lib/services/file.ts
@@ -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),
+ };
+};
diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte
new file mode 100644
index 0000000..380dfb2
--- /dev/null
+++ b/src/routes/(fullscreen)/file/[id]/+page.svelte
@@ -0,0 +1,42 @@
+
+
+
+ 파일
+
+
+{#if metadata}
+
+{:else}
+
+{/if}
diff --git a/src/routes/(fullscreen)/file/[id]/+page.ts b/src/routes/(fullscreen)/file/[id]/+page.ts
new file mode 100644
index 0000000..6ecfe77
--- /dev/null
+++ b/src/routes/(fullscreen)/file/[id]/+page.ts
@@ -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,
+ };
+};
diff --git a/src/routes/(fullscreen)/file/[id]/service.ts b/src/routes/(fullscreen)/file/[id]/service.ts
new file mode 100644
index 0000000..16aba8b
--- /dev/null
+++ b/src/routes/(fullscreen)/file/[id]/service.ts
@@ -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((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();
+ });
+};
diff --git a/src/routes/(main)/directory/[[id]]/service.ts b/src/routes/(main)/directory/[[id]]/service.ts
index b093e61..d7cae06 100644
--- a/src/routes/(main)/directory/[[id]]/service.ts
+++ b/src/routes/(main)/directory/[[id]]/service.ts
@@ -1,12 +1,12 @@
import { callSignedPostApi } from "$lib/hooks";
import {
encodeToBase64,
- decodeFromBase64,
generateDataKey,
wrapDataKey,
unwrapDataKey,
encryptData,
- decryptData,
+ encryptString,
+ decryptString,
digestMessage,
signRequestBody,
} from "$lib/modules/crypto";
@@ -14,28 +14,18 @@ import type {
DirectroyInfoResponse,
DirectoryCreateRequest,
FileUploadRequest,
- FileInfoResponse,
} from "$lib/server/schemas";
import type { MasterKey } from "$lib/stores";
+export { decryptFileMetadata } from "$lib/services/file";
+
export const decryptDirectroyMetadata = async (
metadata: NonNullable,
masterKey: CryptoKey,
) => {
const { dataKey } = await unwrapDataKey(metadata.dek, masterKey);
return {
- name: new TextDecoder().decode(
- 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),
- ),
+ name: await decryptString(metadata.name, metadata.nameIv, dataKey),
};
};
@@ -69,7 +59,7 @@ export const requestFileUpload = async (
const { dataKey } = await generateDataKey();
const fileEncrypted = await encryptData(await file.arrayBuffer(), dataKey);
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();
form.set(
@@ -81,7 +71,7 @@ export const requestFileUpload = async (
dek: await wrapDataKey(dataKey, masterKey.key),
contentHash: encodeToBase64(fileEncryptedHash),
contentIv: fileEncrypted.iv,
- name: encodeToBase64(nameEncrypted.ciphertext),
+ name: nameEncrypted.ciphertext,
nameIv: nameEncrypted.iv,
},
signKey,