Merge remote-tracking branch 'origin/main' into feat/share-folder-browsing

# Conflicts:
#	static/js/views/public/publicShare.js
This commit is contained in:
abnvle
2026-05-10 20:46:29 +02:00
51 changed files with 554 additions and 115 deletions
+9 -13
View File
@@ -1,6 +1,6 @@
use uuid::Uuid;
use crate::domain::services::path_service::StoragePath;
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
// Re-export entity errors from the centralized module
pub use super::entity_errors::{FileError, FileResult};
@@ -100,9 +100,8 @@ impl File {
mime_type: String,
folder_id: Option<String>,
) -> FileResult<Self> {
// Validate file name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FileError::InvalidFileName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FileError::InvalidFileName(format!("{name}: {reason}")));
}
let now = std::time::SystemTime::now()
@@ -137,9 +136,8 @@ impl File {
created_at: u64,
modified_at: u64,
) -> FileResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FileError::InvalidFileName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FileError::InvalidFileName(format!("{name}: {reason}")));
}
// Store the path string for serialization compatibility
@@ -199,9 +197,8 @@ impl File {
owner_id: Option<Uuid>,
etag: String,
) -> FileResult<Self> {
// Validate file name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FileError::InvalidFileName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FileError::InvalidFileName(format!("{name}: {reason}")));
}
// Store the path string for serialization compatibility
@@ -321,9 +318,8 @@ impl File {
/// Creates a new version of the file with updated name
pub fn with_name(&self, new_name: String) -> FileResult<Self> {
// Validate file name
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
return Err(FileError::InvalidFileName(new_name));
if let Err(reason) = validate_storage_name(&new_name) {
return Err(FileError::InvalidFileName(format!("{new_name}: {reason}")));
}
// Update path based on name
+9 -8
View File
@@ -1,6 +1,6 @@
use uuid::Uuid;
use crate::domain::services::path_service::StoragePath;
use crate::domain::services::path_service::{StoragePath, validate_storage_name};
// Re-export entity errors from the centralized module
pub use super::entity_errors::{FolderError, FolderResult};
@@ -71,8 +71,8 @@ impl Folder {
owner_id: Option<Uuid>,
) -> FolderResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FolderError::InvalidFolderName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FolderError::InvalidFolderName(format!("{name}: {reason}")));
}
let now = std::time::SystemTime::now()
@@ -126,8 +126,8 @@ impl Folder {
modified_at: u64,
) -> FolderResult<Self> {
// Validate folder name
if name.is_empty() || name.contains('/') || name.contains('\\') {
return Err(FolderError::InvalidFolderName(name));
if let Err(reason) = validate_storage_name(&name) {
return Err(FolderError::InvalidFolderName(format!("{name}: {reason}")));
}
// Store the path string for serialization compatibility
@@ -208,9 +208,10 @@ impl Folder {
/// Creates a new version of the folder with updated name
pub fn with_name(&self, new_name: String) -> FolderResult<Self> {
// Validate folder name
if new_name.is_empty() || new_name.contains('/') || new_name.contains('\\') {
return Err(FolderError::InvalidFolderName(new_name));
if let Err(reason) = validate_storage_name(&new_name) {
return Err(FolderError::InvalidFolderName(format!(
"{new_name}: {reason}"
)));
}
// Update path based on the name
+11
View File
@@ -11,6 +11,9 @@ pub struct Session {
user_agent: Option<String>,
created_at: DateTime<Utc>,
revoked: bool,
/// Groups all tokens issued from the same original login.
/// Replaying a revoked token from this family triggers full-family revocation.
family_id: Uuid,
}
impl Session {
@@ -20,6 +23,7 @@ impl Session {
ip_address: Option<String>,
user_agent: Option<String>,
expires_in_days: i64,
family_id: Uuid,
) -> Self {
if refresh_token.is_empty() {
panic!("Session refresh_token cannot be empty");
@@ -35,6 +39,7 @@ impl Session {
user_agent,
created_at: now,
revoked: false,
family_id,
}
}
@@ -48,6 +53,7 @@ impl Session {
user_agent: Option<String>,
created_at: DateTime<Utc>,
revoked: bool,
family_id: Uuid,
) -> Self {
Self {
id,
@@ -58,6 +64,7 @@ impl Session {
user_agent,
created_at,
revoked,
family_id,
}
}
@@ -101,4 +108,8 @@ impl Session {
pub fn revoke(&mut self) {
self.revoked = true;
}
pub fn family_id(&self) -> Uuid {
self.family_id
}
}
@@ -52,6 +52,9 @@ pub trait SessionRepository: Send + Sync + 'static {
/// Revokes all sessions for a user
async fn revoke_all_user_sessions(&self, user_id: Uuid) -> SessionRepositoryResult<u64>;
/// Revokes all sessions in a token family (theft response)
async fn revoke_session_family(&self, family_id: Uuid) -> SessionRepositoryResult<u64>;
/// Deletes expired sessions
async fn delete_expired_sessions(&self) -> SessionRepositoryResult<u64>;
}
+20
View File
@@ -6,6 +6,26 @@
use std::path::PathBuf;
/// Validates a single file or folder name component.
///
/// Returns `Err` with a human-readable reason if the name is rejected.
/// Callers should wrap the reason into their own error type.
pub fn validate_storage_name(name: &str) -> Result<(), &'static str> {
if name.is_empty() {
return Err("name cannot be empty");
}
if name.contains('/') || name.contains('\\') {
return Err("name must not contain '/' or '\\'");
}
if name.contains('\0') {
return Err("name must not contain null bytes");
}
if name == "." || name == ".." {
return Err("'.' and '..' are not valid names");
}
Ok(())
}
/// Represents a storage path in the domain (Value Object)
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct StoragePath {