Refactor error handling in IAM and CMS crates
- Introduced `RepositoryError` and `ServiceError` enums in both IAM and CMS domains for better error management. - Updated domain traits and services to return specific error types instead of `anyhow::Result`. - Enhanced session and OAuth repository implementations to handle errors more explicitly. - Refactored session service methods to return `Result<T, ServiceError>` for improved error handling. - Updated HTTP handlers to utilize the new error types. - Modified password hashing functions to run in a blocking context using `tokio::task::spawn_blocking`. - Added tests for new error handling mechanisms and async password functions.
This commit is contained in:
@@ -24,9 +24,20 @@ use tracing;
|
||||
|
||||
use zesdex_utils::write_json_atomic;
|
||||
|
||||
use crate::domain::error::RepositoryError;
|
||||
use crate::domain::repository::SessionRepository;
|
||||
use crate::domain::session::Session;
|
||||
|
||||
/// Validate a session id, rejecting path-traversal patterns.
|
||||
fn validate_id(id: &str) -> Result<(), RepositoryError> {
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
return Err(RepositoryError::InvalidId(format!(
|
||||
"session id '{id}' must not contain path separators"
|
||||
)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Concrete filesystem session repository.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FileSystemSessionRepository;
|
||||
@@ -39,11 +50,15 @@ impl FileSystemSessionRepository {
|
||||
}
|
||||
|
||||
impl SessionRepository for FileSystemSessionRepository {
|
||||
fn list_sessions(&self, base_dir: &Path) -> anyhow::Result<Vec<Session>> {
|
||||
fn list_sessions(&self, base_dir: &Path) -> Result<Vec<Session>, RepositoryError> {
|
||||
let sessions_dir = base_dir.join("sessions");
|
||||
let Ok(entries) = std::fs::read_dir(&sessions_dir) else {
|
||||
tracing::warn!(path = %sessions_dir.display(), "sessions directory not found");
|
||||
return Ok(Vec::new());
|
||||
let entries = match std::fs::read_dir(&sessions_dir) {
|
||||
Ok(e) => e,
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
tracing::warn!(path = %sessions_dir.display(), "sessions directory not found");
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
Err(e) => return Err(RepositoryError::Io(e)),
|
||||
};
|
||||
let mut sessions = Vec::new();
|
||||
for entry in entries.flatten() {
|
||||
@@ -59,38 +74,33 @@ impl SessionRepository for FileSystemSessionRepository {
|
||||
Ok(sessions)
|
||||
}
|
||||
|
||||
fn load_session(&self, base_dir: &Path, id: &str) -> anyhow::Result<Session> {
|
||||
// Directory-traversal prevention.
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
anyhow::bail!("invalid session id '{id}': must not contain path separators");
|
||||
}
|
||||
fn load_session(&self, base_dir: &Path, id: &str) -> Result<Session, RepositoryError> {
|
||||
validate_id(id)?;
|
||||
let path = base_dir.join("sessions").join(id).join("session.json");
|
||||
if !path.exists() {
|
||||
anyhow::bail!("session not found: {id}");
|
||||
return Err(RepositoryError::NotFound(format!("session not found: {id}")));
|
||||
}
|
||||
tracing::debug!(session_id = %id, path = %path.display(), "loading session");
|
||||
let data = std::fs::read_to_string(&path)?;
|
||||
let session: Session = serde_json::from_str(&data)?;
|
||||
let data = std::fs::read_to_string(&path)?; // → RepositoryError
|
||||
let session: Session = serde_json::from_str(&data)?; // → RepositoryError
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
fn save_session(&self, base_dir: &Path, session: &Session) -> anyhow::Result<()> {
|
||||
fn save_session(&self, base_dir: &Path, session: &Session) -> Result<(), RepositoryError> {
|
||||
let dir = session.session_dir(base_dir);
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
std::fs::create_dir_all(&dir)?; // → RepositoryError
|
||||
let path = dir.join("session.json");
|
||||
tracing::debug!(session_id = %session.id, path = %path.display(), "saving session");
|
||||
write_json_atomic(&path, session, None)?;
|
||||
write_json_atomic(&path, session, None).map_err(RepositoryError::from_anyhow)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delete_session(&self, base_dir: &Path, id: &str) -> anyhow::Result<()> {
|
||||
if id.contains('/') || id.contains('\\') || id.contains("..") {
|
||||
anyhow::bail!("invalid session id '{id}': must not contain path separators");
|
||||
}
|
||||
fn delete_session(&self, base_dir: &Path, id: &str) -> Result<(), RepositoryError> {
|
||||
validate_id(id)?;
|
||||
let dir = base_dir.join("sessions").join(id);
|
||||
tracing::debug!(session_id = %id, path = %dir.display(), "deleting session");
|
||||
if dir.exists() {
|
||||
std::fs::remove_dir_all(&dir)?;
|
||||
std::fs::remove_dir_all(&dir)?; // → RepositoryError
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user