apub-publickey 0.1.0

Utilities for building activitypub servers
Documentation
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 {
    /// I dream of the day when asking for a Public Key gives me a public key
    PublicKey(SimplePublicKey),

    /// Getting an Actor from a PublicKey fetch is a bug IMO but mastodon does it so :shrug:
    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
    }
}