feat: Enhance validation and permissions handling across controllers
- Added `ValidatedJson` extractor for automatic JSON validation in `events_controller.rs`, `testimonials_controller.rs`, `mentors_controller.rs`, `gacha_items_controller.rs`, and `hackathon_controller.rs`. - Replaced manual permission checks with `require_permissions!` and `require_auth!` macros in relevant controllers to streamline permission handling. - Introduced `sanitization` utilities in `sanitization.rs` for improved input sanitization. - Added `permission_macros.rs` to encapsulate permission checking logic and reduce boilerplate. - Updated dependencies in `Cargo.toml` to include `serde_json` and `validator`. - Implemented error handling improvements in `notification_service.rs` for better response management.
This commit is contained in:
@@ -22,5 +22,6 @@ tracing.workspace = true
|
||||
base64.workspace = true
|
||||
sha2.workspace = true
|
||||
reqwest.workspace = true
|
||||
regex = "1.11"
|
||||
dotenvy = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||
|
||||
@@ -19,6 +19,7 @@ pub mod query_builder;
|
||||
pub mod errors;
|
||||
pub mod query_list;
|
||||
pub mod response_format;
|
||||
pub mod sanitization;
|
||||
pub mod serde_helpers;
|
||||
pub mod validator;
|
||||
|
||||
@@ -43,6 +44,16 @@ pub use query_builder::{
|
||||
pub use query_list::QueryListBuilder;
|
||||
pub use errors::AppError;
|
||||
pub use response_format::{common_response, success_created_response, success_list_response, success_response, error_response};
|
||||
pub use sanitization::{
|
||||
sanitize_html,
|
||||
sanitize_dangerous_patterns,
|
||||
sanitize_filename,
|
||||
sanitize_user_text,
|
||||
sanitize_email,
|
||||
sanitize_url,
|
||||
normalize_whitespace,
|
||||
contains_path_traversal,
|
||||
};
|
||||
pub use serde_helpers::{
|
||||
deserialize_datetime,
|
||||
option_thing_or_string,
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
//! Input sanitization utilities for security
|
||||
//!
|
||||
//! This module provides utilities to sanitize user input and prevent
|
||||
//! common security vulnerabilities like XSS, HTML injection, etc.
|
||||
|
||||
use regex::Regex;
|
||||
use std::sync::LazyLock;
|
||||
|
||||
// Note: HTML escaping is done via char-by-char mapping for better performance
|
||||
// No regex needed for basic HTML entity escaping
|
||||
|
||||
/// SQL-like injection patterns (even though we use SurrealDB, be safe)
|
||||
static SQL_INJECTION_PATTERNS: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"(?i)(union|select|insert|update|delete|drop|create|alter|exec|script|javascript|onerror|onload)").unwrap()
|
||||
});
|
||||
|
||||
/// Path traversal patterns
|
||||
static PATH_TRAVERSAL_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||
Regex::new(r"\.\.(/|\\)").unwrap()
|
||||
});
|
||||
|
||||
/// Sanitize HTML by escaping special characters
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use imphnen_utils::sanitize_html;
|
||||
///
|
||||
/// let dirty = "<script>alert('xss')</script>";
|
||||
/// let clean = sanitize_html(dirty);
|
||||
/// assert_eq!(clean, "<script>alert('xss')</script>");
|
||||
/// ```
|
||||
pub fn sanitize_html(input: &str) -> String {
|
||||
input
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'<' => "<".to_string(),
|
||||
'>' => ">".to_string(),
|
||||
'"' => """.to_string(),
|
||||
'\'' => "'".to_string(),
|
||||
'&' => "&".to_string(),
|
||||
_ => c.to_string(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Sanitize string to prevent potential injection attacks
|
||||
///
|
||||
/// This is a conservative sanitization that removes potentially dangerous patterns
|
||||
pub fn sanitize_dangerous_patterns(input: &str) -> String {
|
||||
SQL_INJECTION_PATTERNS.replace_all(input, "[FILTERED]").into_owned()
|
||||
}
|
||||
|
||||
/// Check if string contains path traversal attempts
|
||||
pub fn contains_path_traversal(input: &str) -> bool {
|
||||
PATH_TRAVERSAL_REGEX.is_match(input)
|
||||
}
|
||||
|
||||
/// Sanitize a string for safe usage in file names
|
||||
///
|
||||
/// Removes or replaces characters that could cause issues in file systems
|
||||
pub fn sanitize_filename(input: &str) -> String {
|
||||
input
|
||||
.chars()
|
||||
.map(|c| match c {
|
||||
'/' | '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => '_',
|
||||
c if c.is_control() => '_',
|
||||
c => c,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Sanitize user input text (removes HTML and dangerous patterns)
|
||||
///
|
||||
/// Use this for fields like names, descriptions, bios, etc.
|
||||
pub fn sanitize_user_text(input: &str) -> String {
|
||||
let without_html = sanitize_html(input);
|
||||
sanitize_dangerous_patterns(&without_html)
|
||||
}
|
||||
|
||||
/// Trim and normalize whitespace in a string
|
||||
pub fn normalize_whitespace(input: &str) -> String {
|
||||
input
|
||||
.split_whitespace()
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
.trim()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Validate and sanitize email format
|
||||
pub fn sanitize_email(email: &str) -> Option<String> {
|
||||
let trimmed = email.trim().to_lowercase();
|
||||
|
||||
// Basic email validation
|
||||
if trimmed.contains('@') && trimmed.contains('.') {
|
||||
Some(trimmed)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Sanitize URL to prevent javascript: and data: schemes
|
||||
pub fn sanitize_url(url: &str) -> Option<String> {
|
||||
let trimmed = url.trim();
|
||||
|
||||
// Block dangerous URL schemes
|
||||
let lower = trimmed.to_lowercase();
|
||||
if lower.starts_with("javascript:") || lower.starts_with("data:") || lower.starts_with("vbscript:") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Allow http, https, and relative URLs
|
||||
if lower.starts_with("http://") || lower.starts_with("https://") || lower.starts_with("/") {
|
||||
Some(trimmed.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_html() {
|
||||
assert_eq!(
|
||||
sanitize_html("<script>alert('xss')</script>"),
|
||||
"<script>alert('xss')</script>"
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_html("Normal text"),
|
||||
"Normal text"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_dangerous_patterns() {
|
||||
assert!(sanitize_dangerous_patterns("SELECT * FROM users").contains("[FILTERED]"));
|
||||
assert_eq!(
|
||||
sanitize_dangerous_patterns("Normal search query"),
|
||||
"Normal search query"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_path_traversal() {
|
||||
assert!(contains_path_traversal("../../../etc/passwd"));
|
||||
assert!(contains_path_traversal("..\\windows\\system32"));
|
||||
assert!(!contains_path_traversal("normal/path/to/file"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_filename() {
|
||||
assert_eq!(
|
||||
sanitize_filename("file<name>.txt"),
|
||||
"file_name_.txt"
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_filename("normal_file.pdf"),
|
||||
"normal_file.pdf"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_url() {
|
||||
assert_eq!(
|
||||
sanitize_url("https://example.com"),
|
||||
Some("https://example.com".to_string())
|
||||
);
|
||||
assert_eq!(sanitize_url("javascript:alert('xss')"), None);
|
||||
assert_eq!(sanitize_url("data:text/html,<script>alert('xss')</script>"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_normalize_whitespace() {
|
||||
assert_eq!(
|
||||
normalize_whitespace(" multiple spaces "),
|
||||
"multiple spaces"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user