Increase maximum length of display_name to 200 chars and validate it on profile import
This commit is contained in:
parent
690edddbc1
commit
1936219b3d
9 changed files with 91 additions and 13 deletions
|
@ -0,0 +1 @@
|
||||||
|
ALTER TABLE actor_profile ALTER COLUMN display_name TYPE VARCHAR(200);
|
|
@ -1,7 +1,7 @@
|
||||||
CREATE TABLE actor_profile (
|
CREATE TABLE actor_profile (
|
||||||
id UUID PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
username VARCHAR(100) NOT NULL,
|
username VARCHAR(100) NOT NULL,
|
||||||
display_name VARCHAR(100),
|
display_name VARCHAR(200),
|
||||||
acct VARCHAR(200) UNIQUE NOT NULL,
|
acct VARCHAR(200) UNIQUE NOT NULL,
|
||||||
bio TEXT,
|
bio TEXT,
|
||||||
bio_source TEXT,
|
bio_source TEXT,
|
||||||
|
|
|
@ -135,10 +135,25 @@ pub enum ImportError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
FetchError(#[from] FetchError),
|
FetchError(#[from] FetchError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
ValidationError(#[from] ValidationError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
DatabaseError(#[from] DatabaseError),
|
DatabaseError(#[from] DatabaseError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ImportError> for HttpError {
|
||||||
|
fn from(error: ImportError) -> Self {
|
||||||
|
match error {
|
||||||
|
ImportError::FetchError(error) => {
|
||||||
|
HttpError::ValidationError(error.to_string())
|
||||||
|
},
|
||||||
|
ImportError::ValidationError(error) => error.into(),
|
||||||
|
ImportError::DatabaseError(error) => error.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_or_fetch_profile_by_actor_id(
|
async fn get_or_fetch_profile_by_actor_id(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
instance: &Instance,
|
instance: &Instance,
|
||||||
|
@ -187,6 +202,7 @@ pub async fn import_profile_by_actor_address(
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
log::info!("fetched profile {}", profile_data.acct);
|
log::info!("fetched profile {}", profile_data.acct);
|
||||||
|
profile_data.clean()?;
|
||||||
let profile = create_profile(db_client, &profile_data).await?;
|
let profile = create_profile(db_client, &profile_data).await?;
|
||||||
Ok(profile)
|
Ok(profile)
|
||||||
}
|
}
|
||||||
|
@ -328,14 +344,14 @@ pub async fn process_note(
|
||||||
&actor_address,
|
&actor_address,
|
||||||
).await {
|
).await {
|
||||||
Ok(profile) => profile,
|
Ok(profile) => profile,
|
||||||
Err(ImportError::DatabaseError(error)) => {
|
|
||||||
return Err(error.into());
|
|
||||||
},
|
|
||||||
Err(ImportError::FetchError(error)) => {
|
Err(ImportError::FetchError(error)) => {
|
||||||
// Ignore mention if fetcher fails
|
// Ignore mention if fetcher fails
|
||||||
log::warn!("{}", error);
|
log::warn!("{}", error);
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
|
Err(other_error) => {
|
||||||
|
return Err(other_error.into());
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(other_error) => return Err(other_error.into()),
|
Err(other_error) => return Err(other_error.into()),
|
||||||
|
|
|
@ -58,7 +58,7 @@ async fn create_status(
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let instance = config.instance();
|
let instance = config.instance();
|
||||||
let mut post_data = PostCreateData::from(data.into_inner());
|
let mut post_data = PostCreateData::from(data.into_inner());
|
||||||
post_data.validate()?;
|
post_data.clean()?;
|
||||||
// Mentions
|
// Mentions
|
||||||
let mention_map = find_mentioned_profiles(
|
let mention_map = find_mentioned_profiles(
|
||||||
db_client,
|
db_client,
|
||||||
|
|
|
@ -205,7 +205,7 @@ pub struct PostCreateData {
|
||||||
|
|
||||||
impl PostCreateData {
|
impl PostCreateData {
|
||||||
/// Validate and clean post data.
|
/// Validate and clean post data.
|
||||||
pub fn validate(&mut self) -> Result<(), ValidationError> {
|
pub fn clean(&mut self) -> Result<(), ValidationError> {
|
||||||
let content_safe = clean_html(&self.content);
|
let content_safe = clean_html(&self.content);
|
||||||
let content_trimmed = content_safe.trim();
|
let content_trimmed = content_safe.trim();
|
||||||
if content_trimmed.is_empty() {
|
if content_trimmed.is_empty() {
|
||||||
|
@ -233,7 +233,7 @@ mod tests {
|
||||||
object_id: None,
|
object_id: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
assert_eq!(post_data_1.validate().is_ok(), false);
|
assert_eq!(post_data_1.clean().is_ok(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -249,7 +249,7 @@ mod tests {
|
||||||
object_id: None,
|
object_id: None,
|
||||||
created_at: None,
|
created_at: None,
|
||||||
};
|
};
|
||||||
assert_eq!(post_data_2.validate().is_ok(), true);
|
assert_eq!(post_data_2.clean().is_ok(), true);
|
||||||
assert_eq!(post_data_2.content.as_str(), "test");
|
assert_eq!(post_data_2.content.as_str(), "test");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod queries;
|
pub mod queries;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
|
pub mod validators;
|
||||||
|
|
|
@ -11,6 +11,10 @@ use uuid::Uuid;
|
||||||
use crate::activitypub::views::get_actor_url;
|
use crate::activitypub::views::get_actor_url;
|
||||||
use crate::errors::{ConversionError, ValidationError};
|
use crate::errors::{ConversionError, ValidationError};
|
||||||
use crate::utils::html::clean_html;
|
use crate::utils::html::clean_html;
|
||||||
|
use super::validators::{
|
||||||
|
validate_username,
|
||||||
|
validate_display_name,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct ExtraField {
|
pub struct ExtraField {
|
||||||
|
@ -137,6 +141,14 @@ pub struct ProfileCreateData {
|
||||||
pub actor: Option<Value>,
|
pub actor: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ProfileCreateData {
|
||||||
|
pub fn clean(&self) -> Result<(), ValidationError> {
|
||||||
|
validate_username(&self.username)?;
|
||||||
|
validate_display_name(self.display_name.as_ref())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ProfileUpdateData {
|
pub struct ProfileUpdateData {
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
|
@ -169,7 +181,7 @@ impl ProfileUpdateData {
|
||||||
unique_labels.dedup();
|
unique_labels.dedup();
|
||||||
if unique_labels.len() < self.extra_fields.len() {
|
if unique_labels.len() < self.extra_fields.len() {
|
||||||
return Err(ValidationError("duplicate labels"));
|
return Err(ValidationError("duplicate labels"));
|
||||||
}
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
46
src/models/profiles/validators.rs
Normal file
46
src/models/profiles/validators.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::errors::ValidationError;
|
||||||
|
|
||||||
|
pub fn validate_username(username: &str) -> Result<(), ValidationError> {
|
||||||
|
if username.len() > 100 {
|
||||||
|
return Err(ValidationError("username is too long"));
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_display_name(display_name: Option<&String>)
|
||||||
|
-> Result<(), ValidationError>
|
||||||
|
{
|
||||||
|
if let Some(display_name) = display_name {
|
||||||
|
if display_name.len() > 200 {
|
||||||
|
return Err(ValidationError("display name is too long"));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_username() {
|
||||||
|
let result_1 = validate_username("test");
|
||||||
|
assert!(result_1.is_ok());
|
||||||
|
let result_2 = validate_username(&"x".repeat(101));
|
||||||
|
assert!(result_2.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_validate_display_name() {
|
||||||
|
let display_name = "test".to_string();
|
||||||
|
let result_1 = validate_display_name(Some(&display_name));
|
||||||
|
assert!(result_1.is_ok());
|
||||||
|
|
||||||
|
let result_2 = validate_display_name(None);
|
||||||
|
assert!(result_2.is_ok());
|
||||||
|
|
||||||
|
let display_name = "x".repeat(201);
|
||||||
|
let result_3 = validate_display_name(Some(&display_name));
|
||||||
|
assert!(result_3.is_err());
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ use uuid::Uuid;
|
||||||
|
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::models::profiles::types::DbActorProfile;
|
use crate::models::profiles::types::DbActorProfile;
|
||||||
|
use crate::models::profiles::validators::validate_username;
|
||||||
|
|
||||||
#[derive(FromSql)]
|
#[derive(FromSql)]
|
||||||
#[postgres(name = "user_account")]
|
#[postgres(name = "user_account")]
|
||||||
|
@ -53,7 +54,7 @@ pub struct UserCreateData {
|
||||||
pub invite_code: Option<String>,
|
pub invite_code: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_username(username: &str) -> Result<(), ValidationError> {
|
fn validate_local_username(username: &str) -> Result<(), ValidationError> {
|
||||||
// The username regexp should not allow domain names and IP addresses
|
// The username regexp should not allow domain names and IP addresses
|
||||||
let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap();
|
let username_regexp = Regex::new(r"^[a-z0-9_]+$").unwrap();
|
||||||
if !username_regexp.is_match(username) {
|
if !username_regexp.is_match(username) {
|
||||||
|
@ -66,6 +67,7 @@ impl UserCreateData {
|
||||||
/// Validate and clean.
|
/// Validate and clean.
|
||||||
pub fn clean(&self) -> Result<(), ValidationError> {
|
pub fn clean(&self) -> Result<(), ValidationError> {
|
||||||
validate_username(&self.username)?;
|
validate_username(&self.username)?;
|
||||||
|
validate_local_username(&self.username)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,10 +77,10 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_validate_username() {
|
fn test_validate_local_username() {
|
||||||
let result_1 = validate_username("name_1");
|
let result_1 = validate_local_username("name_1");
|
||||||
assert_eq!(result_1.is_ok(), true);
|
assert_eq!(result_1.is_ok(), true);
|
||||||
let result_2 = validate_username("name&");
|
let result_2 = validate_local_username("name&");
|
||||||
assert_eq!(result_2.is_ok(), false);
|
assert_eq!(result_2.is_ok(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue