diff --git a/crates/archivr-server/src/routes.rs b/crates/archivr-server/src/routes.rs index 8ffa1d6..456a9bf 100644 --- a/crates/archivr-server/src/routes.rs +++ b/crates/archivr-server/src/routes.rs @@ -280,9 +280,11 @@ async fn list_tags( async fn create_tag_handler( State(state): State, + auth_user: AuthUser, Path(archive_id): Path, Json(body): Json, ) -> Result<(StatusCode, Json), ApiError> { + auth_user.require_role(ROLE_USER)?; if body.path.trim().is_empty() { return Err(ApiError::bad_request("tag path must not be empty")); } @@ -306,9 +308,11 @@ async fn list_entry_tags( async fn assign_entry_tag_handler( State(state): State, + auth_user: AuthUser, Path((archive_id, entry_uid)): Path<(String, String)>, Json(body): Json, ) -> Result<(StatusCode, Json), ApiError> { + auth_user.require_role(ROLE_USER)?; if body.tag_path.trim().is_empty() { return Err(ApiError::bad_request("tag_path must not be empty")); } @@ -322,8 +326,10 @@ async fn assign_entry_tag_handler( async fn remove_entry_tag_handler( State(state): State, + auth_user: AuthUser, Path((archive_id, entry_uid, tag_uid)): Path<(String, String, String)>, ) -> Result { + auth_user.require_role(ROLE_USER)?; let mounted = mounted_archive(&state, &archive_id)?; let conn = database::open_or_initialize(&mounted.archive_path)?; if archive::remove_entry_tag(&conn, &entry_uid, &tag_uid)? { @@ -357,9 +363,11 @@ struct CreateTokenBody { async fn capture_handler( State(state): State, + auth_user: AuthUser, Path(archive_id): Path, Json(body): Json, ) -> Result, ApiError> { + auth_user.require_role(ROLE_USER)?; if body.locator.trim().is_empty() { return Err(ApiError::bad_request("locator must not be empty")); } @@ -1262,7 +1270,7 @@ mod tests { ) .await .unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); // auth fires before validation } #[tokio::test] @@ -1279,7 +1287,7 @@ mod tests { ) .await .unwrap(); - assert_eq!(response.status(), StatusCode::NOT_FOUND); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); // auth fires before archive lookup } #[tokio::test] @@ -1382,4 +1390,17 @@ mod tests { assert_eq!(response.status(), StatusCode::UNAUTHORIZED); } + #[tokio::test] + async fn capture_returns_401_for_unauthenticated() { + let (test_app, _dir) = make_test_app(); + let response = test_app + .oneshot(Request::builder().method("POST") + .uri("/api/archives/test/captures") + .header("content-type", "application/json") + .body(Body::from(r#"{"locator":"https://example.com"}"#)) + .unwrap()) + .await.unwrap(); + assert_eq!(response.status(), StatusCode::UNAUTHORIZED); + } + }