Validate acct during profile creation
This commit is contained in:
parent
429f530a71
commit
8b6aef2b7a
5 changed files with 67 additions and 10 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
|
@ -178,12 +181,6 @@ pub struct ActorAddress {
|
||||||
pub instance: String,
|
pub instance: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for ActorAddress {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
format!("{}@{}", self.username, self.instance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ActorAddress {
|
impl ActorAddress {
|
||||||
pub fn is_local(&self, instance_host: &str) -> bool {
|
pub fn is_local(&self, instance_host: &str) -> bool {
|
||||||
self.instance == instance_host
|
self.instance == instance_host
|
||||||
|
@ -199,6 +196,30 @@ impl ActorAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// See also: USERNAME_RE in models::profiles::validators
|
||||||
|
const ACTOR_ADDRESS_RE: &str = r"(?P<username>[\w\.-]+)@(?P<instance>[\w\.-]+)";
|
||||||
|
|
||||||
|
impl FromStr for ActorAddress {
|
||||||
|
type Err = ValidationError;
|
||||||
|
|
||||||
|
fn from_str(value: &str) -> Result<Self, Self::Err> {
|
||||||
|
let actor_address_re = Regex::new(ACTOR_ADDRESS_RE).unwrap();
|
||||||
|
let caps = actor_address_re.captures(value)
|
||||||
|
.ok_or(ValidationError("invalid actor address"))?;
|
||||||
|
let actor_address = Self {
|
||||||
|
username: caps["username"].to_string(),
|
||||||
|
instance: caps["instance"].to_string(),
|
||||||
|
};
|
||||||
|
Ok(actor_address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for ActorAddress {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{}@{}", self.username, self.instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type ActorKeyError = rsa::pkcs8::Error;
|
pub type ActorKeyError = rsa::pkcs8::Error;
|
||||||
|
|
||||||
pub fn get_local_actor(
|
pub fn get_local_actor(
|
||||||
|
@ -330,6 +351,15 @@ mod tests {
|
||||||
const INSTANCE_HOST: &str = "example.com";
|
const INSTANCE_HOST: &str = "example.com";
|
||||||
const INSTANCE_URL: &str = "https://example.com";
|
const INSTANCE_URL: &str = "https://example.com";
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_actor_address_parsing() {
|
||||||
|
let value = "user_1@example.com";
|
||||||
|
let actor_address = value.parse::<ActorAddress>().unwrap();
|
||||||
|
assert_eq!(actor_address.username, "user_1");
|
||||||
|
assert_eq!(actor_address.instance, "example.com");
|
||||||
|
assert_eq!(actor_address.to_string(), value);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_actor_address() {
|
fn test_get_actor_address() {
|
||||||
let actor = Actor {
|
let actor = Actor {
|
||||||
|
|
|
@ -40,7 +40,7 @@ enum SearchQuery {
|
||||||
fn parse_profile_query(query: &str) ->
|
fn parse_profile_query(query: &str) ->
|
||||||
Result<(String, Option<String>), ValidationError>
|
Result<(String, Option<String>), ValidationError>
|
||||||
{
|
{
|
||||||
// See also: USERNAME_RE in models::profiles::validators
|
// See also: ACTOR_ADDRESS_RE in activitypub::actors::types
|
||||||
let acct_query_re =
|
let acct_query_re =
|
||||||
Regex::new(r"^(@|!)?(?P<user>[\w\.-]+)(@(?P<instance>[\w\.-]+))?$").unwrap();
|
Regex::new(r"^(@|!)?(?P<user>[\w\.-]+)(@(?P<instance>[\w\.-]+))?$").unwrap();
|
||||||
let acct_query_caps = acct_query_re.captures(query)
|
let acct_query_caps = acct_query_re.captures(query)
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::errors::{DatabaseError, ValidationError};
|
||||||
use crate::models::profiles::queries::get_profiles_by_accts;
|
use crate::models::profiles::queries::get_profiles_by_accts;
|
||||||
use crate::models::profiles::types::DbActorProfile;
|
use crate::models::profiles::types::DbActorProfile;
|
||||||
|
|
||||||
// See also: USERNAME_RE in models::profiles::validators
|
// See also: ACTOR_ADDRESS_RE in activitypub::actors::types
|
||||||
const MENTION_RE: &str = r"@?(?P<user>[\w\.-]+)@(?P<instance>.+)";
|
const MENTION_RE: &str = r"@?(?P<user>[\w\.-]+)@(?P<instance>.+)";
|
||||||
const MENTION_SEARCH_RE: &str = r"(?m)(?P<before>^|\s|[\(])@(?P<user>[\w\.-]+)@(?P<instance>\S+)";
|
const MENTION_SEARCH_RE: &str = r"(?m)(?P<before>^|\s|[\(])@(?P<user>[\w\.-]+)@(?P<instance>\S+)";
|
||||||
const MENTION_SEARCH_SECONDARY_RE: &str = r"^(?P<instance>[\w\.-]+\w)(?P<after>[\.,:?\)]?(<br>)?)$";
|
const MENTION_SEARCH_SECONDARY_RE: &str = r"^(?P<instance>[\w\.-]+\w)(?P<after>[\.,:?\)]?(<br>)?)$";
|
||||||
|
|
|
@ -10,7 +10,7 @@ use serde::{
|
||||||
};
|
};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::activitypub::actors::types::Actor;
|
use crate::activitypub::actors::types::{Actor, ActorAddress};
|
||||||
use crate::activitypub::identifiers::local_actor_id;
|
use crate::activitypub::identifiers::local_actor_id;
|
||||||
use crate::database::json_macro::{json_from_sql, json_to_sql};
|
use crate::database::json_macro::{json_from_sql, json_to_sql};
|
||||||
use crate::errors::{ConversionError, ValidationError};
|
use crate::errors::{ConversionError, ValidationError};
|
||||||
|
@ -337,6 +337,17 @@ impl ProfileCreateData {
|
||||||
if let Some(display_name) = &self.display_name {
|
if let Some(display_name) = &self.display_name {
|
||||||
validate_display_name(display_name)?;
|
validate_display_name(display_name)?;
|
||||||
};
|
};
|
||||||
|
let acct_username = if self.actor_json.is_none() {
|
||||||
|
// Local profile
|
||||||
|
self.acct.clone()
|
||||||
|
} else {
|
||||||
|
// Remote profile
|
||||||
|
let ActorAddress { username, .. } = self.acct.parse::<ActorAddress>()?;
|
||||||
|
username
|
||||||
|
};
|
||||||
|
if self.username != acct_username {
|
||||||
|
return Err(ValidationError("username doesn't match acct"));
|
||||||
|
};
|
||||||
if let Some(bio) = &self.bio {
|
if let Some(bio) = &self.bio {
|
||||||
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
let cleaned_bio = clean_bio(bio, self.actor_json.is_some())?;
|
||||||
self.bio = Some(cleaned_bio);
|
self.bio = Some(cleaned_bio);
|
||||||
|
@ -477,4 +488,20 @@ mod tests {
|
||||||
remote_profile.acct,
|
remote_profile.acct,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_clean_profile_create_data() {
|
||||||
|
let mut profile_data = ProfileCreateData {
|
||||||
|
username: "test".to_string(),
|
||||||
|
display_name: Some("Test Test".to_string()),
|
||||||
|
acct: "test@example.org".to_string(),
|
||||||
|
actor_json: Some(Actor {
|
||||||
|
id: "https://example.org/test".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let result = profile_data.clean();
|
||||||
|
assert_eq!(result.is_ok(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ async fn get_user_info(
|
||||||
) -> Result<JsonResourceDescriptor, HttpError> {
|
) -> Result<JsonResourceDescriptor, HttpError> {
|
||||||
// Parse 'acct' URI
|
// Parse 'acct' URI
|
||||||
// https://datatracker.ietf.org/doc/html/rfc7565#section-7
|
// https://datatracker.ietf.org/doc/html/rfc7565#section-7
|
||||||
// See also: USERNAME_RE in models::profiles::validators
|
// See also: ACTOR_ADDRESS_RE in activitypub::actors::types
|
||||||
let uri_regexp = Regex::new(r"acct:(?P<user>[\w\.-]+)@(?P<instance>.+)").unwrap();
|
let uri_regexp = Regex::new(r"acct:(?P<user>[\w\.-]+)@(?P<instance>.+)").unwrap();
|
||||||
let uri_caps = uri_regexp.captures(&query_params.resource)
|
let uri_caps = uri_regexp.captures(&query_params.resource)
|
||||||
.ok_or(ValidationError("invalid query target"))?;
|
.ok_or(ValidationError("invalid query target"))?;
|
||||||
|
|
Loading…
Reference in a new issue