Address path traversal and other security fixes
This commit is contained in:
@@ -85,7 +85,7 @@ pub async fn login<D: UserDao>(
|
||||
HttpResponse::Ok().json(Token { token: &token })
|
||||
} else {
|
||||
error!("Failed login attempt for user: '{}'", creds.username);
|
||||
HttpResponse::NotFound().finish()
|
||||
HttpResponse::Unauthorized().finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_login_reports_404_when_user_does_not_exist() {
|
||||
async fn test_login_reports_401_when_user_does_not_exist() {
|
||||
let mut dao = TestUserDao::new();
|
||||
dao.create_user("user", "password");
|
||||
|
||||
@@ -139,6 +139,6 @@ mod tests {
|
||||
|
||||
let response = login::<TestUserDao>(j, web::Data::new(Mutex::new(dao))).await;
|
||||
|
||||
assert_eq!(response.status(), 404);
|
||||
assert_eq!(response.status(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ impl FromRequest for Claims {
|
||||
)
|
||||
.and_then(|header| {
|
||||
Claims::from_str(header)
|
||||
.with_context(|| format!("Unable to decode token from: {}", header))
|
||||
.with_context(|| "Unable to decode token from Authorization header")
|
||||
})
|
||||
.map_or_else(
|
||||
|e| {
|
||||
|
||||
15
src/files.rs
15
src/files.rs
@@ -932,6 +932,7 @@ pub async fn get_gps_summary(
|
||||
request: HttpRequest,
|
||||
req: Query<FilesRequest>,
|
||||
exif_dao: Data<Mutex<Box<dyn ExifDao>>>,
|
||||
app_state: Data<AppState>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
use crate::data::{GpsPhotoSummary, GpsPhotosResponse};
|
||||
|
||||
@@ -952,17 +953,17 @@ pub async fn get_gps_summary(
|
||||
// The database stores relative paths, so we use the path as-is
|
||||
// Normalize empty path or "/" to return all GPS photos
|
||||
let requested_path = if req.path.is_empty() || req.path == "/" {
|
||||
""
|
||||
String::new()
|
||||
} else {
|
||||
// Just do basic validation to prevent path traversal
|
||||
if req.path.contains("..") {
|
||||
warn!("Path traversal attempt: {}", req.path);
|
||||
// Validate path using the same check as all other endpoints
|
||||
if is_valid_full_path(&app_state.base_path, &req.path, false).is_none() {
|
||||
warn!("Invalid path for GPS summary: {}", req.path);
|
||||
cx.span().set_status(Status::error("Invalid path"));
|
||||
return Ok(HttpResponse::Forbidden().json(serde_json::json!({
|
||||
return Ok(HttpResponse::BadRequest().json(serde_json::json!({
|
||||
"error": "Invalid path"
|
||||
})));
|
||||
}
|
||||
req.path.as_str()
|
||||
req.path.clone()
|
||||
};
|
||||
|
||||
let recursive = req.recursive.unwrap_or(false);
|
||||
@@ -973,7 +974,7 @@ pub async fn get_gps_summary(
|
||||
|
||||
// Query database for all photos with GPS
|
||||
let mut exif_dao_guard = exif_dao.lock().expect("Unable to get ExifDao");
|
||||
match exif_dao_guard.get_all_with_gps(&cx, requested_path, recursive) {
|
||||
match exif_dao_guard.get_all_with_gps(&cx, &requested_path, recursive) {
|
||||
Ok(gps_data) => {
|
||||
let mut photos: Vec<GpsPhotoSummary> = gps_data
|
||||
.into_iter()
|
||||
|
||||
14
src/main.rs
14
src/main.rs
@@ -503,14 +503,10 @@ async fn stream_video(
|
||||
let playlist = &path.path;
|
||||
debug!("Playlist: {}", playlist);
|
||||
|
||||
// Extract video playlist dir to dotenv
|
||||
if !playlist.starts_with(&app_state.video_path)
|
||||
&& is_valid_full_path(&app_state.base_path, playlist, false).is_some()
|
||||
// Only serve files under video_path (HLS playlists) or base_path (source videos)
|
||||
if playlist.starts_with(&app_state.video_path)
|
||||
|| is_valid_full_path(&app_state.base_path, playlist, false).is_some()
|
||||
{
|
||||
span.set_status(Status::error(format!("playlist not valid {}", playlist)));
|
||||
|
||||
HttpResponse::BadRequest().finish()
|
||||
} else {
|
||||
match NamedFile::open(playlist) {
|
||||
Ok(file) => {
|
||||
span.set_status(Status::Ok);
|
||||
@@ -521,6 +517,9 @@ async fn stream_video(
|
||||
HttpResponse::NotFound().finish()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
span.set_status(Status::error(format!("playlist not valid {}", playlist)));
|
||||
HttpResponse::BadRequest().finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1209,6 +1208,7 @@ fn main() -> std::io::Result<()> {
|
||||
.app_data::<Data<Mutex<SqliteKnowledgeDao>>>(Data::new(Mutex::new(
|
||||
SqliteKnowledgeDao::new(),
|
||||
)))
|
||||
.app_data(mp::form::MultipartFormConfig::default().total_limit(1024 * 1024 * 1024)) // 1GB upload limit
|
||||
.app_data(web::JsonConfig::default().error_handler(|err, req| {
|
||||
let detail = err.to_string();
|
||||
log::warn!(
|
||||
|
||||
Reference in New Issue
Block a user