From 10eba7844471e4703965d1a81d32bb44ede4dff0 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 18 Jan 2025 18:12:40 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=EC=8B=9C=EC=9D=98=20=EC=B2=B4=ED=81=AC=EC=84=AC=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- drizzle/0002_good_talisman.sql | 1 + drizzle/meta/0002_snapshot.json | 1308 +++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/lib/modules/file/upload.ts | 10 +- src/lib/server/db/file.ts | 6 +- src/lib/server/db/schema/file.ts | 1 + src/lib/server/services/file.ts | 20 +- src/routes/api/file/upload/+server.ts | 52 +- 8 files changed, 1379 insertions(+), 26 deletions(-) create mode 100644 drizzle/0002_good_talisman.sql create mode 100644 drizzle/meta/0002_snapshot.json diff --git a/drizzle/0002_good_talisman.sql b/drizzle/0002_good_talisman.sql new file mode 100644 index 0000000..55387df --- /dev/null +++ b/drizzle/0002_good_talisman.sql @@ -0,0 +1 @@ +ALTER TABLE `file` ADD `encrypted_content_hash` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..d9da594 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1308 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "93a0c9c7-dde3-4025-bd59-0fe3a6b70fa0", + "prevId": "5e999e6f-1ec4-40b0-bb10-741ffc6da4af", + "tables": { + "client": { + "name": "client", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "encryption_public_key": { + "name": "encryption_public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "signature_public_key": { + "name": "signature_public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "client_encryption_public_key_unique": { + "name": "client_encryption_public_key_unique", + "columns": [ + "encryption_public_key" + ], + "isUnique": true + }, + "client_signature_public_key_unique": { + "name": "client_signature_public_key_unique", + "columns": [ + "signature_public_key" + ], + "isUnique": true + }, + "client_encryption_public_key_signature_public_key_unique": { + "name": "client_encryption_public_key_signature_public_key_unique", + "columns": [ + "encryption_public_key", + "signature_public_key" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user_client": { + "name": "user_client", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'challenging'" + } + }, + "indexes": {}, + "foreignKeys": { + "user_client_user_id_user_id_fk": { + "name": "user_client_user_id_user_id_fk", + "tableFrom": "user_client", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_client_id_client_id_fk": { + "name": "user_client_client_id_client_id_fk", + "tableFrom": "user_client", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_client_user_id_client_id_pk": { + "columns": [ + "client_id", + "user_id" + ], + "name": "user_client_user_id_client_id_pk" + } + }, + "uniqueConstraints": {} + }, + "user_client_challenge": { + "name": "user_client_challenge", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowed_ip": { + "name": "allowed_ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_client_challenge_answer_unique": { + "name": "user_client_challenge_answer_unique", + "columns": [ + "answer" + ], + "isUnique": true + } + }, + "foreignKeys": { + "user_client_challenge_user_id_user_id_fk": { + "name": "user_client_challenge_user_id_user_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_challenge_client_id_client_id_fk": { + "name": "user_client_challenge_client_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk": { + "name": "user_client_challenge_user_id_client_id_user_client_user_id_client_id_fk", + "tableFrom": "user_client_challenge", + "tableTo": "user_client", + "columnsFrom": [ + "user_id", + "client_id" + ], + "columnsTo": [ + "user_id", + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "directory": { + "name": "directory", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_data_encryption_key": { + "name": "encrypted_data_encryption_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data_encryption_key_version": { + "name": "data_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_name": { + "name": "encrypted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "directory_encrypted_data_encryption_key_unique": { + "name": "directory_encrypted_data_encryption_key_unique", + "columns": [ + "encrypted_data_encryption_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "directory_user_id_user_id_fk": { + "name": "directory_user_id_user_id_fk", + "tableFrom": "directory", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directory_parent_id_directory_id_fk": { + "name": "directory_parent_id_directory_id_fk", + "tableFrom": "directory", + "tableTo": "directory", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "directory_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "directory", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "directory_log": { + "name": "directory_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "directory_id": { + "name": "directory_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "directory_log_directory_id_directory_id_fk": { + "name": "directory_log_directory_id_directory_id_fk", + "tableFrom": "directory_log", + "tableTo": "directory", + "columnsFrom": [ + "directory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file": { + "name": "file", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "parent_id": { + "name": "parent_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_data_encryption_key": { + "name": "encrypted_data_encryption_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data_encryption_key_version": { + "name": "data_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "content_hmac": { + "name": "content_hmac", + "type": "text", + "primaryKey": false, + "notNull": false, + "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_content_hash": { + "name": "encrypted_content_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_name": { + "name": "encrypted_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_created_at": { + "name": "encrypted_created_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "encrypted_last_modified_at": { + "name": "encrypted_last_modified_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "file_path_unique": { + "name": "file_path_unique", + "columns": [ + "path" + ], + "isUnique": true + }, + "file_encrypted_data_encryption_key_unique": { + "name": "file_encrypted_data_encryption_key_unique", + "columns": [ + "encrypted_data_encryption_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "file_parent_id_directory_id_fk": { + "name": "file_parent_id_directory_id_fk", + "tableFrom": "file", + "tableTo": "directory", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_user_id_fk": { + "name": "file_user_id_user_id_fk", + "tableFrom": "file", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "file_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "file_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "file", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "file_log": { + "name": "file_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_name": { + "name": "new_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "file_log_file_id_file_id_fk": { + "name": "file_log_file_id_file_id_fk", + "tableFrom": "file_log", + "tableTo": "file", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "hmac_secret_key": { + "name": "hmac_secret_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "hmac_secret_key_encrypted_key_unique": { + "name": "hmac_secret_key_encrypted_key_unique", + "columns": [ + "encrypted_key" + ], + "isUnique": true + } + }, + "foreignKeys": { + "hmac_secret_key_user_id_user_id_fk": { + "name": "hmac_secret_key_user_id_user_id_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "hmac_secret_key_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "hmac_secret_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "hmac_secret_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "hmac_secret_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "hmac_secret_key_log": { + "name": "hmac_secret_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hmac_secret_key_version": { + "name": "hmac_secret_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "hmac_secret_key_log_user_id_user_id_fk": { + "name": "hmac_secret_key_log_user_id_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_action_by_user_id_fk": { + "name": "hmac_secret_key_log_action_by_user_id_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "user", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk": { + "name": "hmac_secret_key_log_user_id_hmac_secret_key_version_hmac_secret_key_user_id_version_fk", + "tableFrom": "hmac_secret_key_log", + "tableTo": "hmac_secret_key", + "columnsFrom": [ + "user_id", + "hmac_secret_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "client_master_encryption_key": { + "name": "client_master_encryption_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key": { + "name": "encrypted_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "encrypted_key_signature": { + "name": "encrypted_key_signature", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "client_master_encryption_key_user_id_user_id_fk": { + "name": "client_master_encryption_key_user_id_user_id_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_master_encryption_key_client_id_client_id_fk": { + "name": "client_master_encryption_key_client_id_client_id_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk": { + "name": "client_master_encryption_key_user_id_version_master_encryption_key_user_id_version_fk", + "tableFrom": "client_master_encryption_key", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "client_master_encryption_key_user_id_client_id_version_pk": { + "columns": [ + "client_id", + "user_id", + "version" + ], + "name": "client_master_encryption_key_user_id_client_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "master_encryption_key": { + "name": "master_encryption_key", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retired_at": { + "name": "retired_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_user_id_user_id_fk": { + "name": "master_encryption_key_user_id_user_id_fk", + "tableFrom": "master_encryption_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "master_encryption_key_user_id_version_pk": { + "columns": [ + "user_id", + "version" + ], + "name": "master_encryption_key_user_id_version_pk" + } + }, + "uniqueConstraints": {} + }, + "master_encryption_key_log": { + "name": "master_encryption_key_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "master_encryption_key_version": { + "name": "master_encryption_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "action_by": { + "name": "action_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "master_encryption_key_log_user_id_user_id_fk": { + "name": "master_encryption_key_log_user_id_user_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_action_by_client_id_fk": { + "name": "master_encryption_key_log_action_by_client_id_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "client", + "columnsFrom": [ + "action_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk": { + "name": "master_encryption_key_log_user_id_master_encryption_key_version_master_encryption_key_user_id_version_fk", + "tableFrom": "master_encryption_key_log", + "tableTo": "master_encryption_key", + "columnsFrom": [ + "user_id", + "master_encryption_key_version" + ], + "columnsTo": [ + "user_id", + "version" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_by_ip": { + "name": "last_used_by_ip", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_by_user_agent": { + "name": "last_used_by_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "session_user_id_client_id_unique": { + "name": "session_user_id_client_id_unique", + "columns": [ + "user_id", + "client_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "session_client_id_client_id_fk": { + "name": "session_client_id_client_id_fk", + "tableFrom": "session", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "session_upgrade_challenge": { + "name": "session_upgrade_challenge", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "answer": { + "name": "answer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowed_ip": { + "name": "allowed_ip", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_upgrade_challenge_session_id_unique": { + "name": "session_upgrade_challenge_session_id_unique", + "columns": [ + "session_id" + ], + "isUnique": true + }, + "session_upgrade_challenge_answer_unique": { + "name": "session_upgrade_challenge_answer_unique", + "columns": [ + "answer" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_upgrade_challenge_session_id_session_id_fk": { + "name": "session_upgrade_challenge_session_id_session_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "session", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "session_upgrade_challenge_client_id_client_id_fk": { + "name": "session_upgrade_challenge_client_id_client_id_fk", + "tableFrom": "session_upgrade_challenge", + "tableTo": "client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "nickname": { + "name": "nickname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 65be42a..f5d931a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1736720831242, "tag": "0001_blushing_alice", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1737191517463, + "tag": "0002_good_talisman", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/lib/modules/file/upload.ts b/src/lib/modules/file/upload.ts index 2518c7f..a2a9707 100644 --- a/src/lib/modules/file/upload.ts +++ b/src/lib/modules/file/upload.ts @@ -8,6 +8,7 @@ import { wrapDataKey, encryptData, encryptString, + digestMessage, signMessageHmac, } from "$lib/modules/crypto"; import type { @@ -97,6 +98,8 @@ const encryptFile = limitFunction( const dataKeyWrapped = await wrapDataKey(dataKey, masterKey.key); const fileEncrypted = await encryptData(fileBuffer, dataKey); + const fileEncryptedHash = encodeToBase64(await digestMessage(fileEncrypted.ciphertext)); + const nameEncrypted = await encryptString(file.name, dataKey); const createdAtEncrypted = createdAt && (await encryptString(createdAt.getTime().toString(), dataKey)); @@ -110,8 +113,9 @@ const encryptFile = limitFunction( return { dataKeyWrapped, dataKeyVersion, - fileEncrypted, fileType, + fileEncrypted, + fileEncryptedHash, nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, @@ -184,8 +188,9 @@ export const uploadFile = async ( const { dataKeyWrapped, dataKeyVersion, - fileEncrypted, fileType, + fileEncrypted, + fileEncryptedHash, nameEncrypted, createdAtEncrypted, lastModifiedAtEncrypted, @@ -212,6 +217,7 @@ export const uploadFile = async ( } as FileUploadRequest), ); form.set("content", new Blob([fileEncrypted.ciphertext])); + form.set("checksum", fileEncryptedHash); await requestFileUpload(status, form); return true; diff --git a/src/lib/server/db/file.ts b/src/lib/server/db/file.ts index a42235b..6bd0452 100644 --- a/src/lib/server/db/file.ts +++ b/src/lib/server/db/file.ts @@ -26,6 +26,7 @@ export interface NewFileParams { contentHmac: string | null; contentType: string; encContentIv: string; + encContentHash: string; encName: string; encNameIv: string; encCreatedAt: string | null; @@ -198,11 +199,12 @@ export const registerFile = async (params: NewFileParams) => { userId: params.userId, mekVersion: params.mekVersion, hskVersion: params.hskVersion, - contentHmac: params.contentHmac, - contentType: params.contentType, encDek: params.encDek, dekVersion: params.dekVersion, + contentHmac: params.contentHmac, + contentType: params.contentType, encContentIv: params.encContentIv, + encContentHash: params.encContentHash, encName: { ciphertext: params.encName, iv: params.encNameIv }, encCreatedAt: params.encCreatedAt && params.encCreatedAtIv diff --git a/src/lib/server/db/schema/file.ts b/src/lib/server/db/schema/file.ts index 65c5471..ffe303b 100644 --- a/src/lib/server/db/schema/file.ts +++ b/src/lib/server/db/schema/file.ts @@ -60,6 +60,7 @@ export const file = sqliteTable( contentHmac: text("content_hmac"), // Base64 contentType: text("content_type").notNull(), encContentIv: text("encrypted_content_iv").notNull(), // Base64 + encContentHash: text("encrypted_content_hash").notNull(), // Base64 encName: ciphertext("encrypted_name").notNull(), encCreatedAt: ciphertext("encrypted_created_at"), encLastModifiedAt: ciphertext("encrypted_last_modified_at").notNull(), diff --git a/src/lib/server/services/file.ts b/src/lib/server/services/file.ts index 3589bed..cabcc1d 100644 --- a/src/lib/server/services/file.ts +++ b/src/lib/server/services/file.ts @@ -1,4 +1,5 @@ import { error } from "@sveltejs/kit"; +import { createHash } from "crypto"; import { createReadStream, createWriteStream } from "fs"; import { mkdir, stat, unlink } from "fs/promises"; import { dirname } from "path"; @@ -95,8 +96,9 @@ const safeUnlink = async (path: string) => { }; export const uploadFile = async ( - params: Omit, + params: Omit, encContentStream: Readable, + encContentHash: Promise, ) => { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); const oneMinuteLater = new Date(Date.now() + 60 * 1000); @@ -108,16 +110,30 @@ export const uploadFile = async ( await mkdir(dirname(path), { recursive: true }); try { - await pipeline(encContentStream, createWriteStream(path, { flags: "wx", mode: 0o600 })); + const hashStream = createHash("sha256"); + const [_, hash] = await Promise.all([ + pipeline(encContentStream, hashStream, createWriteStream(path, { flags: "wx", mode: 0o600 })), + encContentHash, + ]); + if (hashStream.digest("base64") != hash) { + throw new Error("Invalid checksum"); + } + await registerFile({ ...params, path, + encContentHash: hash, }); } catch (e) { await safeUnlink(path); if (e instanceof IntegrityError && e.message === "Inactive MEK version") { error(400, "Invalid MEK version"); + } else if ( + e instanceof Error && + (e.message === "Invalid request body" || e.message === "Invalid checksum") + ) { + error(400, "Invalid request body"); } throw e; } diff --git a/src/routes/api/file/upload/+server.ts b/src/routes/api/file/upload/+server.ts index 0e8c082..a69df0c 100644 --- a/src/routes/api/file/upload/+server.ts +++ b/src/routes/api/file/upload/+server.ts @@ -67,27 +67,39 @@ export const POST: RequestHandler = async ({ locals, request }) => { let metadata: FileMetadata | null = null; let content: Readable | null = null; + const checksum = new Promise((resolveChecksum, rejectChecksum) => { + bb.on( + "field", + handler(async (fieldname, val) => { + if (fieldname === "metadata") { + if (!metadata) { + // Ignore subsequent metadata fields + metadata = parseFileMetadata(userId, val); + } + } else if (fieldname === "checksum") { + resolveChecksum(val); // Ignore subsequent checksum fields + } else { + error(400, "Invalid request body"); + } + }), + ); + bb.on( + "file", + handler(async (fieldname, file) => { + if (fieldname !== "content") error(400, "Invalid request body"); + if (!metadata || content) error(400, "Invalid request body"); + content = file; - bb.on( - "field", - handler(async (fieldname, val) => { - if (fieldname !== "metadata") error(400, "Invalid request body"); - if (metadata || content) error(400, "Invalid request body"); - metadata = parseFileMetadata(userId, val); - }), - ); - bb.on( - "file", - handler(async (fieldname, file) => { - if (fieldname !== "content") error(400, "Invalid request body"); - if (!metadata || content) error(400, "Invalid request body"); - content = file; - - await uploadFile(metadata, content); - resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); - }), - ); - bb.on("error", (e) => content?.emit("error", e) ?? reject(e)); + await uploadFile(metadata, content, checksum); + resolve(text("File uploaded", { headers: { "Content-Type": "text/plain" } })); + }), + ); + bb.on("finish", () => rejectChecksum(new Error("Invalid request body"))); + bb.on("error", (e) => { + content?.emit("error", e) ?? reject(e); + rejectChecksum(e); + }); + }); request.body!.pipeTo(Writable.toWeb(bb)).catch(() => {}); // busboy will handle the error });