diff --git a/Cargo.lock b/Cargo.lock index 1e67305b..ad63f464 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,6 +771,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dd6caf6059519a65843af8fe2a3ae298b14b80179855aeb4adc2c1934ee619" +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "futures" version = "0.3.31" @@ -1698,6 +1708,7 @@ dependencies = [ "chrono", "dotenv", "flate2", + "fs2", "futures", "hex", "http-body", @@ -3292,6 +3303,28 @@ dependencies = [ "wasite", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.62.2" diff --git a/Cargo.toml b/Cargo.toml index 532759b6..64a13479 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,6 +47,7 @@ hex = "0.4.3" http-body-util = "0.1.3" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls-webpki-roots"] } base64 = "0.22.1" +fs2 = "0.4" [features] default = [] diff --git a/src/application/services/auth_application_service.rs b/src/application/services/auth_application_service.rs index 798a180a..87ea763a 100644 --- a/src/application/services/auth_application_service.rs +++ b/src/application/services/auth_application_service.rs @@ -3,6 +3,7 @@ use std::sync::RwLock; use std::collections::HashMap; use std::sync::Mutex; use std::time::Instant; +use std::path::PathBuf; use crate::domain::entities::user::{User, UserRole}; use crate::domain::entities::session::Session; use crate::application::ports::auth_ports::{UserStoragePort, SessionStoragePort, PasswordHasherPort, TokenServicePort, OidcServicePort, OidcIdClaims}; @@ -36,12 +37,18 @@ struct OidcState { config: Option, } +/// Default quota: 100 GB +const DEFAULT_ADMIN_QUOTA: i64 = 107_374_182_400; +const DEFAULT_USER_QUOTA: i64 = 1_073_741_824; // 1 GB + pub struct AuthApplicationService { user_storage: Arc, session_storage: Arc, password_hasher: Arc, token_service: Arc, folder_service: Option>, + /// Path to the storage directory, used for disk-space–aware quota calculation + storage_path: PathBuf, oidc: RwLock, /// Pending OIDC authorization flows keyed by state token (CSRF + PKCE + nonce) pending_oidc_flows: Mutex>, @@ -55,6 +62,7 @@ impl AuthApplicationService { session_storage: Arc, password_hasher: Arc, token_service: Arc, + storage_path: PathBuf, ) -> Self { Self { user_storage, @@ -62,11 +70,54 @@ impl AuthApplicationService { password_hasher, token_service, folder_service: None, + storage_path, oidc: RwLock::new(OidcState { service: None, config: None }), pending_oidc_flows: Mutex::new(HashMap::new()), pending_oidc_tokens: Mutex::new(HashMap::new()), } } + + /// Returns the default quota for the given role, capped to the available + /// disk space on the filesystem that hosts the storage directory. + fn capped_quota(&self, role: &UserRole) -> i64 { + let base_quota = match role { + UserRole::Admin => DEFAULT_ADMIN_QUOTA, + _ => DEFAULT_USER_QUOTA, + }; + + match Self::available_disk_space(&self.storage_path) { + Some(avail) => { + let avail_i64 = avail as i64; + if avail_i64 < base_quota { + tracing::info!( + "Available disk space ({} bytes) is less than default {} quota ({} bytes) — capping quota", + avail_i64, + if *role == UserRole::Admin { "admin" } else { "user" }, + base_quota, + ); + avail_i64 + } else { + base_quota + } + } + None => { + tracing::warn!("Could not determine available disk space, using default quota"); + base_quota + } + } + } + + /// Query the available space on the filesystem that contains `path`. + fn available_disk_space(path: &std::path::Path) -> Option { + use fs2::available_space; + match available_space(path) { + Ok(space) => Some(space), + Err(e) => { + tracing::warn!("Failed to query disk space for {:?}: {}", path, e); + None + } + } + } /// Configures the folder service, needed to create personal folders pub fn with_folder_service(mut self, folder_service: Arc) -> Self { @@ -200,12 +251,8 @@ impl AuthApplicationService { } }; - // Quota based on role: 100GB for admin, 1GB for regular users - let quota = if role == UserRole::Admin { - 107374182400 // 100GB for admin - } else { - 1024 * 1024 * 1024 // 1GB for regular users - }; + // Quota based on role, capped to available disk space + let quota = self.capped_quota(&role); // Validate password length before hashing if dto.password.len() < 8 { @@ -544,8 +591,8 @@ impl AuthApplicationService { // 3. Create new admin user with the provided credentials but admin role let admin_role = UserRole::Admin; - // Use 100GB for admin quota - let admin_quota = 107374182400; + // Admin quota, capped to available disk space + let admin_quota = self.capped_quota(&admin_role); // Create the new admin user let user = User::new( @@ -937,11 +984,7 @@ impl AuthApplicationService { // Determine role from OIDC groups let role = self.map_oidc_role(&claims.groups, &oidc_config); - let quota = if role == UserRole::Admin { - 107374182400 // 100GB - } else { - 1024 * 1024 * 1024 // 1GB - }; + let quota = self.capped_quota(&role); // Sanitize username (max 32 chars, ensure uniqueness) let mut username = oidc_username.chars().take(32).collect::(); diff --git a/src/infrastructure/auth_factory.rs b/src/infrastructure/auth_factory.rs index cf260858..79fd2f4e 100644 --- a/src/infrastructure/auth_factory.rs +++ b/src/infrastructure/auth_factory.rs @@ -37,6 +37,7 @@ pub async fn create_auth_services( session_repository, password_hasher, token_service.clone(), + config.storage_path.clone(), ); // Configure folder service if available