diff --git a/drizzle/0000_lazy_scarecrow.sql b/drizzle/0000_handy_captain_marvel.sql similarity index 97% rename from drizzle/0000_lazy_scarecrow.sql rename to drizzle/0000_handy_captain_marvel.sql index 89e8f99..05d5e02 100644 --- a/drizzle/0000_lazy_scarecrow.sql +++ b/drizzle/0000_handy_captain_marvel.sql @@ -32,7 +32,7 @@ CREATE TABLE `directory` ( `user_id` integer NOT NULL, `master_encryption_key_version` integer NOT NULL, `encrypted_data_encryption_key` text NOT NULL, - `encrypted_at` integer NOT NULL, + `data_encryption_key_version` integer NOT NULL, `encrypted_name` text NOT NULL, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, @@ -47,7 +47,9 @@ CREATE TABLE `file` ( `user_id` integer NOT NULL, `master_encryption_key_version` integer NOT NULL, `encrypted_data_encryption_key` text NOT NULL, - `encrypted_at` integer NOT NULL, + `data_encryption_key_version` integer NOT NULL, + `content_type` text NOT NULL, + `encrypted_content_iv` text NOT NULL, `encrypted_name` text NOT NULL, FOREIGN KEY (`parent_id`) REFERENCES `directory`(`id`) ON UPDATE no action ON DELETE no action, FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action, diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json index 49d6d24..d8c1013 100644 --- a/drizzle/meta/0000_snapshot.json +++ b/drizzle/meta/0000_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "901e84cd-f9eb-4329-a374-f71264675515", + "id": "929c6bca-d0c0-4899-afc6-a0a498226f28", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "client": { @@ -262,8 +262,8 @@ "notNull": true, "autoincrement": false }, - "encrypted_at": { - "name": "encrypted_at", + "data_encryption_key_version": { + "name": "data_encryption_key_version", "type": "integer", "primaryKey": false, "notNull": true, @@ -384,13 +384,27 @@ "notNull": true, "autoincrement": false }, - "encrypted_at": { - "name": "encrypted_at", + "data_encryption_key_version": { + "name": "data_encryption_key_version", "type": "integer", "primaryKey": false, "notNull": true, "autoincrement": false }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_content_iv": { + "name": "encrypted_content_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, "encrypted_name": { "name": "encrypted_name", "type": "text", diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 7874a98..b2615a0 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1735748192401, - "tag": "0000_lazy_scarecrow", + "when": 1736170919561, + "tag": "0000_handy_captain_marvel", "breakpoints": true } ] diff --git a/package.json b/package.json index ca5bc28..e1c05db 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "eslint-plugin-tailwindcss": "^3.17.5", "file-saver": "^2.0.5", "globals": "^15.0.0", + "mime": "^4.0.6", "prettier": "^3.3.2", "prettier-plugin-svelte": "^3.2.6", "prettier-plugin-tailwindcss": "^0.6.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b791d45..9b4997e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,6 +88,9 @@ devDependencies: globals: specifier: ^15.0.0 version: 15.14.0 + mime: + specifier: ^4.0.6 + version: 4.0.6 prettier: specifier: ^3.3.2 version: 3.4.2 @@ -2689,6 +2692,12 @@ packages: picomatch: 2.3.1 dev: true + /mime@4.0.6: + resolution: {integrity: sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==} + engines: {node: '>=16'} + hasBin: true + dev: true + /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} diff --git a/src/lib/modules/file.ts b/src/lib/modules/file.ts index 25e301e..9f5f725 100644 --- a/src/lib/modules/file.ts +++ b/src/lib/modules/file.ts @@ -64,6 +64,7 @@ const fetchFileInfo = async (fileId: number, masterKey: CryptoKey) => { id: fileId, dataKey, dataKeyVersion: metadata.dekVersion, + contentType: metadata.contentType, contentIv: metadata.contentIv, name: await decryptString(metadata.name, metadata.nameIv, dataKey), }; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index f7aefdf..2fe4b53 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -21,6 +21,7 @@ export interface NewFileParams { mekVersion: number; encDek: string; dekVersion: Date; + contentType: string; encContentIv: string; encName: string; encNameIv: string; @@ -137,6 +138,7 @@ export const registerNewFile = async (params: NewFileParams) => { createdAt: now, userId: params.userId, mekVersion: params.mekVersion, + contentType: params.contentType, encDek: params.encDek, dekVersion: params.dekVersion, encContentIv: params.encContentIv, diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index c2ef676..dbaf944 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -47,6 +47,7 @@ export const file = sqliteTable( 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(), + contentType: text("content_type").notNull(), encContentIv: text("encrypted_content_iv").notNull(), // Base64 encName: ciphertext("encrypted_name").notNull(), }, diff --git a/src/lib/server/schemas/file.ts b/src/lib/server/schemas/file.ts index 13649e7..0df09df 100644 --- a/src/lib/server/schemas/file.ts +++ b/src/lib/server/schemas/file.ts @@ -1,3 +1,4 @@ +import mime from "mime"; import { z } from "zod"; export const fileRenameRequest = z.object({ @@ -12,6 +13,10 @@ export const fileInfoResponse = z.object({ mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.date(), + contentType: z + .string() + .nonempty() + .refine((value) => mime.getExtension(value) !== null), // MIME type contentIv: z.string().base64().nonempty(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), @@ -23,6 +28,10 @@ export const fileUploadRequest = z.object({ mekVersion: z.number().int().positive(), dek: z.string().base64().nonempty(), dekVersion: z.coerce.date(), + contentType: z + .string() + .nonempty() + .refine((value) => mime.getExtension(value) !== null), // MIME type contentIv: z.string().base64().nonempty(), name: z.string().base64().nonempty(), nameIv: z.string().base64().nonempty(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 11fa536..7bf9b72 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -83,6 +83,7 @@ export const getFileInformation = async (userId: number, fileId: number) => { mekVersion: file.mekVersion, encDek: file.encDek, dekVersion: file.dekVersion, + contentType: file.contentType, encContentIv: file.encContentIv, encName: file.encName, }; diff --git a/src/lib/stores/file.ts b/src/lib/stores/file.ts index 78e7691..24997da 100644 --- a/src/lib/stores/file.ts +++ b/src/lib/stores/file.ts @@ -22,6 +22,7 @@ export interface FileInfo { id: number; dataKey: CryptoKey; dataKeyVersion: Date; + contentType: string; contentIv: string; name: string; } diff --git a/src/routes/(fullscreen)/file/[id]/+page.svelte b/src/routes/(fullscreen)/file/[id]/+page.svelte index ea9a886..a6d2f72 100644 --- a/src/routes/(fullscreen)/file/[id]/+page.svelte +++ b/src/routes/(fullscreen)/file/[id]/+page.svelte @@ -1,26 +1,48 @@