Breaking changes to FederationRepository, ApObjectHandler, ApUser:
FederationRepository:
- add is_activity_processed / mark_activity_processed (inbox idempotency)
- add get_accepted_follower_inboxes (DB-side dedup/filtering, replaces in-memory load-all)
ApObjectHandler:
- add on_announce_of_remote (cross-server boosts, previously silently dropped)
ApUser:
- add manually_approves_followers: bool
- add actor_type: ApActorType (was hardcoded Person)
Security:
- block check before actor HTTP fetch in Follow (prevents SSRF on blocked actors)
- 4xx responses use generic "not found"/"bad request" (no internal leak)
- 1 MB DefaultBodyLimit on inbox routes
- zeroize private key after generation
Delivery:
- all broadcasts are now non-blocking (tokio::spawn fallback, or EventPublisher queue)
- EventPublisher redesigned with typed FederationEvent enum (DeliveryRequested/DeliveryFailed)
- new deliver_to_inbox() public method for queue consumers
- configurable delivery_max_attempts and delivery_initial_delay_secs via builder
- Follow saved as Pending BEFORE delivery (race condition fix)
Router:
- GET /users/{id} (actor), GET /users/{id}/followers, GET /users/{id}/following now mounted
Protocol:
- mention extraction from Create/Update tag arrays → on_mention() dispatched
- WebFinger: add aliases field (acct: URI + AP actor URL)
- outbox: add last link, use count_local_posts for totalItems
- idempotency guard added to every inbound activity receive()
- actor serializes display_name and configurable actor_type/manually_approves_followers
Bump: 0.1.10 → 0.2.0
52 lines
1.5 KiB
Rust
52 lines
1.5 KiB
Rust
use std::fmt::{Display, Formatter};
|
|
|
|
use axum::http::StatusCode;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Error(pub(crate) anyhow::Error, pub(crate) StatusCode);
|
|
|
|
impl Error {
|
|
pub fn not_found(e: impl Into<anyhow::Error>) -> Self {
|
|
Self(e.into(), StatusCode::NOT_FOUND)
|
|
}
|
|
|
|
pub fn bad_request(e: impl Into<anyhow::Error>) -> Self {
|
|
Self(e.into(), StatusCode::BAD_REQUEST)
|
|
}
|
|
}
|
|
|
|
impl Display for Error {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
std::fmt::Display::fmt(&self.0, f)
|
|
}
|
|
}
|
|
|
|
impl<T> From<T> for Error
|
|
where
|
|
T: Into<anyhow::Error>,
|
|
{
|
|
fn from(t: T) -> Self {
|
|
Error(t.into(), StatusCode::INTERNAL_SERVER_ERROR)
|
|
}
|
|
}
|
|
|
|
impl axum::response::IntoResponse for Error {
|
|
fn into_response(self) -> axum::response::Response {
|
|
let status = self.1;
|
|
// Always log the real error internally; never expose it to the client.
|
|
if status.is_server_error() {
|
|
tracing::error!(error = %self.0, status = status.as_u16(), "federation error");
|
|
} else {
|
|
tracing::debug!(error = %self.0, status = status.as_u16(), "federation client error");
|
|
}
|
|
let body = match status {
|
|
StatusCode::NOT_FOUND => "not found",
|
|
StatusCode::BAD_REQUEST => "bad request",
|
|
StatusCode::UNAUTHORIZED => "unauthorized",
|
|
StatusCode::FORBIDDEN => "forbidden",
|
|
_ => "internal server error",
|
|
};
|
|
(status, body).into_response()
|
|
}
|
|
}
|