DB에 동시적으로 접근하더라도 데이터 무결성이 깨지지 않도록 DB 접근 코드 수정

This commit is contained in:
static
2025-01-11 03:55:19 +09:00
parent 045eb69487
commit 0bdf990dae
12 changed files with 486 additions and 438 deletions

View File

@@ -1,77 +1,19 @@
import { error } from "@sveltejs/kit";
import { createReadStream, createWriteStream, ReadStream, WriteStream } from "fs";
import { createReadStream, createWriteStream } from "fs";
import { mkdir, stat, unlink } from "fs/promises";
import { dirname } from "path";
import { Readable, Writable } from "stream";
import { v4 as uuidv4 } from "uuid";
import { IntegrityError } from "$lib/server/db/error";
import {
registerNewFile,
registerFile,
getFile,
setFileEncName,
unregisterFile,
type NewFileParams,
} from "$lib/server/db/file";
import { getActiveMekVersion } from "$lib/server/db/mek";
import env from "$lib/server/loadenv";
export const deleteFile = async (userId: number, fileId: number) => {
const file = await getFile(userId, fileId);
if (!file) {
error(404, "Invalid file id");
}
const path = await unregisterFile(userId, fileId);
if (!path) {
error(500, "Invalid file id");
}
unlink(path); // Intended
};
const convertToReadableStream = (readStream: ReadStream) => {
return new ReadableStream<Uint8Array>({
start: (controller) => {
readStream.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk as Buffer)));
readStream.on("end", () => controller.close());
readStream.on("error", (e) => controller.error(e));
},
cancel: () => {
readStream.destroy();
},
});
};
export const getFileStream = async (userId: number, fileId: number) => {
const file = await getFile(userId, fileId);
if (!file) {
error(404, "Invalid file id");
}
const { size } = await stat(file.path);
return {
encContentStream: convertToReadableStream(createReadStream(file.path)),
encContentSize: size,
};
};
export const renameFile = async (
userId: number,
fileId: number,
dekVersion: Date,
newEncName: string,
newEncNameIv: string,
) => {
const file = await getFile(userId, fileId);
if (!file) {
error(404, "Invalid file id");
} else if (file.dekVersion.getTime() !== dekVersion.getTime()) {
error(400, "Invalid DEK version");
}
if (!(await setFileEncName(userId, fileId, dekVersion, newEncName, newEncNameIv))) {
error(500, "Invalid file id or DEK version");
}
};
export const getFileInformation = async (userId: number, fileId: number) => {
const file = await getFile(userId, fileId);
if (!file) {
@@ -89,20 +31,50 @@ export const getFileInformation = async (userId: number, fileId: number) => {
};
};
const convertToWritableStream = (writeStream: WriteStream) => {
return new WritableStream<Uint8Array>({
write: (chunk) =>
new Promise((resolve, reject) => {
writeStream.write(chunk, (e) => {
if (e) {
reject(e);
} else {
resolve();
}
});
}),
close: () => new Promise((resolve) => writeStream.end(resolve)),
});
export const deleteFile = async (userId: number, fileId: number) => {
try {
const filePath = await unregisterFile(userId, fileId);
unlink(filePath); // Intended
} catch (e) {
if (e instanceof IntegrityError && e.message === "File not found") {
error(404, "Invalid file id");
}
throw e;
}
};
export const getFileStream = async (userId: number, fileId: number) => {
const file = await getFile(userId, fileId);
if (!file) {
error(404, "Invalid file id");
}
const { size } = await stat(file.path);
return {
encContentStream: Readable.toWeb(createReadStream(file.path)),
encContentSize: size,
};
};
export const renameFile = async (
userId: number,
fileId: number,
dekVersion: Date,
newEncName: string,
newEncNameIv: string,
) => {
try {
await setFileEncName(userId, fileId, dekVersion, newEncName, newEncNameIv);
} catch (e) {
if (e instanceof IntegrityError) {
if (e.message === "File not found") {
error(404, "Invalid file id");
} else if (e.message === "Invalid DEK version") {
error(400, "Invalid DEK version");
}
}
throw e;
}
};
const safeUnlink = async (path: string) => {
@@ -113,13 +85,6 @@ export const uploadFile = async (
params: Omit<NewFileParams, "path">,
encContentStream: ReadableStream<Uint8Array>,
) => {
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 oneMinuteAgo = new Date(Date.now() - 60 * 1000);
const oneMinuteLater = new Date(Date.now() + 60 * 1000);
if (params.dekVersion <= oneMinuteAgo || params.dekVersion >= oneMinuteLater) {
@@ -131,14 +96,20 @@ export const uploadFile = async (
try {
await encContentStream.pipeTo(
convertToWritableStream(createWriteStream(path, { flags: "wx", mode: 0o600 })),
Writable.toWeb(createWriteStream(path, { flags: "wx", mode: 0o600 })),
);
await registerNewFile({
await registerFile({
...params,
path,
});
} catch (e) {
await safeUnlink(path);
if (e instanceof IntegrityError) {
if (e.message === "Inactive MEK version") {
error(400, "Invalid MEK version");
}
}
throw e;
}
};