diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index 270add3..edf0249 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -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" }, ); diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index dbaf944..0bae91a 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -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"), +}); diff --git a/src/lib/server/schemas/directory.ts b/src/lib/server/schemas/directory.ts index eda0ac3..5f526aa 100644 --- a/src/lib/server/schemas/directory.ts +++ b/src/lib/server/schemas/directory.ts @@ -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(), diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 3d3d6f5..0592835 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -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(), diff --git a/src/lib/server/services/directory.ts b/src/lib/server/services/directory.ts index 5dc408f..aba0343 100644 --- a/src/lib/server/services/directory.ts +++ b/src/lib/server/services/directory.ts @@ -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, diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index c87414c..b342b90 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -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, diff --git a/src/routes/api/directory/[id]/+server.ts b/src/routes/api/directory/[id]/+server.ts index 1b50018..b8cea7f 100644 --- a/src/routes/api/directory/[id]/+server.ts +++ b/src/routes/api/directory/[id]/+server.ts @@ -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(), diff --git a/src/routes/api/file/[id]/+server.ts b/src/routes/api/file/[id]/+server.ts index 6007589..cb5d0de 100644 --- a/src/routes/api/file/[id]/+server.ts +++ b/src/routes/api/file/[id]/+server.ts @@ -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(),