use apub_core::{
activitypub::PublicKey,
repo::{Dereference, Repo},
session::Session,
};
use apub_privatekey::KeyId;
use url::{Host, Url};
#[async_trait::async_trait(?Send)]
pub trait PublicKeyRepo {
type Error: std::error::Error;
async fn store(&self, public_key: &SimplePublicKey) -> Result<(), Self::Error>;
async fn fetch(&self, key_id: &KeyId) -> Result<Option<SimplePublicKey>, Self::Error>;
}
pub trait PublicKeyRepoFactory {
type PublicKeyRepo: PublicKeyRepo;
fn public_key_repo(&self) -> Self::PublicKeyRepo;
}
pub struct PublicKeyClient<LocalRepo, RemoteRepo> {
local_host: Host<String>,
local_port: Option<u16>,
local: LocalRepo,
remote: RemoteRepo,
}
#[derive(Debug, thiserror::Error)]
pub enum PublicKeyError<E, R> {
#[error("Public Key's Owner doesn't match fetched Actor")]
OwnerMismatch,
#[error("{0}")]
PublicKeyRepo(#[source] E),
#[error("{0}")]
RemoteRepo(#[source] R),
}
struct PublicKeyId<'a>(&'a KeyId);
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct SimplePublicKey {
pub id: KeyId,
pub owner: Url,
pub public_key_pem: String,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(untagged)]
#[serde(rename_all = "camelCase")]
pub enum PublicKeyResponse {
PublicKey(SimplePublicKey),
Actor {
id: Url,
public_key: SimplePublicKey,
},
}
impl<LocalRepo, RemoteRepo> PublicKeyClient<LocalRepo, RemoteRepo>
where
LocalRepo: PublicKeyRepo,
RemoteRepo: Repo,
{
pub fn new(
local: LocalRepo,
remote: RemoteRepo,
local_host: Host<String>,
local_port: Option<u16>,
) -> Self {
PublicKeyClient {
local_host,
local_port,
local,
remote,
}
}
pub async fn find<S: Session>(
&self,
key_id: &KeyId,
session: S,
) -> Result<Option<SimplePublicKey>, PublicKeyError<LocalRepo::Error, RemoteRepo::Error>> {
let opt = self
.local
.fetch(key_id)
.await
.map_err(PublicKeyError::PublicKeyRepo)?;
if self.is_local(key_id) {
return Ok(opt);
}
let response = self
.remote
.fetch(PublicKeyId(key_id), session)
.await
.map_err(PublicKeyError::RemoteRepo)?;
if let Some(response) = response {
let public_key = match response {
PublicKeyResponse::Actor { id, public_key } if id == public_key.owner => public_key,
PublicKeyResponse::PublicKey(public_key) => public_key,
_ => return Err(PublicKeyError::OwnerMismatch),
};
self.local
.store(&public_key)
.await
.map_err(PublicKeyError::PublicKeyRepo)?;
return Ok(Some(public_key));
}
Ok(None)
}
fn is_local(&self, url: &Url) -> bool {
url.host() == Some(borrow_host(&self.local_host)) && url.port() == self.local_port
}
}
fn borrow_host(host: &Host<String>) -> Host<&str> {
match host {
Host::Ipv4(ip) => Host::Ipv4(*ip),
Host::Ipv6(ip) => Host::Ipv6(*ip),
Host::Domain(ref domain) => Host::Domain(domain),
}
}
impl<'a> Dereference for PublicKeyId<'a> {
type Output = PublicKeyResponse;
fn url(&self) -> &Url {
self.0
}
}
impl PublicKey for SimplePublicKey {
fn id(&self) -> &Url {
&self.id
}
fn owner(&self) -> &Url {
&self.owner
}
fn public_key_pem(&self) -> &str {
&self.public_key_pem
}
}