fix(security): add server-side URL scheme validation for roaster homepage
Reject non-http(s) URL schemes (javascript:, data:, etc.) in both NewRoaster::normalize() and new UpdateRoaster::normalize(). The HTML input type="url" provides browser-side validation, but the API path had no server-side check.
This commit is contained in:
parent
1f2ec0fc14
commit
830d424297
2 changed files with 27 additions and 1 deletions
|
|
@ -96,6 +96,7 @@ pub(crate) async fn update_roaster(
|
|||
Path(id): Path<RoasterId>,
|
||||
Json(payload): Json<UpdateRoaster>,
|
||||
) -> Result<Json<Roaster>, ApiError> {
|
||||
let payload = payload.normalize();
|
||||
let has_changes = payload.name.is_some()
|
||||
|| payload.country.is_some()
|
||||
|| payload.city.is_some()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ impl NewRoaster {
|
|||
self.name = self.name.trim().to_string();
|
||||
self.country = self.country.trim().to_string();
|
||||
self.city = normalize_optional_field(self.city);
|
||||
self.homepage = normalize_optional_field(self.homepage);
|
||||
self.homepage =
|
||||
normalize_optional_field(self.homepage).filter(|url| is_valid_url_scheme(url));
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +54,13 @@ fn normalize_optional_field(value: Option<String>) -> Option<String> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if the URL starts with `http://` or `https://`.
|
||||
/// Rejects `javascript:`, `data:`, and other potentially dangerous schemes.
|
||||
fn is_valid_url_scheme(url: &str) -> bool {
|
||||
let lower = url.trim().to_lowercase();
|
||||
lower.starts_with("http://") || lower.starts_with("https://")
|
||||
}
|
||||
|
||||
impl Roaster {
|
||||
pub fn to_timeline_event(&self) -> NewTimelineEvent {
|
||||
let mut details = vec![TimelineEventDetail {
|
||||
|
|
@ -88,6 +96,23 @@ pub struct UpdateRoaster {
|
|||
pub homepage: Option<String>,
|
||||
}
|
||||
|
||||
impl UpdateRoaster {
|
||||
pub fn normalize(mut self) -> Self {
|
||||
self.homepage = self
|
||||
.homepage
|
||||
.and_then(|h| {
|
||||
let trimmed = h.trim().to_string();
|
||||
if trimmed.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(trimmed)
|
||||
}
|
||||
})
|
||||
.filter(|url| is_valid_url_scheme(url));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub enum RoasterSortKey {
|
||||
CreatedAt,
|
||||
|
|
|
|||
Loading…
Reference in a new issue