From 9fad26d53888de0450cae21a68f47f6965af9fe5 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 6 Jan 2025 03:05:31 +0900 Subject: [PATCH] =?UTF-8?q?/api/file/[id]/delete,=20/api/file/[id]/rename,?= =?UTF-8?q?=20/api/directory/[id]/delete,=20/api/directory/[id]/rename=20E?= =?UTF-8?q?ndpoint=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/server/db/file.ts | 63 +++++++++++++++++ src/lib/server/schemas/directory.ts | 6 ++ src/lib/server/schemas/file.ts | 6 ++ src/lib/server/services/directory.ts | 68 +++++++++++++++++++ src/lib/server/services/file.ts | 55 +++++++-------- src/routes/api/directory/[id]/+server.ts | 2 +- .../api/directory/[id]/delete/+server.ts | 20 ++++++ .../api/directory/[id]/rename/+server.ts | 27 ++++++++ src/routes/api/directory/create/+server.ts | 2 +- src/routes/api/file/[id]/delete/+server.ts | 20 ++++++ src/routes/api/file/[id]/rename/+server.ts | 27 ++++++++ 11 files changed, 263 insertions(+), 33 deletions(-) create mode 100644 src/lib/server/services/directory.ts create mode 100644 src/routes/api/directory/[id]/delete/+server.ts create mode 100644 src/routes/api/directory/[id]/rename/+server.ts create mode 100644 src/routes/api/file/[id]/delete/+server.ts create mode 100644 src/routes/api/file/[id]/rename/+server.ts diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index a85dcab..0b1d5bd 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -69,6 +69,47 @@ export const getDirectory = async (userId: number, directoryId: number) => { return res[0] ?? null; }; +export const setDirectoryEncName = async ( + userId: number, + directoryId: number, + encName: string, + encNameIv: string, +) => { + await db + .update(directory) + .set({ encName: { ciphertext: encName, iv: encNameIv } }) + .where(and(eq(directory.userId, userId), eq(directory.id, directoryId))) + .execute(); +}; + +export const unregisterDirectory = async (userId: number, directoryId: number) => { + return await db.transaction(async (tx) => { + const getFilePaths = async (parentId: number) => { + const files = await tx + .select({ path: file.path }) + .from(file) + .where(and(eq(file.userId, userId), eq(file.parentId, parentId))); + return files.map(({ path }) => path); + }; + const unregisterSubDirectoriesRecursively = async (directoryId: number): Promise => { + const subDirectories = await tx + .select({ id: directory.id }) + .from(directory) + .where(and(eq(directory.userId, userId), eq(directory.parentId, directoryId))); + const subDirectoryFilePaths = await Promise.all( + subDirectories.map(async ({ id }) => await unregisterSubDirectoriesRecursively(id)), + ); + const filePaths = await getFilePaths(directoryId); + + await tx.delete(file).where(eq(file.parentId, directoryId)); + await tx.delete(directory).where(eq(directory.id, directoryId)); + + return filePaths.concat(...subDirectoryFilePaths); + }; + return await unregisterSubDirectoriesRecursively(directoryId); + }); +}; + export const registerNewFile = async (params: NewFileParams) => { await db.transaction(async (tx) => { const meks = await tx @@ -115,3 +156,25 @@ export const getFile = async (userId: number, fileId: number) => { .execute(); return res[0] ?? null; }; + +export const setFileEncName = async ( + userId: number, + fileId: number, + encName: string, + encNameIv: string, +) => { + await db + .update(file) + .set({ encName: { ciphertext: encName, iv: encNameIv } }) + .where(and(eq(file.userId, userId), eq(file.id, fileId))) + .execute(); +}; + +export const unregisterFile = async (userId: number, fileId: number) => { + const res = await db + .delete(file) + .where(and(eq(file.userId, userId), eq(file.id, fileId))) + .returning({ path: file.path }) + .execute(); + return res[0]?.path ?? null; +}; diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index b4594c9..cad0f0c 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -1,5 +1,11 @@ import { z } from "zod"; +export const directoryRenameRequest = z.object({ + name: z.string().base64().nonempty(), + nameIv: z.string().base64().nonempty(), +}); +export type DirectoryRenameRequest = z.infer; + export const directroyInfoResponse = z.object({ metadata: z .object({ diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index a9ba7e4..b753517 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -1,5 +1,11 @@ import { z } from "zod"; +export const fileRenameRequest = z.object({ + name: z.string().base64().nonempty(), + nameIv: z.string().base64().nonempty(), +}); +export type FileRenameRequest = z.infer; + export const fileInfoResponse = z.object({ createdAt: z.date(), mekVersion: z.number().int().positive(), diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts new file mode 100644 index 0000000..ede995b --- /dev/null +++ b/src/lib/server/services/directory.ts @@ -0,0 +1,68 @@ +import { error } from "@sveltejs/kit"; +import { unlink } from "fs/promises"; +import { + getAllDirectoriesByParent, + registerNewDirectory, + getDirectory, + setDirectoryEncName, + unregisterDirectory, + getAllFilesByParent, + type NewDirectroyParams, +} from "$lib/server/db/file"; +import { getActiveMekVersion } from "$lib/server/db/mek"; + +export const deleteDirectory = async (userId: number, directoryId: number) => { + const directory = await getDirectory(userId, directoryId); + if (!directory) { + error(404, "Invalid directory id"); + } + + const filePaths = await unregisterDirectory(userId, directoryId); + filePaths.map((path) => unlink(path)); // Intended +}; + +export const renameDirectory = async ( + userId: number, + directoryId: number, + newEncName: string, + newEncNameIv: string, +) => { + const directory = await getDirectory(userId, directoryId); + if (!directory) { + error(404, "Invalid directory id"); + } + + await setDirectoryEncName(userId, directoryId, newEncName, newEncNameIv); +}; + +export const getDirectroyInformation = async (userId: number, directroyId: "root" | number) => { + const directory = directroyId !== "root" ? await getDirectory(userId, directroyId) : undefined; + if (directory === null) { + error(404, "Invalid directory id"); + } + + const directories = await getAllDirectoriesByParent(userId, directroyId); + const files = await getAllFilesByParent(userId, directroyId); + + return { + metadata: directory && { + createdAt: directory.createdAt, + mekVersion: directory.mekVersion, + encDek: directory.encDek, + encName: directory.encName, + }, + directories: directories.map(({ id }) => id), + files: files.map(({ id }) => id), + }; +}; + +export const createDirectory = async (params: NewDirectroyParams) => { + const activeMekVersion = await getActiveMekVersion(params.userId); + if (activeMekVersion === null) { + error(500, "Invalid MEK version"); + } else if (activeMekVersion !== params.mekVersion) { + error(400, "Invalid MEK version"); + } + + await registerNewDirectory(params); +}; diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index fda36a6..e98a642 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -5,48 +5,27 @@ import { mkdir, stat, unlink } from "fs/promises"; import { dirname } from "path"; import { v4 as uuidv4 } from "uuid"; import { - getAllDirectoriesByParent, - registerNewDirectory, - getDirectory, registerNewFile, - getAllFilesByParent, getFile, - type NewDirectroyParams, + setFileEncName, + unregisterFile, type NewFileParams, } from "$lib/server/db/file"; import { getActiveMekVersion } from "$lib/server/db/mek"; import env from "$lib/server/loadenv"; -export const getDirectroyInformation = async (userId: number, directroyId: "root" | number) => { - const directory = directroyId !== "root" ? await getDirectory(userId, directroyId) : undefined; - if (directory === null) { - error(404, "Invalid directory id"); +export const deleteFile = async (userId: number, fileId: number) => { + const file = await getFile(userId, fileId); + if (!file) { + error(404, "Invalid file id"); } - const directories = await getAllDirectoriesByParent(userId, directroyId); - const files = await getAllFilesByParent(userId, directroyId); - - return { - metadata: directory && { - createdAt: directory.createdAt, - mekVersion: directory.mekVersion, - encDek: directory.encDek, - encName: directory.encName, - }, - directories: directories.map(({ id }) => id), - files: files.map(({ id }) => id), - }; -}; - -export const createDirectory = async (params: NewDirectroyParams) => { - const activeMekVersion = await getActiveMekVersion(params.userId); - if (activeMekVersion === null) { - error(500, "Invalid MEK version"); - } else if (activeMekVersion !== params.mekVersion) { - error(400, "Invalid MEK version"); + const path = await unregisterFile(userId, fileId); + if (!path) { + error(500, "Invalid file id"); } - await registerNewDirectory(params); + unlink(path); // Intended }; const convertToReadableStream = (readStream: ReadStream) => { @@ -75,6 +54,20 @@ export const getFileStream = async (userId: number, fileId: number) => { }; }; +export const renameFile = async ( + userId: number, + fileId: number, + newEncName: string, + newEncNameIv: string, +) => { + const file = await getFile(userId, fileId); + if (!file) { + error(404, "Invalid file id"); + } + + await setFileEncName(userId, fileId, newEncName, newEncNameIv); +}; + export const getFileInformation = async (userId: number, fileId: number) => { const file = await getFile(userId, fileId); if (!file) { diff --git a/src/routes/api/directory/[id]/+server.ts b/src/routes/api/directory/[id]/+server.ts index 108a3b1..806ea4d 100644 --- a/src/routes/api/directory/[id]/+server.ts +++ b/src/routes/api/directory/[id]/+server.ts @@ -2,7 +2,7 @@ import { error, json } from "@sveltejs/kit"; import { z } from "zod"; import { authorize } from "$lib/server/modules/auth"; import { directroyInfoResponse, type DirectroyInfoResponse } from "$lib/server/schemas"; -import { getDirectroyInformation } from "$lib/server/services/file"; +import { getDirectroyInformation } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; export const GET: RequestHandler = async ({ cookies, params }) => { diff --git a/src/routes/api/directory/[id]/delete/+server.ts b/src/routes/api/directory/[id]/delete/+server.ts new file mode 100644 index 0000000..c7777df --- /dev/null +++ b/src/routes/api/directory/[id]/delete/+server.ts @@ -0,0 +1,20 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { deleteDirectory } from "$lib/server/services/directory"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ cookies, params }) => { + const { userId } = await authorize(cookies, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + await deleteDirectory(userId, id); + return text("Directory deleted", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/directory/[id]/rename/+server.ts b/src/routes/api/directory/[id]/rename/+server.ts new file mode 100644 index 0000000..89fa98d --- /dev/null +++ b/src/routes/api/directory/[id]/rename/+server.ts @@ -0,0 +1,27 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { parseSignedRequest } from "$lib/server/modules/crypto"; +import { directoryRenameRequest } from "$lib/server/schemas"; +import { renameDirectory } from "$lib/server/services/directory"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, cookies, params }) => { + const { userId, clientId } = await authorize(cookies, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + const { name, nameIv } = await parseSignedRequest( + clientId, + await request.json(), + directoryRenameRequest, + ); + + await renameDirectory(userId, id, name, nameIv); + return text("Directory renamed", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/directory/create/+server.ts b/src/routes/api/directory/create/+server.ts index ca13705..559d34b 100644 --- a/src/routes/api/directory/create/+server.ts +++ b/src/routes/api/directory/create/+server.ts @@ -2,7 +2,7 @@ import { text } from "@sveltejs/kit"; import { authorize } from "$lib/server/modules/auth"; import { parseSignedRequest } from "$lib/server/modules/crypto"; import { directoryCreateRequest } from "$lib/server/schemas"; -import { createDirectory } from "$lib/server/services/file"; +import { createDirectory } from "$lib/server/services/directory"; import type { RequestHandler } from "./$types"; export const POST: RequestHandler = async ({ request, cookies }) => { diff --git a/src/routes/api/file/[id]/delete/+server.ts b/src/routes/api/file/[id]/delete/+server.ts new file mode 100644 index 0000000..4cbf733 --- /dev/null +++ b/src/routes/api/file/[id]/delete/+server.ts @@ -0,0 +1,20 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { deleteFile } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ cookies, params }) => { + const { userId } = await authorize(cookies, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + + await deleteFile(userId, id); + return text("File deleted", { headers: { "Content-Type": "text/plain" } }); +}; diff --git a/src/routes/api/file/[id]/rename/+server.ts b/src/routes/api/file/[id]/rename/+server.ts new file mode 100644 index 0000000..5c816a8 --- /dev/null +++ b/src/routes/api/file/[id]/rename/+server.ts @@ -0,0 +1,27 @@ +import { error, text } from "@sveltejs/kit"; +import { z } from "zod"; +import { authorize } from "$lib/server/modules/auth"; +import { parseSignedRequest } from "$lib/server/modules/crypto"; +import { fileRenameRequest } from "$lib/server/schemas"; +import { renameFile } from "$lib/server/services/file"; +import type { RequestHandler } from "./$types"; + +export const POST: RequestHandler = async ({ request, cookies, params }) => { + const { userId, clientId } = await authorize(cookies, "activeClient"); + + const zodRes = z + .object({ + id: z.coerce.number().int().positive(), + }) + .safeParse(params); + if (!zodRes.success) error(400, "Invalid path parameters"); + const { id } = zodRes.data; + const { name, nameIv } = await parseSignedRequest( + clientId, + await request.json(), + fileRenameRequest, + ); + + await renameFile(userId, id, name, nameIv); + return text("File renamed", { headers: { "Content-Type": "text/plain" } }); +};