파일/디렉터리 생성/이름 변경시 로그를 남기도록 변경

This commit is contained in:
static
2025-01-12 19:02:21 +09:00
parent aebbc6d0c0
commit f8115f4f2e
8 changed files with 74 additions and 32 deletions

View File

@@ -1,13 +1,13 @@
import { and, eq, isNull } from "drizzle-orm";
import db from "./drizzle";
import { IntegrityError } from "./error";
import { directory, file, mek } from "./schema";
import { directory, directoryLog, file, fileLog, mek } from "./schema";
type DirectoryId = "root" | number;
export interface NewDirectoryParams {
userId: number;
parentId: DirectoryId;
userId: number;
mekVersion: number;
encDek: string;
dekVersion: Date;
@@ -16,9 +16,9 @@ export interface NewDirectoryParams {
}
export interface NewFileParams {
path: string;
parentId: DirectoryId;
userId: number;
path: string;
mekVersion: number;
encDek: string;
dekVersion: Date;
@@ -40,14 +40,23 @@ export const registerDirectory = async (params: NewDirectoryParams) => {
throw new IntegrityError("Inactive MEK version");
}
await tx.insert(directory).values({
createdAt: new Date(),
parentId: params.parentId === "root" ? null : params.parentId,
userId: params.userId,
mekVersion: params.mekVersion,
encDek: params.encDek,
dekVersion: params.dekVersion,
encName: { ciphertext: params.encName, iv: params.encNameIv },
const newDirectories = await tx
.insert(directory)
.values({
parentId: params.parentId === "root" ? null : params.parentId,
userId: params.userId,
mekVersion: params.mekVersion,
encDek: params.encDek,
dekVersion: params.dekVersion,
encName: { ciphertext: params.encName, iv: params.encNameIv },
})
.returning({ id: directory.id });
const { id: directoryId } = newDirectories[0]!;
await tx.insert(directoryLog).values({
directoryId,
timestamp: new Date(),
action: "create",
newName: { ciphertext: params.encName, iv: params.encNameIv },
});
},
{ behavior: "exclusive" },
@@ -99,6 +108,12 @@ export const setDirectoryEncName = async (
.update(directory)
.set({ encName: { ciphertext: encName, iv: encNameIv } })
.where(and(eq(directory.userId, userId), eq(directory.id, directoryId)));
await tx.insert(directoryLog).values({
directoryId,
timestamp: new Date(),
action: "rename",
newName: { ciphertext: encName, iv: encNameIv },
});
},
{ behavior: "exclusive" },
);
@@ -148,17 +163,26 @@ export const registerFile = async (params: NewFileParams) => {
throw new IntegrityError("Inactive MEK version");
}
await tx.insert(file).values({
path: params.path,
parentId: params.parentId === "root" ? null : params.parentId,
createdAt: new Date(),
userId: params.userId,
mekVersion: params.mekVersion,
contentType: params.contentType,
encDek: params.encDek,
dekVersion: params.dekVersion,
encContentIv: params.encContentIv,
encName: { ciphertext: params.encName, iv: params.encNameIv },
const newFiles = await tx
.insert(file)
.values({
path: params.path,
parentId: params.parentId === "root" ? null : params.parentId,
userId: params.userId,
mekVersion: params.mekVersion,
contentType: params.contentType,
encDek: params.encDek,
dekVersion: params.dekVersion,
encContentIv: params.encContentIv,
encName: { ciphertext: params.encName, iv: params.encNameIv },
})
.returning({ id: file.id });
const { id: fileId } = newFiles[0]!;
await tx.insert(fileLog).values({
fileId,
timestamp: new Date(),
action: "create",
newName: { ciphertext: params.encName, iv: params.encNameIv },
});
},
{ behavior: "exclusive" },
@@ -210,6 +234,12 @@ export const setFileEncName = async (
.update(file)
.set({ encName: { ciphertext: encName, iv: encNameIv } })
.where(and(eq(file.userId, userId), eq(file.id, fileId)));
await tx.insert(fileLog).values({
fileId,
timestamp: new Date(),
action: "rename",
newName: { ciphertext: encName, iv: encNameIv },
});
},
{ behavior: "exclusive" },
);

View File

@@ -12,7 +12,6 @@ export const directory = sqliteTable(
"directory",
{
id: integer("id").primaryKey({ autoIncrement: true }),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
parentId: integer("parent_id"),
userId: integer("user_id")
.notNull()
@@ -34,16 +33,25 @@ export const directory = sqliteTable(
}),
);
export const directoryLog = sqliteTable("directory_log", {
id: integer("id").primaryKey({ autoIncrement: true }),
directoryId: integer("directory_id")
.notNull()
.references(() => directory.id, { onDelete: "cascade" }),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
action: text("action", { enum: ["create", "rename"] }).notNull(),
newName: ciphertext("new_name"),
});
export const file = sqliteTable(
"file",
{
id: integer("id").primaryKey({ autoIncrement: true }),
path: text("path").notNull().unique(),
parentId: integer("parent_id").references(() => directory.id),
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
userId: integer("user_id")
.notNull()
.references(() => user.id),
path: text("path").notNull().unique(),
mekVersion: integer("master_encryption_key_version").notNull(),
encDek: text("encrypted_data_encryption_key").notNull().unique(), // Base64
dekVersion: integer("data_encryption_key_version", { mode: "timestamp_ms" }).notNull(),
@@ -58,3 +66,13 @@ export const file = sqliteTable(
}),
}),
);
export const fileLog = sqliteTable("file_log", {
id: integer("id").primaryKey({ autoIncrement: true }),
fileId: integer("file_id")
.notNull()
.references(() => file.id, { onDelete: "cascade" }),
timestamp: integer("timestamp", { mode: "timestamp_ms" }).notNull(),
action: text("action", { enum: ["create", "rename"] }).notNull(),
newName: ciphertext("new_name"),
});

View File

@@ -3,7 +3,6 @@ import { z } from "zod";
export const directoryInfoResponse = z.object({
metadata: z
.object({
createdAt: z.string().datetime(),
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),

View File

@@ -2,7 +2,6 @@ import mime from "mime";
import { z } from "zod";
export const fileInfoResponse = z.object({
createdAt: z.string().datetime(),
mekVersion: z.number().int().positive(),
dek: z.string().base64().nonempty(),
dekVersion: z.string().datetime(),

View File

@@ -22,7 +22,6 @@ export const getDirectoryInformation = async (userId: number, directoryId: "root
return {
metadata: directory && {
createdAt: directory.createdAt,
mekVersion: directory.mekVersion,
encDek: directory.encDek,
dekVersion: directory.dekVersion,

View File

@@ -21,7 +21,6 @@ export const getFileInformation = async (userId: number, fileId: number) => {
}
return {
createdAt: file.createdAt,
mekVersion: file.mekVersion,
encDek: file.encDek,
dekVersion: file.dekVersion,

View File

@@ -20,7 +20,6 @@ export const GET: RequestHandler = async ({ locals, params }) => {
return json(
directoryInfoResponse.parse({
metadata: metadata && {
createdAt: metadata.createdAt.toISOString(),
mekVersion: metadata.mekVersion,
dek: metadata.encDek,
dekVersion: metadata.dekVersion.toISOString(),

View File

@@ -16,11 +16,10 @@ export const GET: RequestHandler = async ({ locals, params }) => {
if (!zodRes.success) error(400, "Invalid path parameters");
const { id } = zodRes.data;
const { createdAt, mekVersion, encDek, dekVersion, contentType, encContentIv, encName } =
const { mekVersion, encDek, dekVersion, contentType, encContentIv, encName } =
await getFileInformation(userId, id);
return json(
fileInfoResponse.parse({
createdAt: createdAt.toISOString(),
mekVersion,
dek: encDek,
dekVersion: dekVersion.toISOString(),