Improve instance URL parsing and validation

This commit is contained in:
silverpill 2021-11-10 14:32:27 +00:00
parent 81d6cf3daf
commit 7f07468d14
6 changed files with 76 additions and 27 deletions

View file

@ -56,13 +56,13 @@ pub async fn fetch_avatar_and_banner(
pub async fn fetch_profile(
username: &str,
instance_uri: &str,
instance_host: &str,
media_dir: &PathBuf,
) -> Result<ProfileCreateData, FetchError> {
let actor_address = format!("{}@{}", &username, &instance_uri);
let actor_address = format!("{}@{}", &username, &instance_host);
let webfinger_account_uri = format!("acct:{}", actor_address);
// TOOD: support http
let webfinger_url = format!("https://{}/.well-known/webfinger", instance_uri);
let webfinger_url = format!("https://{}/.well-known/webfinger", instance_host);
let client = reqwest::Client::new();
let webfinger_data = client.get(&webfinger_url)
.query(&[("resource", webfinger_account_uri)])

View file

@ -2,7 +2,7 @@ use std::path::PathBuf;
use std::str::FromStr;
use serde::{de, Deserialize, Deserializer};
use url::{Url, ParseError as UrlParseError};
use url::Url;
use crate::errors::ConversionError;
@ -82,7 +82,7 @@ pub struct Config {
pub http_port: u32,
// Instance info
pub instance_uri: String,
instance_uri: String,
pub instance_title: String,
pub instance_short_description: String,
pub instance_description: String,
@ -102,18 +102,24 @@ pub struct Config {
}
impl Config {
fn try_instance_url(&self) -> Result<Url, UrlParseError> {
fn try_instance_url(&self) -> Result<Url, ConversionError> {
// TODO: allow http in production
let scheme = match self.environment {
Environment::Development => "http",
Environment::Production => "https",
};
let url_str = format!("{}://{}", scheme, self.instance_uri);
Url::parse(&url_str)
let url = Url::parse(&url_str).map_err(|_| ConversionError)?;
url.host().ok_or(ConversionError)?; // validates URL
Ok(url)
}
pub fn instance(&self) -> Instance {
Instance { _url: self.try_instance_url().unwrap() }
}
pub fn instance_url(&self) -> String {
self.try_instance_url().unwrap().origin().ascii_serialization()
self.instance().url()
}
pub fn media_dir(&self) -> PathBuf {
@ -121,6 +127,21 @@ impl Config {
}
}
pub struct Instance {
_url: Url,
}
impl Instance {
pub fn url(&self) -> String {
self._url.origin().ascii_serialization()
}
pub fn host(&self) -> String {
self._url.host_str().unwrap().to_string()
}
}
pub fn parse_config() -> Config {
let env = parse_env();
let config_yaml = std::fs::read_to_string(env.config_path)
@ -144,3 +165,26 @@ pub fn parse_config() -> Config {
config
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_instance_url_https_dns() {
let instance_url = Url::parse("https://example.com/").unwrap();
let instance = Instance { _url: instance_url };
assert_eq!(instance.url(), "https://example.com");
assert_eq!(instance.host(), "example.com");
}
#[test]
fn test_instance_url_http_ipv4() {
let instance_url = Url::parse("http://1.2.3.4:3777/").unwrap();
let instance = Instance { _url: instance_url };
assert_eq!(instance.url(), "http://1.2.3.4:3777");
assert_eq!(instance.host(), "1.2.3.4");
}
}

View file

@ -4,7 +4,7 @@ use crate::config::Config;
use crate::ethereum::contracts::MANAGER;
#[derive(Serialize)]
pub struct Instance {
pub struct InstanceInfo {
uri: String,
title: String,
short_description: String,
@ -19,10 +19,10 @@ pub struct Instance {
ipfs_gateway_url: Option<String>,
}
impl From<&Config> for Instance {
impl From<&Config> for InstanceInfo {
fn from(config: &Config) -> Self {
Self {
uri: config.instance_uri.clone(),
uri: config.instance().host(),
title: config.instance_title.clone(),
short_description: config.instance_short_description.clone(),
description: config.instance_description.clone(),

View file

@ -2,13 +2,13 @@ use actix_web::{get, web, HttpResponse, Scope};
use crate::config::Config;
use crate::errors::HttpError;
use super::types::Instance;
use super::types::InstanceInfo;
#[get("")]
async fn instance_view(
config: web::Data<Config>,
) -> Result<HttpResponse, HttpError> {
let instance = Instance::from(config.as_ref());
let instance = InstanceInfo::from(config.as_ref());
Ok(HttpResponse::Ok().json(instance))
}

View file

@ -21,9 +21,9 @@ fn parse_profile_query(query: &str) ->
let username = acct_caps.name("user")
.ok_or(ValidationError("invalid search query"))?
.as_str().to_string();
let instance = acct_caps.name("instance")
let maybe_instance = acct_caps.name("instance")
.and_then(|val| Some(val.as_str().to_string()));
Ok((username, instance))
Ok((username, maybe_instance))
}
async fn search_profiles(
@ -40,9 +40,9 @@ async fn search_profiles(
};
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
if profiles.len() == 0 && instance.is_some() {
let instance_uri = instance.unwrap();
let instance_host = instance.unwrap();
let media_dir = config.media_dir();
match fetch_profile(&username, &instance_uri, &media_dir).await {
match fetch_profile(&username, &instance_host, &media_dir).await {
Ok(profile_data) => {
let profile = create_profile(db_client, &profile_data).await?;
log::info!(

View file

@ -1,9 +1,10 @@
use actix_web::{get, web, HttpResponse};
use regex::Regex;
use tokio_postgres::GenericClient;
use crate::activitypub::views::get_actor_url;
use crate::activitypub::constants::ACTIVITY_CONTENT_TYPE;
use crate::config::Config;
use crate::config::{Config, Instance};
use crate::database::{Pool, get_database_client};
use crate::errors::HttpError;
use crate::models::users::queries::is_registered_user;
@ -15,8 +16,8 @@ use super::types::{
};
async fn get_user_info(
db_pool: &Pool,
config: &Config,
db_client: &impl GenericClient,
instance: Instance,
query_params: WebfingerQueryParams,
) -> Result<JsonResourceDescriptor, HttpError> {
// Parse 'acct' URI
@ -27,19 +28,18 @@ async fn get_user_info(
let username = uri_caps.name("user")
.ok_or(HttpError::ValidationError("invalid query target".into()))?
.as_str();
let instance_uri = uri_caps.name("instance")
let instance_host = uri_caps.name("instance")
.ok_or(HttpError::ValidationError("invalid query target".into()))?
.as_str();
if instance_uri != config.instance_uri {
// Wrong instance URI
if instance_host != instance.host() {
// Wrong instance
return Err(HttpError::NotFoundError("user"));
}
let db_client = &**get_database_client(db_pool).await?;
if !is_registered_user(db_client, &username).await? {
return Err(HttpError::NotFoundError("user"));
}
let actor_url = get_actor_url(&config.instance_url(), &username);
let actor_url = get_actor_url(&instance.url(), &username);
let link = Link {
rel: "self".to_string(),
link_type: Some(ACTIVITY_CONTENT_TYPE.to_string()),
@ -55,10 +55,15 @@ async fn get_user_info(
#[get("/.well-known/webfinger")]
pub async fn get_descriptor(
config: web::Data<Config>,
db_bool: web::Data<Pool>,
db_pool: web::Data<Pool>,
query_params: web::Query<WebfingerQueryParams>,
) -> Result<HttpResponse, HttpError> {
let jrd = get_user_info(&db_bool, &config, query_params.into_inner()).await?;
let db_client = &**get_database_client(&db_pool).await?;
let jrd = get_user_info(
db_client,
config.instance(),
query_params.into_inner(),
).await?;
let response = HttpResponse::Ok()
.content_type(JRD_CONTENT_TYPE)
.json(jrd);