Upgrade deps (#135)
* Upgrade deps (#133)
* Upgrade deps
* fmt
* fix
* remove once_cell
* upgrade rust
* fix warnings
* Upgrading deps. (#137)
* Upgrading deps.
* Axum upgrade.
* Remove some uses of async_trait
* Remove diesel feature
* Rust 1.85
* fix once lock for domain regex
* Revert "Remove some uses of async_trait"
This reverts commit 51bf4b332e.
* update deps
* Make it work with Lemmy
---------
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
This commit is contained in:
parent
b13c1cc092
commit
80dce32279
17 changed files with 3212 additions and 256 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,5 @@
|
||||||
/target
|
/target
|
||||||
/.idea
|
/.idea
|
||||||
/Cargo.lock
|
|
||||||
perf.data*
|
perf.data*
|
||||||
flamegraph.svg
|
flamegraph.svg
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
variables:
|
variables:
|
||||||
- &rust_image "rust:1.82-bullseye"
|
- &rust_image "rust:1.81-bullseye"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
cargo_fmt:
|
cargo_fmt:
|
||||||
|
|
|
||||||
3134
Cargo.lock
generated
Normal file
3134
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
69
Cargo.toml
69
Cargo.toml
|
|
@ -12,7 +12,6 @@ documentation = "https://docs.rs/activitypub_federation/"
|
||||||
default = ["actix-web", "axum"]
|
default = ["actix-web", "axum"]
|
||||||
actix-web = ["dep:actix-web", "dep:http02"]
|
actix-web = ["dep:actix-web", "dep:http02"]
|
||||||
axum = ["dep:axum", "dep:tower"]
|
axum = ["dep:axum", "dep:tower"]
|
||||||
diesel = ["dep:diesel"]
|
|
||||||
|
|
||||||
[lints.rust]
|
[lints.rust]
|
||||||
warnings = "deny"
|
warnings = "deny"
|
||||||
|
|
@ -32,71 +31,69 @@ redundant_closure_for_method_calls = "deny"
|
||||||
unwrap_used = "deny"
|
unwrap_used = "deny"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { version = "0.4.38", features = ["clock"], default-features = false }
|
chrono = { version = "0.4.41", features = ["clock"], default-features = false }
|
||||||
serde = { version = "1.0.204", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
async-trait = "0.1.81"
|
async-trait = "0.1.88"
|
||||||
url = { version = "2.5.2", features = ["serde"] }
|
url = { version = "2.5.4", features = ["serde"] }
|
||||||
serde_json = { version = "1.0.120", features = ["preserve_order"] }
|
serde_json = { version = "1.0.140", features = ["preserve_order"] }
|
||||||
reqwest = { version = "0.12.5", default-features = false, features = [
|
reqwest = { version = "0.12.18", default-features = false, features = [
|
||||||
"json",
|
"json",
|
||||||
"stream",
|
"stream",
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
] }
|
] }
|
||||||
reqwest-middleware = "0.3.2"
|
reqwest-middleware = "0.4.2"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.41"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rsa = "0.9.6"
|
rsa = "0.9.8"
|
||||||
once_cell = "1.19.0"
|
http = "1.3.1"
|
||||||
http = "1.1.0"
|
sha2 = { version = "0.10.9", features = ["oid"] }
|
||||||
sha2 = { version = "0.10.8", features = ["oid"] }
|
thiserror = "2.0.12"
|
||||||
thiserror = "1.0.62"
|
derive_builder = "0.20.2"
|
||||||
derive_builder = "0.20.0"
|
itertools = "0.14.0"
|
||||||
itertools = "0.13.0"
|
dyn-clone = "1.0.19"
|
||||||
dyn-clone = "1.0.17"
|
|
||||||
enum_delegate = "0.2.0"
|
enum_delegate = "0.2.0"
|
||||||
httpdate = "1.0.3"
|
httpdate = "1.0.3"
|
||||||
http-signature-normalization-reqwest = { version = "0.12.0", default-features = false, features = [
|
http-signature-normalization-reqwest = { version = "0.13.0", default-features = false, features = [
|
||||||
"sha-2",
|
"sha-2",
|
||||||
"middleware",
|
"middleware",
|
||||||
"default-spawner",
|
"default-spawner",
|
||||||
] }
|
] }
|
||||||
http-signature-normalization = "0.7.0"
|
http-signature-normalization = "0.7.0"
|
||||||
bytes = "1.6.1"
|
bytes = "1.10.1"
|
||||||
futures-core = { version = "0.3.30", default-features = false }
|
futures-core = { version = "0.3.31", default-features = false }
|
||||||
pin-project-lite = "0.2.14"
|
pin-project-lite = "0.2.16"
|
||||||
activitystreams-kinds = "0.3.0"
|
activitystreams-kinds = "0.3.0"
|
||||||
regex = { version = "1.10.5", default-features = false, features = [
|
regex = { version = "1.11.1", default-features = false, features = [
|
||||||
"std",
|
"std",
|
||||||
"unicode",
|
"unicode",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.38.0", features = [
|
tokio = { version = "1.45.0", features = [
|
||||||
"sync",
|
"sync",
|
||||||
"rt",
|
"rt",
|
||||||
"rt-multi-thread",
|
"rt-multi-thread",
|
||||||
"time",
|
"time",
|
||||||
] }
|
] }
|
||||||
diesel = { version = "2.2.1", features = [
|
futures = "0.3.31"
|
||||||
"postgres",
|
moka = { version = "0.12.10", features = ["future"] }
|
||||||
], default-features = false, optional = true }
|
|
||||||
futures = "0.3.30"
|
|
||||||
moka = { version = "0.12.8", features = ["future"] }
|
|
||||||
either = "1.15.0"
|
either = "1.15.0"
|
||||||
|
|
||||||
# Actix-web
|
# Actix-web
|
||||||
actix-web = { version = "4.8.0", default-features = false, optional = true }
|
actix-web = { version = "4.11.0", default-features = false, optional = true }
|
||||||
http02 = { package = "http", version = "0.2.12", optional = true }
|
http02 = { package = "http", version = "0.2.12", optional = true }
|
||||||
|
|
||||||
# Axum
|
# Axum
|
||||||
axum = { version = "0.7.5", features = ["json"], default-features = false, optional = true }
|
axum = { version = "0.8.4", features = [
|
||||||
tower = { version = "0.4.13", optional = true }
|
"json",
|
||||||
|
], default-features = false, optional = true }
|
||||||
|
tower = { version = "0.5.2", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0.86"
|
anyhow = "1.0.98"
|
||||||
axum = { version = "0.7.5", features = ["macros"] }
|
axum = { version = "0.8.4", features = ["macros"] }
|
||||||
axum-extra = { version = "0.9.3", features = ["typed-header"] }
|
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||||
env_logger = "0.11.3"
|
env_logger = "0.11.8"
|
||||||
tokio = { version = "1.38.0", features = ["full"] }
|
tokio = { version = "1.45.0", features = ["full"] }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
strip = "symbols"
|
strip = "symbols"
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
|
||||||
info!("Listening with axum on {hostname}");
|
info!("Listening with axum on {hostname}");
|
||||||
let config = config.clone();
|
let config = config.clone();
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/:user/inbox", post(http_post_user_inbox))
|
.route("/{user}/inbox", post(http_post_user_inbox))
|
||||||
.route("/:user", get(http_get_user))
|
.route("/{user}", get(http_get_user))
|
||||||
.route("/.well-known/webfinger", get(webfinger))
|
.route("/.well-known/webfinger", get(webfinger))
|
||||||
.layer(FederationMiddleware::new(config));
|
.layer(FederationMiddleware::new(config));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,14 +122,14 @@ mod test {
|
||||||
let (_, _, config) = setup_receive_test().await;
|
let (_, _, config) = setup_receive_test().await;
|
||||||
|
|
||||||
let actor = Url::parse("http://ds9.lemmy.ml/u/lemmy_alpha").unwrap();
|
let actor = Url::parse("http://ds9.lemmy.ml/u/lemmy_alpha").unwrap();
|
||||||
let id = "http://localhost:123/1";
|
let activity_id = "http://localhost:123/1";
|
||||||
let activity = json!({
|
let activity = json!({
|
||||||
"actor": actor.as_str(),
|
"actor": actor.as_str(),
|
||||||
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
"to": ["https://www.w3.org/ns/activitystreams#Public"],
|
||||||
"object": "http://ds9.lemmy.ml/post/1",
|
"object": "http://ds9.lemmy.ml/post/1",
|
||||||
"cc": ["http://enterprise.lemmy.ml/c/main"],
|
"cc": ["http://enterprise.lemmy.ml/c/main"],
|
||||||
"type": "Delete",
|
"type": "Delete",
|
||||||
"id": id
|
"id": activity_id
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
let body: Bytes = serde_json::to_vec(&activity).unwrap().into();
|
let body: Bytes = serde_json::to_vec(&activity).unwrap().into();
|
||||||
|
|
@ -144,8 +144,8 @@ mod test {
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(Error::ParseReceivedActivity(_, url)) => {
|
Err(Error::ParseReceivedActivity { err: _, id }) => {
|
||||||
assert_eq!(id, url.expect("has url").as_str());
|
assert_eq!(activity_id, id.expect("has url").as_str());
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ use crate::{
|
||||||
traits::{ActivityHandler, Actor, Object},
|
traits::{ActivityHandler, Actor, Object},
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::FromRequest,
|
extract::FromRequest,
|
||||||
http::{Request, StatusCode},
|
http::{Request, StatusCode},
|
||||||
|
|
@ -58,7 +57,6 @@ pub struct ActivityData {
|
||||||
body: Vec<u8>,
|
body: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<S> FromRequest<S> for ActivityData
|
impl<S> FromRequest<S> for ActivityData
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::config::{Data, FederationConfig, FederationMiddleware};
|
use crate::config::{Data, FederationConfig, FederationMiddleware};
|
||||||
use axum::{async_trait, body::Body, extract::FromRequestParts, http::Request, response::Response};
|
use axum::{body::Body, extract::FromRequestParts, http::Request, response::Response};
|
||||||
use http::{request::Parts, StatusCode};
|
use http::{request::Parts, StatusCode};
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use tower::{Layer, Service};
|
use tower::{Layer, Service};
|
||||||
|
|
@ -43,7 +43,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<S, T: Clone + 'static> FromRequestParts<S> for Data<T>
|
impl<S, T: Clone + 'static> FromRequestParts<S> for Data<T>
|
||||||
where
|
where
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ use bytes::Bytes;
|
||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
use dyn_clone::{clone_trait_object, DynClone};
|
use dyn_clone::{clone_trait_object, DynClone};
|
||||||
use moka::future::Cache;
|
use moka::future::Cache;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use reqwest::{redirect::Policy, Client, Request};
|
use reqwest::{redirect::Policy, Client, Request};
|
||||||
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
|
||||||
|
|
@ -38,6 +37,7 @@ use std::{
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicU32, Ordering},
|
atomic::{AtomicU32, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
|
OnceLock,
|
||||||
},
|
},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
@ -114,8 +114,10 @@ pub struct FederationConfig<T: Clone> {
|
||||||
pub(crate) queue_retry_count: usize,
|
pub(crate) queue_retry_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) static DOMAIN_REGEX: Lazy<Regex> =
|
pub(crate) fn domain_regex() -> &'static Regex {
|
||||||
Lazy::new(|| Regex::new(r"^[a-zA-Z0-9.-]*$").expect("compile regex"));
|
static DOMAIN_REGEX: OnceLock<Regex> = OnceLock::new();
|
||||||
|
DOMAIN_REGEX.get_or_init(|| Regex::new(r"^[a-zA-Z0-9.-]*$").expect("compile regex"))
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Clone> FederationConfig<T> {
|
impl<T: Clone> FederationConfig<T> {
|
||||||
/// Returns a new config builder with default values.
|
/// Returns a new config builder with default values.
|
||||||
|
|
@ -174,7 +176,7 @@ impl<T: Clone> FederationConfig<T> {
|
||||||
let Some(domain) = url.domain() else {
|
let Some(domain) = url.domain() else {
|
||||||
return Err(Error::UrlVerificationError("Url must have a domain"));
|
return Err(Error::UrlVerificationError("Url must have a domain"));
|
||||||
};
|
};
|
||||||
if !DOMAIN_REGEX.is_match(domain) {
|
if !domain_regex().is_match(domain) {
|
||||||
return Err(Error::UrlVerificationError("Invalid characters in domain"));
|
return Err(Error::UrlVerificationError("Invalid characters in domain"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,16 @@ pub enum Error {
|
||||||
#[error("Failed to parse object {1} with content {2}: {0}")]
|
#[error("Failed to parse object {1} with content {2}: {0}")]
|
||||||
ParseFetchedObject(serde_json::Error, Url, String),
|
ParseFetchedObject(serde_json::Error, Url, String),
|
||||||
/// Failed to parse an activity received from another instance
|
/// Failed to parse an activity received from another instance
|
||||||
#[error("Failed to parse incoming activity {}: {0}", match .1 {
|
#[error("Failed to parse incoming activity {}: {0}", match .id {
|
||||||
Some(t) => format!("with id {t}"),
|
Some(t) => format!("with id {t}"),
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
})]
|
})]
|
||||||
ParseReceivedActivity(serde_json::Error, Option<Url>),
|
ParseReceivedActivity {
|
||||||
|
/// The parse error
|
||||||
|
err: serde_json::Error,
|
||||||
|
/// ID of the Activitypub object which caused this error
|
||||||
|
id: Option<Url>,
|
||||||
|
},
|
||||||
/// Reqwest Middleware Error
|
/// Reqwest Middleware Error
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
ReqwestMiddleware(#[from] reqwest_middleware::Error),
|
ReqwestMiddleware(#[from] reqwest_middleware::Error),
|
||||||
|
|
|
||||||
|
|
@ -102,92 +102,3 @@ where
|
||||||
self.0.eq(&other.0) && self.1 == other.1
|
self.0.eq(&other.0) && self.1 == other.1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "diesel")]
|
|
||||||
const _IMPL_DIESEL_NEW_TYPE_FOR_COLLECTION_ID: () = {
|
|
||||||
use diesel::{
|
|
||||||
backend::Backend,
|
|
||||||
deserialize::{FromSql, FromStaticSqlRow},
|
|
||||||
expression::AsExpression,
|
|
||||||
internal::derives::as_expression::Bound,
|
|
||||||
pg::Pg,
|
|
||||||
query_builder::QueryId,
|
|
||||||
serialize,
|
|
||||||
serialize::{Output, ToSql},
|
|
||||||
sql_types::{HasSqlType, SingleValue, Text},
|
|
||||||
Expression,
|
|
||||||
Queryable,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: this impl only works for Postgres db because of to_string() call which requires reborrow
|
|
||||||
impl<Kind, ST> ToSql<ST, Pg> for CollectionId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Collection,
|
|
||||||
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
|
|
||||||
String: ToSql<ST, Pg>,
|
|
||||||
{
|
|
||||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
|
|
||||||
let v = self.0.to_string();
|
|
||||||
<String as ToSql<Text, Pg>>::to_sql(&v, &mut out.reborrow())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'expr, Kind, ST> AsExpression<ST> for &'expr CollectionId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Collection,
|
|
||||||
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
|
|
||||||
Bound<ST, String>: Expression<SqlType = ST>,
|
|
||||||
ST: SingleValue,
|
|
||||||
{
|
|
||||||
type Expression = Bound<ST, &'expr str>;
|
|
||||||
fn as_expression(self) -> Self::Expression {
|
|
||||||
Bound::new(self.0.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind, ST> AsExpression<ST> for CollectionId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Collection,
|
|
||||||
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
|
|
||||||
Bound<ST, String>: Expression<SqlType = ST>,
|
|
||||||
ST: SingleValue,
|
|
||||||
{
|
|
||||||
type Expression = Bound<ST, String>;
|
|
||||||
fn as_expression(self) -> Self::Expression {
|
|
||||||
Bound::new(self.0.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind, ST, DB> FromSql<ST, DB> for CollectionId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Collection + Send + 'static,
|
|
||||||
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
|
|
||||||
String: FromSql<ST, DB>,
|
|
||||||
DB: Backend,
|
|
||||||
DB: HasSqlType<ST>,
|
|
||||||
{
|
|
||||||
fn from_sql(
|
|
||||||
raw: DB::RawValue<'_>,
|
|
||||||
) -> Result<Self, Box<dyn ::std::error::Error + Send + Sync>> {
|
|
||||||
let string: String = FromSql::<ST, DB>::from_sql(raw)?;
|
|
||||||
Ok(CollectionId::parse(&string)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind, ST, DB> Queryable<ST, DB> for CollectionId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Collection + Send + 'static,
|
|
||||||
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
|
|
||||||
String: FromStaticSqlRow<ST, DB>,
|
|
||||||
DB: Backend,
|
|
||||||
DB: HasSqlType<ST>,
|
|
||||||
{
|
|
||||||
type Row = String;
|
|
||||||
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
|
|
||||||
Ok(CollectionId::parse(&row)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind> QueryId for CollectionId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Collection + 'static,
|
|
||||||
for<'de2> <Kind as Collection>::Kind: Deserialize<'de2>,
|
|
||||||
{
|
|
||||||
type QueryId = Self;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -271,95 +271,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "diesel")]
|
|
||||||
const _IMPL_DIESEL_NEW_TYPE_FOR_OBJECT_ID: () = {
|
|
||||||
use diesel::{
|
|
||||||
backend::Backend,
|
|
||||||
deserialize::{FromSql, FromStaticSqlRow},
|
|
||||||
expression::AsExpression,
|
|
||||||
internal::derives::as_expression::Bound,
|
|
||||||
pg::Pg,
|
|
||||||
query_builder::QueryId,
|
|
||||||
serialize,
|
|
||||||
serialize::{Output, ToSql},
|
|
||||||
sql_types::{HasSqlType, SingleValue, Text},
|
|
||||||
Expression,
|
|
||||||
Queryable,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: this impl only works for Postgres db because of to_string() call which requires reborrow
|
|
||||||
impl<Kind, ST> ToSql<ST, Pg> for ObjectId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Object,
|
|
||||||
for<'de2> <Kind as Object>::Kind: Deserialize<'de2>,
|
|
||||||
String: ToSql<ST, Pg>,
|
|
||||||
{
|
|
||||||
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
|
|
||||||
let v = self.0.to_string();
|
|
||||||
<String as ToSql<Text, Pg>>::to_sql(&v, &mut out.reborrow())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'expr, Kind, ST> AsExpression<ST> for &'expr ObjectId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Object,
|
|
||||||
for<'de2> <Kind as Object>::Kind: Deserialize<'de2>,
|
|
||||||
Bound<ST, String>: Expression<SqlType = ST>,
|
|
||||||
ST: SingleValue,
|
|
||||||
{
|
|
||||||
type Expression = Bound<ST, &'expr str>;
|
|
||||||
fn as_expression(self) -> Self::Expression {
|
|
||||||
Bound::new(self.0.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind, ST> AsExpression<ST> for ObjectId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Object,
|
|
||||||
for<'de2> <Kind as Object>::Kind: Deserialize<'de2>,
|
|
||||||
Bound<ST, String>: Expression<SqlType = ST>,
|
|
||||||
ST: SingleValue,
|
|
||||||
{
|
|
||||||
type Expression = Bound<ST, String>;
|
|
||||||
fn as_expression(self) -> Self::Expression {
|
|
||||||
Bound::new(self.0.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind, ST, DB> FromSql<ST, DB> for ObjectId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Object + Send + 'static,
|
|
||||||
for<'de2> <Kind as Object>::Kind: Deserialize<'de2>,
|
|
||||||
String: FromSql<ST, DB>,
|
|
||||||
DB: Backend,
|
|
||||||
DB: HasSqlType<ST>,
|
|
||||||
{
|
|
||||||
fn from_sql(
|
|
||||||
raw: DB::RawValue<'_>,
|
|
||||||
) -> Result<Self, Box<dyn ::std::error::Error + Send + Sync>> {
|
|
||||||
let string: String = FromSql::<ST, DB>::from_sql(raw)?;
|
|
||||||
Ok(ObjectId::parse(&string)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind, ST, DB> Queryable<ST, DB> for ObjectId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Object + Send + 'static,
|
|
||||||
for<'de2> <Kind as Object>::Kind: Deserialize<'de2>,
|
|
||||||
String: FromStaticSqlRow<ST, DB>,
|
|
||||||
DB: Backend,
|
|
||||||
DB: HasSqlType<ST>,
|
|
||||||
{
|
|
||||||
type Row = String;
|
|
||||||
fn build(row: Self::Row) -> diesel::deserialize::Result<Self> {
|
|
||||||
Ok(ObjectId::parse(&row)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<Kind> QueryId for ObjectId<Kind>
|
|
||||||
where
|
|
||||||
Kind: Object + 'static,
|
|
||||||
for<'de2> <Kind as Object>::Kind: Deserialize<'de2>,
|
|
||||||
{
|
|
||||||
type QueryId = Self;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Internal only
|
/// Internal only
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{Data, DOMAIN_REGEX},
|
config::{domain_regex, Data},
|
||||||
error::Error,
|
error::Error,
|
||||||
fetch::{fetch_object_http_with_accept, object_id::ObjectId},
|
fetch::{fetch_object_http_with_accept, object_id::ObjectId},
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
|
|
@ -7,10 +7,9 @@ use crate::{
|
||||||
};
|
};
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{collections::HashMap, fmt::Display};
|
use std::{collections::HashMap, fmt::Display, sync::LazyLock};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
@ -56,7 +55,7 @@ where
|
||||||
.ok_or(WebFingerError::WrongFormat.into_crate_error())?;
|
.ok_or(WebFingerError::WrongFormat.into_crate_error())?;
|
||||||
|
|
||||||
// For production mode make sure that domain doesnt contain any port or path.
|
// For production mode make sure that domain doesnt contain any port or path.
|
||||||
if !data.config.debug && !DOMAIN_REGEX.is_match(domain) {
|
if !data.config.debug && !domain_regex().is_match(domain) {
|
||||||
return Err(Error::UrlVerificationError("Invalid characters in domain").into());
|
return Err(Error::UrlVerificationError("Invalid characters in domain").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,8 +129,8 @@ pub fn extract_webfinger_name<'i, T>(query: &'i str, data: &Data<T>) -> Result<&
|
||||||
where
|
where
|
||||||
T: Clone,
|
T: Clone,
|
||||||
{
|
{
|
||||||
static WEBFINGER_REGEX: Lazy<Regex> =
|
static WEBFINGER_REGEX: LazyLock<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^acct:([\p{L}0-9_\.\-]+)@(.*)$").expect("compile regex"));
|
LazyLock::new(|| Regex::new(r"^acct:([\p{L}0-9_\.\-]+)@(.*)$").expect("compile regex"));
|
||||||
// Regex to extract usernames from webfinger query. Supports different alphabets using `\p{L}`.
|
// Regex to extract usernames from webfinger query. Supports different alphabets using `\p{L}`.
|
||||||
// TODO: This should use a URL parser
|
// TODO: This should use a URL parser
|
||||||
let captures = WEBFINGER_REGEX
|
let captures = WEBFINGER_REGEX
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ use http_signature_normalization_reqwest::{
|
||||||
prelude::{Config, SignExt},
|
prelude::{Config, SignExt},
|
||||||
DefaultSpawner,
|
DefaultSpawner,
|
||||||
};
|
};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use reqwest::Request;
|
use reqwest::Request;
|
||||||
use reqwest_middleware::RequestBuilder;
|
use reqwest_middleware::RequestBuilder;
|
||||||
use rsa::{
|
use rsa::{
|
||||||
|
|
@ -30,7 +29,7 @@ use rsa::{
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::{collections::BTreeMap, fmt::Debug, time::Duration};
|
use std::{collections::BTreeMap, fmt::Debug, sync::LazyLock, time::Duration};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
@ -86,9 +85,9 @@ pub(crate) async fn sign_request(
|
||||||
private_key: RsaPrivateKey,
|
private_key: RsaPrivateKey,
|
||||||
http_signature_compat: bool,
|
http_signature_compat: bool,
|
||||||
) -> Result<Request, Error> {
|
) -> Result<Request, Error> {
|
||||||
static CONFIG: Lazy<Config<DefaultSpawner>> =
|
static CONFIG: LazyLock<Config<DefaultSpawner>> =
|
||||||
Lazy::new(|| Config::new().set_expiration(EXPIRES_AFTER));
|
LazyLock::new(|| Config::new().set_expiration(EXPIRES_AFTER));
|
||||||
static CONFIG_COMPAT: Lazy<Config> = Lazy::new(|| {
|
static CONFIG_COMPAT: LazyLock<Config> = LazyLock::new(|| {
|
||||||
Config::new()
|
Config::new()
|
||||||
.mastodon_compat()
|
.mastodon_compat()
|
||||||
.set_expiration(EXPIRES_AFTER)
|
.set_expiration(EXPIRES_AFTER)
|
||||||
|
|
@ -189,7 +188,7 @@ fn verify_signature_inner(
|
||||||
uri: &Uri,
|
uri: &Uri,
|
||||||
public_key: &str,
|
public_key: &str,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
static CONFIG: Lazy<http_signature_normalization::Config> = Lazy::new(|| {
|
static CONFIG: LazyLock<http_signature_normalization::Config> = LazyLock::new(|| {
|
||||||
http_signature_normalization::Config::new()
|
http_signature_normalization::Config::new()
|
||||||
.set_expiration(EXPIRES_AFTER)
|
.set_expiration(EXPIRES_AFTER)
|
||||||
.require_digest()
|
.require_digest()
|
||||||
|
|
@ -292,9 +291,10 @@ pub mod test {
|
||||||
use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePrivateKey};
|
use rsa::{pkcs1::DecodeRsaPrivateKey, pkcs8::DecodePrivateKey};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
static ACTOR_ID: Lazy<Url> = Lazy::new(|| Url::parse("https://example.com/u/alice").unwrap());
|
static ACTOR_ID: LazyLock<Url> =
|
||||||
static INBOX_URL: Lazy<Url> =
|
LazyLock::new(|| Url::parse("https://example.com/u/alice").unwrap());
|
||||||
Lazy::new(|| Url::parse("https://example.com/u/alice/inbox").unwrap());
|
static INBOX_URL: LazyLock<Url> =
|
||||||
|
LazyLock::new(|| Url::parse("https://example.com/u/alice/inbox").unwrap());
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_sign() {
|
async fn test_sign() {
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,10 @@ where
|
||||||
<ActorT as Object>::Error: From<Error>,
|
<ActorT as Object>::Error: From<Error>,
|
||||||
Datatype: Clone,
|
Datatype: Clone,
|
||||||
{
|
{
|
||||||
let activity: Activity = serde_json::from_slice(body).map_err(|e| {
|
let activity: Activity = serde_json::from_slice(body).map_err(|err| {
|
||||||
// Attempt to include activity id in error message
|
// Attempt to include activity id in error message
|
||||||
let id = extract_id(body).ok();
|
let id = extract_id(body).ok();
|
||||||
Error::ParseReceivedActivity(e, id)
|
Error::ParseReceivedActivity { err, id }
|
||||||
})?;
|
})?;
|
||||||
data.config.verify_url_and_domain(&activity).await?;
|
data.config.verify_url_and_domain(&activity).await?;
|
||||||
let actor = ObjectId::<ActorT>::from(activity.actor().clone())
|
let actor = ObjectId::<ActorT>::from(activity.actor().clone())
|
||||||
|
|
|
||||||
|
|
@ -349,8 +349,8 @@ pub mod tests {
|
||||||
protocol::verification::verify_domains_match,
|
protocol::verification::verify_domains_match,
|
||||||
};
|
};
|
||||||
use activitystreams_kinds::{activity::FollowType, actor::PersonType};
|
use activitystreams_kinds::{activity::FollowType, actor::PersonType};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DbConnection;
|
pub struct DbConnection;
|
||||||
|
|
@ -392,9 +392,10 @@ pub mod tests {
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static DB_USER_KEYPAIR: Lazy<Keypair> = Lazy::new(|| generate_actor_keypair().unwrap());
|
pub static DB_USER_KEYPAIR: LazyLock<Keypair> =
|
||||||
|
LazyLock::new(|| generate_actor_keypair().unwrap());
|
||||||
|
|
||||||
pub static DB_USER: Lazy<DbUser> = Lazy::new(|| DbUser {
|
pub static DB_USER: LazyLock<DbUser> = LazyLock::new(|| DbUser {
|
||||||
name: String::new(),
|
name: String::new(),
|
||||||
federation_id: "https://localhost/123".parse().unwrap(),
|
federation_id: "https://localhost/123".parse().unwrap(),
|
||||||
inbox: "https://localhost/123/inbox".parse().unwrap(),
|
inbox: "https://localhost/123/inbox".parse().unwrap(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue