security(P0): fail-closed when auth is enabled but cannot initialize

Previously, if enable_auth=true but the database or auth services failed
to initialize, the server silently started in PUBLIC mode — all API routes
accessible without authentication. This is a critical fail-open bug.

Fixes (fail-closed behavior):
1. main.rs: DB pool creation failure with enable_auth=true now panics
   instead of falling through with db_pool=None
2. di.rs: create_auth_services() failure now propagates the error via ?
   instead of logging and continuing with auth_services=None
3. main.rs: Added defensive assert! that auth_service is Some when
   enable_auth=true, preventing any future refactor from reintroducing
   the silent degradation

The server will now refuse to start if authentication is configured
but cannot be properly initialized.
This commit is contained in:
Diocrafts
2026-02-22 22:40:55 +01:00
parent 6ad23e0acc
commit b48f2867ac
2 changed files with 39 additions and 13 deletions
+21 -10
View File
@@ -459,21 +459,32 @@ impl AppServiceFactory {
// Auth services
if self.config.features.enable_auth {
match crate::infrastructure::auth_factory::create_auth_services(
let services = crate::infrastructure::auth_factory::create_auth_services(
&self.config,
pool.clone(),
Some(apps.folder_service_concrete.clone()),
)
.await
{
Ok(services) => {
tracing::info!("Authentication services initialized successfully");
auth_services = Some(services);
}
Err(e) => {
tracing::error!("Failed to initialize authentication services: {}", e);
}
}
.map_err(|e| {
// SECURITY: fail-closed. If auth is required but the auth
// services cannot be created, propagate the error so the
// server refuses to start — never degrade to public mode.
tracing::error!(
"FATAL: enable_auth=true but auth services failed to initialize: {}",
e
);
DomainError::internal_error(
"AuthInit",
format!(
"Authentication is enabled but auth services failed: {}. \
Refusing to start without authentication.",
e
),
)
})?;
tracing::info!("Authentication services initialized successfully");
auth_services = Some(services);
}
}
+18 -3
View File
@@ -72,8 +72,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Some(Arc::new(pool))
}
Err(e) => {
tracing::error!("Failed to initialize database pool: {}", e);
None
// SECURITY: fail-closed. If auth is required but the database
// is unreachable, the server MUST NOT start in public mode.
panic!(
"FATAL: enable_auth=true but database connection failed: {}. \
Refusing to start without authentication.",
e
);
}
}
} else {
@@ -134,7 +139,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
};
// Apply auth middleware to protected API routes when auth is enabled
if config.features.enable_auth && app_state.auth_service.is_some() {
if config.features.enable_auth {
// SECURITY: if auth is required, auth_service MUST be present at this
// point. The earlier guards in di.rs and main.rs guarantee this, but
// add a defensive check so a future refactor cannot silently degrade.
assert!(
app_state.auth_service.is_some(),
"FATAL: enable_auth=true but auth_service is None. \
This should have been caught during initialization."
);
}
if config.features.enable_auth {
use interfaces::api::handlers::auth_handler::auth_routes;
use oxicloud::interfaces::middleware::auth::auth_middleware;