removed verbose comments

This commit is contained in:
Mercurio 2025-05-28 22:40:43 +02:00
parent 197be28e46
commit 0f9b390ced
6 changed files with 1 additions and 76 deletions

View file

@ -50,20 +50,17 @@ impl File {
encryption_key: Option<String>, encryption_key: Option<String>,
encryption_iv: Option<String>, encryption_iv: Option<String>,
) -> Result<Self, AppError> { ) -> Result<Self, AppError> {
// Validate parent directory if provided
if let Some(parent_id) = dto.parent_id { if let Some(parent_id) = dto.parent_id {
let parent = Self::find_by_id(pool, parent_id).await?; let parent = Self::find_by_id(pool, parent_id).await?;
if parent.file_type != FileType::Directory { if parent.file_type != FileType::Directory {
return Err(AppError::NotADirectory); return Err(AppError::NotADirectory);
} }
// Check if user has access to parent directory
if parent.owner_id != owner_id { if parent.owner_id != owner_id {
// TODO: Check if user has write permission through sharing // TODO: Check if user has write permission through sharing
return Err(AppError::AccessDenied); return Err(AppError::AccessDenied);
} }
} }
// Check for duplicate filename in the same directory
let existing_file = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3", let existing_file = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3",
dto.name, dto.parent_id, owner_id) dto.name, dto.parent_id, owner_id)
.fetch_optional(pool) .fetch_optional(pool)
@ -73,7 +70,6 @@ impl File {
return Err(AppError::FileAlreadyExists); return Err(AppError::FileAlreadyExists);
} }
// Create the file record
let file = sqlx::query_as!(File, let file = sqlx::query_as!(File,
r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at) r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
@ -101,20 +97,17 @@ impl File {
dto: CreateDirectoryDto, dto: CreateDirectoryDto,
owner_id: Uuid, owner_id: Uuid,
) -> Result<Self, AppError> { ) -> Result<Self, AppError> {
// Validate parent directory if provided
if let Some(parent_id) = dto.parent_id { if let Some(parent_id) = dto.parent_id {
let parent = Self::find_by_id(pool, parent_id).await?; let parent = Self::find_by_id(pool, parent_id).await?;
if parent.file_type != FileType::Directory { if parent.file_type != FileType::Directory {
return Err(AppError::NotADirectory); return Err(AppError::NotADirectory);
} }
// Check if user has access to parent directory
if parent.owner_id != owner_id { if parent.owner_id != owner_id {
// TODO: Check if user has write permission through sharing // TODO: Check if user has write permission through sharing
return Err(AppError::AccessDenied); return Err(AppError::AccessDenied);
} }
} }
// Check for duplicate directory name in the same parent
let existing_dir = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3 AND file_type = 'directory'", let existing_dir = sqlx::query!("SELECT id FROM files WHERE name = $1 AND parent_id IS NOT DISTINCT FROM $2 AND owner_id = $3 AND file_type = 'directory'",
dto.name, dto.parent_id, owner_id) dto.name, dto.parent_id, owner_id)
.fetch_optional(pool) .fetch_optional(pool)
@ -124,7 +117,6 @@ impl File {
return Err(AppError::DirectoryAlreadyExists); return Err(AppError::DirectoryAlreadyExists);
} }
// Create the directory record
let directory = sqlx::query_as!(File, let directory = sqlx::query_as!(File,
r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at) r#"INSERT INTO files (id, name, file_type, mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
@ -165,17 +157,13 @@ impl File {
directory_id: Option<Uuid>, directory_id: Option<Uuid>,
user_id: Uuid, user_id: Uuid,
) -> Result<Vec<Self>, AppError> { ) -> Result<Vec<Self>, AppError> {
// If directory_id is None, list files at root level for the user
let files = if let Some(dir_id) = directory_id { let files = if let Some(dir_id) = directory_id {
// Verify directory exists and user has access
let directory = Self::find_by_id(pool, dir_id).await?; let directory = Self::find_by_id(pool, dir_id).await?;
if directory.file_type != FileType::Directory { if directory.file_type != FileType::Directory {
return Err(AppError::NotADirectory); return Err(AppError::NotADirectory);
} }
// Check if user has access to this directory
if directory.owner_id != user_id { if directory.owner_id != user_id {
// TODO: Check if user has read permission through sharing
return Err(AppError::AccessDenied); return Err(AppError::AccessDenied);
} }
@ -187,7 +175,6 @@ impl File {
.fetch_all(pool) .fetch_all(pool)
.await? .await?
} else { } else {
// List root level files for the user
sqlx::query_as!(File, sqlx::query_as!(File,
r#"SELECT id, name, file_type as "file_type: FileType", mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at r#"SELECT id, name, file_type as "file_type: FileType", mime_type, size, owner_id, parent_id, encryption_key, encryption_iv, created_at, updated_at
FROM files WHERE parent_id IS NULL AND owner_id = $1 ORDER BY file_type, name"#, FROM files WHERE parent_id IS NULL AND owner_id = $1 ORDER BY file_type, name"#,
@ -205,27 +192,20 @@ impl File {
id: Uuid, id: Uuid,
user_id: Uuid, user_id: Uuid,
) -> Result<(), AppError> { ) -> Result<(), AppError> {
// Find the file/directory
let file = Self::find_by_id(pool, id).await?; let file = Self::find_by_id(pool, id).await?;
// Check if user has permission to delete
if file.owner_id != user_id { if file.owner_id != user_id {
// TODO: Check if user has write permission through sharing
return Err(AppError::AccessDenied); return Err(AppError::AccessDenied);
} }
// If it's a directory, recursively delete all contents
if file.file_type == FileType::Directory { if file.file_type == FileType::Directory {
// Get all files in this directory
let files_in_dir = Self::list_directory(pool, Some(id), user_id).await?; let files_in_dir = Self::list_directory(pool, Some(id), user_id).await?;
// Recursively delete each file/subdirectory
for file in files_in_dir { for file in files_in_dir {
Self::delete(pool, file.id, user_id).await?; Self::delete(pool, file.id, user_id).await?;
} }
} }
// Delete the file/directory record
sqlx::query!("DELETE FROM files WHERE id = $1", id) sqlx::query!("DELETE FROM files WHERE id = $1", id)
.execute(pool) .execute(pool)
.await?; .await?;
@ -236,11 +216,9 @@ impl File {
pub async fn get_file_path(pool: &PgPool, id: Uuid) -> Result<String, AppError> { pub async fn get_file_path(pool: &PgPool, id: Uuid) -> Result<String, AppError> {
let file = Self::find_by_id(pool, id).await?; let file = Self::find_by_id(pool, id).await?;
// Build the path by traversing parent directories
let mut path_parts = vec![file.name.clone()]; let mut path_parts = vec![file.name.clone()];
let mut current_parent_id = file.parent_id; let mut current_parent_id = file.parent_id;
// Prevent infinite loops by limiting depth
let mut depth = 0; let mut depth = 0;
const MAX_DEPTH: usize = 100; const MAX_DEPTH: usize = 100;
@ -255,10 +233,7 @@ impl File {
depth += 1; depth += 1;
} }
// Reverse to get the correct order (root -> leaf)
path_parts.reverse(); path_parts.reverse();
// Join with path separator
Ok(path_parts.join("/")) Ok(path_parts.join("/"))
} }
} }

View file

@ -92,7 +92,6 @@ async fn upload_file(
return Err(AppError::StorageQuotaExceeded("Storage quota exceeded".to_string())); return Err(AppError::StorageQuotaExceeded("Storage quota exceeded".to_string()));
} }
// Create storage service
let storage_service = StorageService::new(&config.storage_path); let storage_service = StorageService::new(&config.storage_path);
// Create file record in database // Create file record in database
@ -122,29 +121,22 @@ async fn download_file(
Extension(encryption_service): Extension<EncryptionService>, Extension(encryption_service): Extension<EncryptionService>,
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
// Get file metadata
let file = FileModel::find_by_id(&pool, id).await?; let file = FileModel::find_by_id(&pool, id).await?;
// Check if user has access to this file
if file.owner_id != auth_user.id { if file.owner_id != auth_user.id {
return Err(AppError::AccessDenied("You don't have access to this file".to_string())); return Err(AppError::AccessDenied("You don't have access to this file".to_string()));
} }
// Check if it's a file (not a directory)
if file.file_type != FileType::File { if file.file_type != FileType::File {
return Err(AppError::InvalidInput("Cannot download a directory".to_string())); return Err(AppError::InvalidInput("Cannot download a directory".to_string()));
} }
// Create storage service
let storage_service = StorageService::new(&config.storage_path); let storage_service = StorageService::new(&config.storage_path);
// Read encrypted file from disk
let encrypted_data = storage_service.read_file(auth_user.id, file.id).await?; let encrypted_data = storage_service.read_file(auth_user.id, file.id).await?;
// Decrypt file
let decrypted_data = encryption_service.decrypt(&encrypted_data)?; let decrypted_data = encryption_service.decrypt(&encrypted_data)?;
// Set up response headers
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(
axum::http::header::CONTENT_TYPE, axum::http::header::CONTENT_TYPE,
@ -155,7 +147,6 @@ async fn download_file(
format!("attachment; filename=\"{}\"", file.name).parse().unwrap(), format!("attachment; filename=\"{}\"", file.name).parse().unwrap(),
); );
// Create a stream from the decrypted data
let stream = tokio_util::io::ReaderStream::new(std::io::Cursor::new(decrypted_data)); let stream = tokio_util::io::ReaderStream::new(std::io::Cursor::new(decrypted_data));
let body = StreamBody::new(stream); let body = StreamBody::new(stream);
@ -177,27 +168,16 @@ async fn delete_file(
Extension(config): Extension<Arc<Config>>, Extension(config): Extension<Arc<Config>>,
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
) -> Result<StatusCode, AppError> { ) -> Result<StatusCode, AppError> {
// Get file metadata
let file = FileModel::find_by_id(&pool, id).await?; let file = FileModel::find_by_id(&pool, id).await?;
// Check if user has access to this file
if file.owner_id != auth_user.id { if file.owner_id != auth_user.id {
return Err(AppError::AccessDenied("You don't have access to this file".to_string())); return Err(AppError::AccessDenied("You don't have access to this file".to_string()));
} }
// Create storage service
let storage_service = StorageService::new(&config.storage_path); let storage_service = StorageService::new(&config.storage_path);
// Delete file or directory
if file.file_type == FileType::Directory { if file.file_type == FileType::Directory {
// For directories, recursively delete all contents
FileModel::delete_directory_recursive(&pool, id, auth_user.id, &storage_service).await?; FileModel::delete_directory_recursive(&pool, id, auth_user.id, &storage_service).await?;
} else { } else {
// For files, delete the file from storage and update user quota
storage_service.delete_file(auth_user.id, file.id).await?; storage_service.delete_file(auth_user.id, file.id).await?;
FileModel::delete(&pool, id).await?; FileModel::delete(&pool, id).await?;
// Update user storage used
crate::models::user::User::update_storage_used(&pool, auth_user.id, -file.size).await?; crate::models::user::User::update_storage_used(&pool, auth_user.id, -file.size).await?;
} }

View file

@ -24,13 +24,8 @@ async fn list_shares(
auth_user: AuthUser, auth_user: AuthUser,
Extension(pool): Extension<PgPool>, Extension(pool): Extension<PgPool>,
) -> Result<Json<Vec<ShareResponse>>, AppError> { ) -> Result<Json<Vec<ShareResponse>>, AppError> {
// Get shares owned by the user
let owned_shares = Share::list_by_owner(&pool, auth_user.id).await?; let owned_shares = Share::list_by_owner(&pool, auth_user.id).await?;
// Get shares shared with the user
let received_shares = Share::list_by_recipient(&pool, auth_user.id).await?; let received_shares = Share::list_by_recipient(&pool, auth_user.id).await?;
// Combine and get full details for each share
let mut share_responses = Vec::new(); let mut share_responses = Vec::new();
for share in owned_shares { for share in owned_shares {
@ -61,8 +56,6 @@ async fn get_share(
Path(id): Path<Uuid>, Path(id): Path<Uuid>,
) -> Result<Json<ShareResponse>, AppError> { ) -> Result<Json<ShareResponse>, AppError> {
let share = Share::find_by_id(&pool, id).await?; let share = Share::find_by_id(&pool, id).await?;
// Check if user has access to this share
if share.owner_id != auth_user.id && share.recipient_id != Some(auth_user.id) { if share.owner_id != auth_user.id && share.recipient_id != Some(auth_user.id) {
return Err(AppError::AccessDenied("You don't have access to this share".to_string())); return Err(AppError::AccessDenied("You don't have access to this share".to_string()));
} }
@ -85,8 +78,6 @@ async fn access_shared_file(
Path(access_key): Path<String>, Path(access_key): Path<String>,
) -> Result<Json<ShareResponse>, AppError> { ) -> Result<Json<ShareResponse>, AppError> {
let share = Share::find_by_access_key(&pool, &access_key).await?; let share = Share::find_by_access_key(&pool, &access_key).await?;
// Check if share is valid (not expired)
if let Some(expires_at) = share.expires_at { if let Some(expires_at) = share.expires_at {
if expires_at < time::OffsetDateTime::now_utc() { if expires_at < time::OffsetDateTime::now_utc() {
return Err(AppError::AccessDenied("This share has expired".to_string())); return Err(AppError::AccessDenied("This share has expired".to_string()));

View file

@ -36,7 +36,6 @@ async fn update_password(
Extension(pool): Extension<PgPool>, Extension(pool): Extension<PgPool>,
Json(update_dto): Json<UpdatePasswordDto>, Json(update_dto): Json<UpdatePasswordDto>,
) -> Result<StatusCode, AppError> { ) -> Result<StatusCode, AppError> {
// Verify current password
let user = User::find_by_id(&pool, auth_user.id).await?; let user = User::find_by_id(&pool, auth_user.id).await?;
let is_valid = crate::utils::password::verify_password( let is_valid = crate::utils::password::verify_password(
&update_dto.current_password, &update_dto.current_password,
@ -47,10 +46,8 @@ async fn update_password(
return Err(AppError::AuthenticationError("Current password is incorrect".to_string())); return Err(AppError::AuthenticationError("Current password is incorrect".to_string()));
} }
// Hash new password
let new_password_hash = crate::utils::password::hash_password(&update_dto.new_password)?; let new_password_hash = crate::utils::password::hash_password(&update_dto.new_password)?;
// Update password in database
sqlx::query!( sqlx::query!(
r#" r#"
UPDATE users UPDATE users

View file

@ -10,12 +10,9 @@ pub struct EncryptionService {
impl EncryptionService { impl EncryptionService {
pub fn new(master_key: &str) -> Self { pub fn new(master_key: &str) -> Self {
// Decode the base64 master key
let key_bytes = general_purpose::STANDARD let key_bytes = general_purpose::STANDARD
.decode(master_key) .decode(master_key)
.expect("Invalid master key format"); .expect("Invalid master key format");
// Create the cipher
let cipher = Aes256Gcm::new_from_slice(&key_bytes) let cipher = Aes256Gcm::new_from_slice(&key_bytes)
.expect("Invalid key length"); .expect("Invalid key length");
@ -23,17 +20,12 @@ impl EncryptionService {
} }
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> { pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> {
// Generate a random 12-byte nonce
let mut nonce_bytes = [0u8; 12]; let mut nonce_bytes = [0u8; 12];
OsRng.fill_bytes(&mut nonce_bytes); OsRng.fill_bytes(&mut nonce_bytes);
let nonce = Nonce::from_slice(&nonce_bytes); let nonce = Nonce::from_slice(&nonce_bytes);
// Encrypt the data
let ciphertext = self.cipher let ciphertext = self.cipher
.encrypt(nonce, data) .encrypt(nonce, data)
.map_err(|e| AppError::EncryptionError(e.to_string()))?; .map_err(|e| AppError::EncryptionError(e.to_string()))?;
// Combine nonce and ciphertext
let mut result = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); let mut result = Vec::with_capacity(nonce_bytes.len() + ciphertext.len());
result.extend_from_slice(&nonce_bytes); result.extend_from_slice(&nonce_bytes);
result.extend_from_slice(&ciphertext); result.extend_from_slice(&ciphertext);
@ -42,15 +34,12 @@ impl EncryptionService {
} }
pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> { pub fn decrypt(&self, data: &[u8]) -> Result<Vec<u8>, AppError> {
// Extract nonce and ciphertext
if data.len() < 12 { if data.len() < 12 {
return Err(AppError::EncryptionError("Invalid encrypted data format".to_string())); return Err(AppError::EncryptionError("Invalid encrypted data format".to_string()));
} }
let nonce = Nonce::from_slice(&data[..12]); let nonce = Nonce::from_slice(&data[..12]);
let ciphertext = &data[12..]; let ciphertext = &data[12..];
// Decrypt the data
let plaintext = self.cipher let plaintext = self.cipher
.decrypt(nonce, ciphertext) .decrypt(nonce, ciphertext)
.map_err(|e| AppError::EncryptionError(e.to_string()))?; .map_err(|e| AppError::EncryptionError(e.to_string()))?;
@ -59,11 +48,9 @@ impl EncryptionService {
} }
pub fn generate_master_key() -> String { pub fn generate_master_key() -> String {
// Generate a random 32-byte key
let mut key = [0u8; 32]; let mut key = [0u8; 32];
OsRng.fill_bytes(&mut key); OsRng.fill_bytes(&mut key);
// Encode as base64
general_purpose::STANDARD.encode(key) general_purpose::STANDARD.encode(key)
} }
} }

View file

@ -18,13 +18,12 @@ impl StorageService {
pub async fn save_file(&self, user_id: Uuid, file_id: Uuid, data: &[u8]) -> Result<(), AppError> { pub async fn save_file(&self, user_id: Uuid, file_id: Uuid, data: &[u8]) -> Result<(), AppError> {
let file_path = self.get_file_path(user_id, file_id); let file_path = self.get_file_path(user_id, file_id);
// Ensure the directory exists
if let Some(parent) = file_path.parent() { if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent).await fs::create_dir_all(parent).await
.map_err(|e| AppError::IoError(e.to_string()))?; .map_err(|e| AppError::IoError(e.to_string()))?;
} }
// Write the file
fs::write(&file_path, data).await fs::write(&file_path, data).await
.map_err(|e| AppError::IoError(e.to_string()))?; .map_err(|e| AppError::IoError(e.to_string()))?;
@ -34,7 +33,6 @@ impl StorageService {
pub async fn read_file(&self, user_id: Uuid, file_id: Uuid) -> Result<Vec<u8>, AppError> { pub async fn read_file(&self, user_id: Uuid, file_id: Uuid) -> Result<Vec<u8>, AppError> {
let file_path = self.get_file_path(user_id, file_id); let file_path = self.get_file_path(user_id, file_id);
// Read the file
let data = fs::read(&file_path).await let data = fs::read(&file_path).await
.map_err(|e| AppError::IoError(e.to_string()))?; .map_err(|e| AppError::IoError(e.to_string()))?;
@ -44,12 +42,10 @@ impl StorageService {
pub async fn delete_file(&self, user_id: Uuid, file_id: Uuid) -> Result<(), AppError> { pub async fn delete_file(&self, user_id: Uuid, file_id: Uuid) -> Result<(), AppError> {
let file_path = self.get_file_path(user_id, file_id); let file_path = self.get_file_path(user_id, file_id);
// Check if file exists
if !file_path.exists() { if !file_path.exists() {
return Ok(()); return Ok(());
} }
// Delete the file
fs::remove_file(&file_path).await fs::remove_file(&file_path).await
.map_err(|e| AppError::IoError(e.to_string()))?; .map_err(|e| AppError::IoError(e.to_string()))?;
@ -59,7 +55,6 @@ impl StorageService {
pub async fn create_user_directory(&self, user_id: Uuid) -> Result<(), AppError> { pub async fn create_user_directory(&self, user_id: Uuid) -> Result<(), AppError> {
let dir_path = self.get_user_directory(user_id); let dir_path = self.get_user_directory(user_id);
// Create the directory if it doesn't exist
if !dir_path.exists() { if !dir_path.exists() {
fs::create_dir_all(&dir_path).await fs::create_dir_all(&dir_path).await
.map_err(|e| AppError::IoError(e.to_string()))?; .map_err(|e| AppError::IoError(e.to_string()))?;