Sign GET request when fetching remote actor profile
This commit is contained in:
parent
286e7155b8
commit
924f5181b6
4 changed files with 61 additions and 13 deletions
|
@ -1,7 +1,10 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
|
use reqwest::Method;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use crate::config::Instance;
|
||||||
|
use crate::http_signatures::create::{create_http_signature, SignatureError};
|
||||||
use crate::models::profiles::types::ProfileCreateData;
|
use crate::models::profiles::types::ProfileCreateData;
|
||||||
use crate::utils::files::{save_file, FileError};
|
use crate::utils::files::{save_file, FileError};
|
||||||
use crate::webfinger::types::JsonResourceDescriptor;
|
use crate::webfinger::types::JsonResourceDescriptor;
|
||||||
|
@ -14,6 +17,9 @@ pub enum FetchError {
|
||||||
#[error("invalid URL")]
|
#[error("invalid URL")]
|
||||||
UrlError(#[from] url::ParseError),
|
UrlError(#[from] url::ParseError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
SignatureError(#[from] SignatureError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
RequestError(#[from] reqwest::Error),
|
RequestError(#[from] reqwest::Error),
|
||||||
|
|
||||||
|
@ -27,6 +33,37 @@ pub enum FetchError {
|
||||||
OtherError(&'static str),
|
OtherError(&'static str),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends GET request to fetch AP object
|
||||||
|
async fn send_request(
|
||||||
|
instance: &Instance,
|
||||||
|
url: &str,
|
||||||
|
query_params: &[(&str, &str)],
|
||||||
|
) -> Result<String, FetchError> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let mut request_builder = client.get(url);
|
||||||
|
if !query_params.is_empty() {
|
||||||
|
request_builder = request_builder.query(query_params);
|
||||||
|
};
|
||||||
|
|
||||||
|
let headers = create_http_signature(
|
||||||
|
Method::GET,
|
||||||
|
url,
|
||||||
|
"",
|
||||||
|
&instance.actor_key,
|
||||||
|
&instance.actor_key_id(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let data = request_builder
|
||||||
|
.header(reqwest::header::ACCEPT, ACTIVITY_CONTENT_TYPE)
|
||||||
|
.header("Host", headers.host)
|
||||||
|
.header("Date", headers.date)
|
||||||
|
.header("Signature", headers.signature)
|
||||||
|
.send().await?
|
||||||
|
.error_for_status()?
|
||||||
|
.text().await?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_avatar_and_banner(
|
pub async fn fetch_avatar_and_banner(
|
||||||
actor: &Actor,
|
actor: &Actor,
|
||||||
media_dir: &Path,
|
media_dir: &Path,
|
||||||
|
@ -55,14 +92,15 @@ pub async fn fetch_avatar_and_banner(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_profile(
|
pub async fn fetch_profile(
|
||||||
|
instance: &Instance,
|
||||||
username: &str,
|
username: &str,
|
||||||
instance_host: &str,
|
actor_host: &str,
|
||||||
media_dir: &Path,
|
media_dir: &Path,
|
||||||
) -> Result<ProfileCreateData, FetchError> {
|
) -> Result<ProfileCreateData, FetchError> {
|
||||||
let actor_address = format!("{}@{}", &username, &instance_host);
|
let actor_address = format!("{}@{}", &username, &actor_host);
|
||||||
let webfinger_account_uri = format!("acct:{}", actor_address);
|
let webfinger_account_uri = format!("acct:{}", actor_address);
|
||||||
// TOOD: support http
|
// TOOD: support http
|
||||||
let webfinger_url = format!("https://{}/.well-known/webfinger", instance_host);
|
let webfinger_url = format!("https://{}/.well-known/webfinger", actor_host);
|
||||||
let client = reqwest::Client::new();
|
let client = reqwest::Client::new();
|
||||||
let webfinger_data = client.get(&webfinger_url)
|
let webfinger_data = client.get(&webfinger_url)
|
||||||
.query(&[("resource", webfinger_account_uri)])
|
.query(&[("resource", webfinger_account_uri)])
|
||||||
|
@ -75,10 +113,11 @@ pub async fn fetch_profile(
|
||||||
.ok_or(FetchError::OtherError("self link not found"))?;
|
.ok_or(FetchError::OtherError("self link not found"))?;
|
||||||
let actor_url = link.href.as_ref()
|
let actor_url = link.href.as_ref()
|
||||||
.ok_or(FetchError::OtherError("account href not found"))?;
|
.ok_or(FetchError::OtherError("account href not found"))?;
|
||||||
fetch_profile_by_actor_id(actor_url, media_dir).await
|
fetch_profile_by_actor_id(instance, actor_url, media_dir).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_profile_by_actor_id(
|
pub async fn fetch_profile_by_actor_id(
|
||||||
|
instance: &Instance,
|
||||||
actor_url: &str,
|
actor_url: &str,
|
||||||
media_dir: &Path,
|
media_dir: &Path,
|
||||||
) -> Result<ProfileCreateData, FetchError> {
|
) -> Result<ProfileCreateData, FetchError> {
|
||||||
|
@ -86,11 +125,7 @@ pub async fn fetch_profile_by_actor_id(
|
||||||
.host_str()
|
.host_str()
|
||||||
.ok_or(FetchError::OtherError("invalid URL"))?
|
.ok_or(FetchError::OtherError("invalid URL"))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
let client = reqwest::Client::new();
|
let actor_json = send_request(instance, actor_url, &[]).await?;
|
||||||
let actor_json = client.get(actor_url)
|
|
||||||
.header(reqwest::header::ACCEPT, ACTIVITY_CONTENT_TYPE)
|
|
||||||
.send().await?
|
|
||||||
.text().await?;
|
|
||||||
let actor_value: Value = serde_json::from_str(&actor_json)?;
|
let actor_value: Value = serde_json::from_str(&actor_json)?;
|
||||||
let actor: Actor = serde_json::from_value(actor_value.clone())?;
|
let actor: Actor = serde_json::from_value(actor_value.clone())?;
|
||||||
let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?;
|
let (avatar, banner) = fetch_avatar_and_banner(&actor, media_dir).await?;
|
||||||
|
|
|
@ -5,7 +5,7 @@ use serde_json::Value;
|
||||||
use tokio_postgres::GenericClient;
|
use tokio_postgres::GenericClient;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::{Config, Instance};
|
||||||
use crate::database::{Pool, get_database_client};
|
use crate::database::{Pool, get_database_client};
|
||||||
use crate::errors::{DatabaseError, HttpError, ValidationError};
|
use crate::errors::{DatabaseError, HttpError, ValidationError};
|
||||||
use crate::models::attachments::queries::create_attachment;
|
use crate::models::attachments::queries::create_attachment;
|
||||||
|
@ -82,13 +82,17 @@ fn parse_object_id(
|
||||||
|
|
||||||
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,
|
||||||
actor_id: &str,
|
actor_id: &str,
|
||||||
media_dir: &Path,
|
media_dir: &Path,
|
||||||
) -> Result<DbActorProfile, HttpError> {
|
) -> Result<DbActorProfile, HttpError> {
|
||||||
let profile = match get_profile_by_actor_id(db_client, actor_id).await {
|
let profile = match get_profile_by_actor_id(db_client, actor_id).await {
|
||||||
Ok(profile) => profile,
|
Ok(profile) => profile,
|
||||||
Err(DatabaseError::NotFound(_)) => {
|
Err(DatabaseError::NotFound(_)) => {
|
||||||
let profile_data = fetch_profile_by_actor_id(actor_id, media_dir).await
|
let profile_data = fetch_profile_by_actor_id(
|
||||||
|
instance, actor_id, media_dir,
|
||||||
|
)
|
||||||
|
.await
|
||||||
.map_err(|_| ValidationError("failed to fetch actor"))?;
|
.map_err(|_| ValidationError("failed to fetch actor"))?;
|
||||||
let profile = create_profile(db_client, &profile_data).await?;
|
let profile = create_profile(db_client, &profile_data).await?;
|
||||||
profile
|
profile
|
||||||
|
@ -153,6 +157,7 @@ pub async fn process_note(
|
||||||
.ok_or(ValidationError("unattributed note"))?;
|
.ok_or(ValidationError("unattributed note"))?;
|
||||||
let author = get_or_fetch_profile_by_actor_id(
|
let author = get_or_fetch_profile_by_actor_id(
|
||||||
db_client,
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
&attributed_to,
|
&attributed_to,
|
||||||
&config.media_dir(),
|
&config.media_dir(),
|
||||||
).await?;
|
).await?;
|
||||||
|
@ -184,6 +189,7 @@ pub async fn process_note(
|
||||||
if tag.tag_type == MENTION {
|
if tag.tag_type == MENTION {
|
||||||
let profile = get_or_fetch_profile_by_actor_id(
|
let profile = get_or_fetch_profile_by_actor_id(
|
||||||
db_client,
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
&tag.href,
|
&tag.href,
|
||||||
&config.media_dir(),
|
&config.media_dir(),
|
||||||
).await?;
|
).await?;
|
||||||
|
@ -270,6 +276,7 @@ pub async fn receive_activity(
|
||||||
(LIKE, _) => {
|
(LIKE, _) => {
|
||||||
let author = get_or_fetch_profile_by_actor_id(
|
let author = get_or_fetch_profile_by_actor_id(
|
||||||
db_client,
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
&activity.actor,
|
&activity.actor,
|
||||||
&config.media_dir(),
|
&config.media_dir(),
|
||||||
).await?;
|
).await?;
|
||||||
|
@ -298,6 +305,7 @@ pub async fn receive_activity(
|
||||||
(FOLLOW, _) => {
|
(FOLLOW, _) => {
|
||||||
let source_profile = get_or_fetch_profile_by_actor_id(
|
let source_profile = get_or_fetch_profile_by_actor_id(
|
||||||
db_client,
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
&activity.actor,
|
&activity.actor,
|
||||||
&config.media_dir(),
|
&config.media_dir(),
|
||||||
).await?;
|
).await?;
|
||||||
|
|
|
@ -123,6 +123,7 @@ pub async fn verify_http_signature(
|
||||||
Err(err) => match err {
|
Err(err) => match err {
|
||||||
DatabaseError::NotFound(_) => {
|
DatabaseError::NotFound(_) => {
|
||||||
let profile_data = fetch_profile_by_actor_id(
|
let profile_data = fetch_profile_by_actor_id(
|
||||||
|
&config.instance(),
|
||||||
&signature_data.actor_id,
|
&signature_data.actor_id,
|
||||||
&config.media_dir(),
|
&config.media_dir(),
|
||||||
).await.map_err(|err| {
|
).await.map_err(|err| {
|
||||||
|
|
|
@ -49,9 +49,13 @@ async fn search_profiles(
|
||||||
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
|
let mut profiles = search_profile(db_client, &username, instance.as_ref()).await?;
|
||||||
if profiles.is_empty() && instance.is_some() {
|
if profiles.is_empty() && instance.is_some() {
|
||||||
let actor_host = instance.unwrap();
|
let actor_host = instance.unwrap();
|
||||||
let media_dir = config.media_dir();
|
|
||||||
// TODO: return error when trying to fetch local profile
|
// TODO: return error when trying to fetch local profile
|
||||||
match fetch_profile(&username, &actor_host, &media_dir).await {
|
match fetch_profile(
|
||||||
|
&config.instance(),
|
||||||
|
&username,
|
||||||
|
&actor_host,
|
||||||
|
&config.media_dir(),
|
||||||
|
).await {
|
||||||
Ok(profile_data) => {
|
Ok(profile_data) => {
|
||||||
let profile = create_profile(db_client, &profile_data).await?;
|
let profile = create_profile(db_client, &profile_data).await?;
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
Loading…
Reference in a new issue