Harden community profile identity rendering
This commit is contained in:
@@ -1015,19 +1015,12 @@ impl AdminService {
|
||||
current_workspace_id: Option<&WorkspaceId>,
|
||||
payload: UpdateProfilePayload,
|
||||
) -> Result<SessionResponse, ApiError> {
|
||||
let display_name = payload.display_name.trim();
|
||||
let email = payload.email.trim().to_ascii_lowercase();
|
||||
|
||||
if display_name.is_empty() {
|
||||
return Err(ApiError::validation("display name is required"));
|
||||
}
|
||||
if email.is_empty() || !email.contains('@') {
|
||||
return Err(ApiError::validation("a valid email address is required"));
|
||||
}
|
||||
let display_name = validate_profile_display_name(&payload.display_name)?;
|
||||
let email = validate_profile_email(&payload.email)?;
|
||||
|
||||
let user = self
|
||||
.registry
|
||||
.update_user_profile(user_id, &email, display_name)
|
||||
.update_user_profile(user_id, &email, &display_name)
|
||||
.await?;
|
||||
let memberships = self.registry.list_workspaces_for_user(user_id).await?;
|
||||
|
||||
@@ -3717,6 +3710,41 @@ fn normalize_base_url(value: &str) -> String {
|
||||
value.trim().trim_end_matches('/').to_owned()
|
||||
}
|
||||
|
||||
fn validate_profile_display_name(value: &str) -> Result<String, ApiError> {
|
||||
let display_name = value.trim();
|
||||
if display_name.is_empty() {
|
||||
return Err(ApiError::validation("display name is required"));
|
||||
}
|
||||
if display_name.chars().count() > 80 {
|
||||
return Err(ApiError::validation(
|
||||
"display name must be at most 80 characters",
|
||||
));
|
||||
}
|
||||
if display_name
|
||||
.chars()
|
||||
.any(|character| character.is_control() || matches!(character, '<' | '>'))
|
||||
{
|
||||
return Err(ApiError::validation(
|
||||
"display name contains unsupported characters",
|
||||
));
|
||||
}
|
||||
Ok(display_name.to_owned())
|
||||
}
|
||||
|
||||
fn validate_profile_email(value: &str) -> Result<String, ApiError> {
|
||||
let email = value.trim().to_ascii_lowercase();
|
||||
if email.is_empty()
|
||||
|| email.len() > 254
|
||||
|| !email.contains('@')
|
||||
|| email.chars().any(|character| {
|
||||
character.is_whitespace() || character.is_control() || matches!(character, '<' | '>')
|
||||
})
|
||||
{
|
||||
return Err(ApiError::validation("a valid email address is required"));
|
||||
}
|
||||
Ok(email)
|
||||
}
|
||||
|
||||
fn validate_secret_payload(payload: &SecretPayload) -> Result<(), ApiError> {
|
||||
if payload.name.trim().is_empty() {
|
||||
return Err(ApiError::validation("secret name must not be empty"));
|
||||
@@ -4291,7 +4319,9 @@ mod tests {
|
||||
|
||||
use crank_core::{ExecutionConfig, HttpMethod, ResponseCachePolicy, RestTarget, Target};
|
||||
|
||||
use super::validate_response_cache_policy;
|
||||
use super::{
|
||||
validate_profile_display_name, validate_profile_email, validate_response_cache_policy,
|
||||
};
|
||||
|
||||
fn cacheable_execution_config() -> ExecutionConfig {
|
||||
ExecutionConfig {
|
||||
@@ -4339,6 +4369,26 @@ mod tests {
|
||||
"response cache is supported only for REST GET operations"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validates_profile_identity_fields() {
|
||||
assert_eq!(
|
||||
validate_profile_display_name(" Updated Owner ").unwrap(),
|
||||
"Updated Owner"
|
||||
);
|
||||
assert_eq!(
|
||||
validate_profile_email(" OWNER@CRANK.LOCAL ").unwrap(),
|
||||
"owner@crank.local"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_profile_identity_page_text() {
|
||||
assert!(validate_profile_display_name("<html>Crank</html>").is_err());
|
||||
assert!(validate_profile_display_name("Crank\nOperations\nSave profile").is_err());
|
||||
assert!(validate_profile_display_name(&"x".repeat(81)).is_err());
|
||||
assert!(validate_profile_email("owner <html>@crank.local").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
fn enrich_operation_summary(
|
||||
|
||||
Reference in New Issue
Block a user