Merge remote-tracking branch 'origin/main' into feat/share-folder-browsing
# Conflicts: # static/js/views/public/publicShare.js
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,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>;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user