Improve instance URL parsing and validation
This commit is contained in:
parent
81d6cf3daf
commit
7f07468d14
6 changed files with 76 additions and 27 deletions
|
@ -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)])
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue