apub/examples/actix-web-example/src/main.rs

425 lines
11 KiB
Rust

use actix_web::{middleware::Logger, web, App, FromRequest, HttpServer, ResponseError};
use apub::{
activitypub::keys::{
publickey::{PublicKeyError, PublicKeyRepoFactory},
KeyId, PrivateKeyRepo, PrivateKeyRepoFactory, PublicKeyRepo, SimplePublicKey,
},
clients::{
awc::{AwcError, SignatureConfig as AwcSignatureConfig},
AwcClient,
},
cryptography::{
rustcrypto::{RsaVerifier, RustcryptoError, Sha256Digest},
DigestFactory, Rustcrypto, VerifyFactory,
},
ingest::{
validate_authority, validate_hosts, validate_inbox,
validators::{
AuthorityError, HostError, InboxError, InboxType, ValidateAuthority, ValidateHosts,
ValidateInbox,
},
Authority,
},
servers::actix_web::{
inbox, serve_objects, Config, SignatureConfig as ActixWebSignatureConfig, VerifyError,
},
session::{BreakerSession, RequestCountSession, SessionFactory},
Dereference, FullRepo, Ingest, Repo, RepoFactory, Session,
};
use dashmap::DashMap;
use example_types::{AcceptedActivity, ActivityType, ObjectId};
use rsa::{pkcs8::ToPrivateKey, RsaPrivateKey};
use std::{
future::{ready, Ready},
sync::Arc,
time::Duration,
};
use url::{Host, Url};
#[derive(Clone, Default)]
struct MemoryRepo {
inner: Arc<DashMap<Url, serde_json::Value>>,
}
#[derive(Clone, Default)]
struct PrivateKeyStore {
inner: Arc<DashMap<Url, (KeyId, String)>>,
}
#[derive(Clone)]
struct RequestVerifier {
repo: MemoryRepo,
private_key_store: PrivateKeyStore,
session: BreakerSession,
client: awc::Client,
signature_config: AwcSignatureConfig,
}
#[derive(Clone)]
struct ActivityIngester {
repo: MemoryRepo,
ingest: ValidateAuthority<ValidateInbox<ValidateHosts<MemoryRepo>>>,
private_key_store: PrivateKeyStore,
session: BreakerSession,
client: awc::Client,
signature_config: AwcSignatureConfig,
config: Config,
}
#[derive(Debug, thiserror::Error)]
enum ServerError {
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Verify(#[from] VerifyError),
#[error(transparent)]
Rustcrypto(#[from] RustcryptoError),
#[error("Public key owner doesn't match actor ID")]
OwnerMismatch,
#[error(transparent)]
Awc(#[from] AwcError<RustcryptoError>),
#[error(transparent)]
Key(#[from] KeyError),
#[error(transparent)]
Host(#[from] HostError),
#[error(transparent)]
Inbox(#[from] InboxError),
#[error(transparent)]
Authority(#[from] AuthorityError),
}
impl From<PublicKeyError<serde_json::Error, AwcError<RustcryptoError>>> for ServerError {
fn from(e: PublicKeyError<serde_json::Error, AwcError<RustcryptoError>>) -> Self {
match e {
PublicKeyError::RemoteRepo(awc_error) => awc_error.into(),
PublicKeyError::PublicKeyRepo(json_error) => json_error.into(),
PublicKeyError::OwnerMismatch => Self::OwnerMismatch,
}
}
}
impl MemoryRepo {
fn insert<D>(&self, id: D, item: &D::Output) -> Result<(), serde_json::Error>
where
D: Dereference,
D::Output: serde::ser::Serialize,
{
let value = serde_json::to_value(item)?;
self.inner.insert(id.url().clone(), value);
Ok(())
}
}
impl ResponseError for ServerError {}
#[async_trait::async_trait(?Send)]
impl Repo for MemoryRepo {
type Error = serde_json::Error;
async fn fetch<D: Dereference, S: Session>(
&self,
id: D,
_session: S,
) -> Result<Option<D::Output>, Self::Error> {
if let Some(obj_ref) = self.inner.get(id.url()) {
serde_json::from_value(obj_ref.clone()).map(Some)
} else {
Ok(None)
}
}
}
#[async_trait::async_trait(?Send)]
impl PublicKeyRepo for MemoryRepo {
type Error = serde_json::Error;
async fn fetch(&self, key_id: &KeyId) -> Result<Option<SimplePublicKey>, Self::Error> {
if let Some(obj_ref) = self.inner.get(key_id) {
return serde_json::from_value(obj_ref.clone()).map(Some);
}
Ok(None)
}
async fn store(&self, public_key: &SimplePublicKey) -> Result<(), Self::Error> {
let value = serde_json::to_value(public_key)?;
self.inner.insert(public_key.id.as_ref().clone(), value);
Ok(())
}
}
#[derive(Clone, Debug)]
struct KeyError;
impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Key is missing")
}
}
impl std::error::Error for KeyError {}
#[async_trait::async_trait(?Send)]
impl PrivateKeyRepo for PrivateKeyStore {
type Error = KeyError;
async fn fetch(&self, actor_id: &Url) -> Result<(KeyId, String), Self::Error> {
if let Some(tup_ref) = self.inner.get(actor_id) {
return Ok(tup_ref.clone());
}
Err(KeyError)
}
async fn store(
&self,
actor_id: Url,
key_id: KeyId,
private_key_pem: String,
) -> Result<(), Self::Error> {
self.inner.insert(actor_id, (key_id, private_key_pem));
Ok(())
}
}
impl PrivateKeyRepoFactory for RequestVerifier {
type PrivateKeyRepo = PrivateKeyStore;
fn build_private_key_repo(&self) -> Self::PrivateKeyRepo {
self.private_key_store.clone()
}
}
impl PrivateKeyRepoFactory for ActivityIngester {
type PrivateKeyRepo = PrivateKeyStore;
fn build_private_key_repo(&self) -> Self::PrivateKeyRepo {
self.private_key_store.clone()
}
}
impl RepoFactory for MemoryRepo {
type Crypto = ();
type Repo = MemoryRepo;
fn build_repo(&self, _: Self::Crypto) -> Self::Repo {
self.clone()
}
}
impl VerifyFactory for RequestVerifier {
type Verify = RsaVerifier;
}
impl RepoFactory for RequestVerifier {
type Crypto = Rustcrypto;
type Repo = AwcClient<Rustcrypto>;
fn build_repo(&self, crypto: Self::Crypto) -> Self::Repo {
AwcClient::new(self.client.clone(), self.signature_config.clone(), crypto)
}
}
impl PublicKeyRepoFactory for RequestVerifier {
type PublicKeyRepo = MemoryRepo;
fn public_key_repo(&self) -> Self::PublicKeyRepo {
self.repo.clone()
}
}
impl SessionFactory for RequestVerifier {
type Session = (RequestCountSession, BreakerSession);
fn build_session(&self) -> Self::Session {
(RequestCountSession::max(25), self.session.clone())
}
}
impl DigestFactory for RequestVerifier {
type Digest = Sha256Digest;
}
struct InboxActor(Url);
impl FromRequest for InboxActor {
type Error = actix_web::Error;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(_: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
ready(Ok(InboxActor(
Url::parse("http://localhost:8008/actor").unwrap(),
)))
}
}
impl InboxType for InboxActor {
fn is_shared(&self) -> bool {
true
}
}
impl AsRef<Url> for InboxActor {
fn as_ref(&self) -> &Url {
&self.0
}
}
#[async_trait::async_trait(?Send)]
impl Ingest<AcceptedActivity> for MemoryRepo {
type Error = ServerError;
type ActorId = InboxActor;
async fn ingest<R: FullRepo, S: Session>(
&self,
_authority: Authority,
_actor_id: Self::ActorId,
activity: AcceptedActivity,
_repo: R,
_remote_repo: &R::Remote,
_session: S,
) -> Result<(), R::Error>
where
R::Error: From<Self::Error>,
{
Ok(self
.insert(activity.id().clone(), &activity)
.map_err(Self::Error::from)?)
}
}
impl RepoFactory for ActivityIngester {
type Crypto = ();
type Repo = MemoryRepo;
fn build_repo(&self, _: Self::Crypto) -> Self::Repo {
self.repo.clone()
}
}
impl SessionFactory for ActivityIngester {
type Session = (RequestCountSession, BreakerSession);
fn build_session(&self) -> Self::Session {
(RequestCountSession::max(25), self.session.clone())
}
}
impl FullRepo for ActivityIngester {
type Error = ServerError;
type Local = MemoryRepo;
type Crypto = Rustcrypto;
type Remote = AwcClient<Rustcrypto>;
type Ingest = ValidateAuthority<ValidateInbox<ValidateHosts<MemoryRepo>>>;
fn local(&self) -> &Self::Local {
&self.repo
}
fn remote(&self, crypto: Self::Crypto) -> Self::Remote {
AwcClient::new(self.client.clone(), self.signature_config.clone(), crypto)
}
fn ingest(&self) -> &Self::Ingest {
&self.ingest
}
fn local_port(&self) -> Option<u16> {
self.config.local_port
}
fn local_domain(&self) -> &Host<String> {
&self.config.local_host
}
}
#[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
if std::env::var("RUST_LOG").is_ok() {
env_logger::builder().init()
} else {
env_logger::Builder::new()
.filter_level(log::LevelFilter::Info)
.init();
};
let repo = MemoryRepo::default();
let private_key_store = PrivateKeyStore::default();
let session = BreakerSession::limit(10, Duration::from_secs(60 * 60));
let signature_config = ActixWebSignatureConfig::default();
let config = Config {
local_host: Host::Domain("localhost".to_string()),
local_port: Some(8008),
scheme: "http://".to_string(),
server_actor_id: "http://localhost:8008/actor".parse()?,
};
let awc_signature_config = AwcSignatureConfig::default();
let private_key = RsaPrivateKey::new(&mut rand::thread_rng(), 1024)?;
let private_key_pem = private_key.to_pkcs8_pem()?;
let actor_id: Url = "http://localhost:8008/actor".parse()?;
let key_id: KeyId = "http://localhost:8008/actor#main-key".parse()?;
private_key_store
.store(actor_id, key_id, private_key_pem.to_string())
.await?;
HttpServer::new(move || {
let client = awc::Client::new();
let verifier = RequestVerifier {
repo: repo.clone(),
private_key_store: private_key_store.clone(),
session: session.clone(),
client: client.clone(),
signature_config: awc_signature_config.clone(),
};
let ingester = ActivityIngester {
repo: repo.clone(),
ingest: validate_authority(validate_inbox(validate_hosts(repo.clone()))),
private_key_store: private_key_store.clone(),
session: session.clone(),
client,
signature_config: awc_signature_config.clone(),
config: config.clone(),
};
App::new()
.wrap(Logger::default())
.service(
web::scope("/shared_inbox").configure(inbox::<_, _, _, ServerError>(
config.clone(),
ingester,
verifier.clone(),
signature_config.clone(),
false,
)),
)
.service(web::scope("/activities").configure(serve_objects::<
ObjectId<ActivityType>,
_,
_,
ServerError,
>(
config.clone(),
repo.clone(),
verifier,
signature_config.clone(),
false,
)))
})
.bind("127.0.0.1:8008")?
.run()
.await?;
Ok(())
}