apub_publickey/
lib.rs

1use apub_core::{
2    activitypub::PublicKey,
3    repo::{Dereference, Repo},
4    session::Session,
5};
6use apub_privatekey::KeyId;
7use url::{Host, Url};
8
9#[async_trait::async_trait(?Send)]
10pub trait PublicKeyRepo {
11    type Error: std::error::Error;
12
13    async fn store(&self, public_key: &SimplePublicKey) -> Result<(), Self::Error>;
14
15    async fn fetch(&self, key_id: &KeyId) -> Result<Option<SimplePublicKey>, Self::Error>;
16}
17
18pub trait PublicKeyRepoFactory {
19    type PublicKeyRepo: PublicKeyRepo;
20
21    fn public_key_repo(&self) -> Self::PublicKeyRepo;
22}
23
24pub struct PublicKeyClient<LocalRepo, RemoteRepo> {
25    local_host: Host<String>,
26    local_port: Option<u16>,
27    local: LocalRepo,
28    remote: RemoteRepo,
29}
30
31#[derive(Debug, thiserror::Error)]
32pub enum PublicKeyError<E, R> {
33    #[error("Public Key's Owner doesn't match fetched Actor")]
34    OwnerMismatch,
35
36    #[error("{0}")]
37    PublicKeyRepo(#[source] E),
38
39    #[error("{0}")]
40    RemoteRepo(#[source] R),
41}
42
43struct PublicKeyId<'a>(&'a KeyId);
44
45#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct SimplePublicKey {
48    pub id: KeyId,
49    pub owner: Url,
50    pub public_key_pem: String,
51}
52
53#[derive(Clone, Debug, serde::Deserialize)]
54#[serde(untagged)]
55#[serde(rename_all = "camelCase")]
56pub enum PublicKeyResponse {
57    /// I dream of the day when asking for a Public Key gives me a public key
58    PublicKey(SimplePublicKey),
59
60    /// Getting an Actor from a PublicKey fetch is a bug IMO but mastodon does it so :shrug:
61    Actor {
62        id: Url,
63        public_key: SimplePublicKey,
64    },
65}
66
67impl<LocalRepo, RemoteRepo> PublicKeyClient<LocalRepo, RemoteRepo>
68where
69    LocalRepo: PublicKeyRepo,
70    RemoteRepo: Repo,
71{
72    pub fn new(
73        local: LocalRepo,
74        remote: RemoteRepo,
75        local_host: Host<String>,
76        local_port: Option<u16>,
77    ) -> Self {
78        PublicKeyClient {
79            local_host,
80            local_port,
81            local,
82            remote,
83        }
84    }
85
86    pub async fn find<S: Session>(
87        &self,
88        key_id: &KeyId,
89        session: S,
90    ) -> Result<Option<SimplePublicKey>, PublicKeyError<LocalRepo::Error, RemoteRepo::Error>> {
91        let opt = self
92            .local
93            .fetch(key_id)
94            .await
95            .map_err(PublicKeyError::PublicKeyRepo)?;
96
97        if self.is_local(key_id) {
98            return Ok(opt);
99        }
100
101        let response = self
102            .remote
103            .fetch(PublicKeyId(key_id), session)
104            .await
105            .map_err(PublicKeyError::RemoteRepo)?;
106
107        if let Some(response) = response {
108            let public_key = match response {
109                PublicKeyResponse::Actor { id, public_key } if id == public_key.owner => public_key,
110                PublicKeyResponse::PublicKey(public_key) => public_key,
111                _ => return Err(PublicKeyError::OwnerMismatch),
112            };
113
114            self.local
115                .store(&public_key)
116                .await
117                .map_err(PublicKeyError::PublicKeyRepo)?;
118
119            return Ok(Some(public_key));
120        }
121
122        Ok(None)
123    }
124
125    fn is_local(&self, url: &Url) -> bool {
126        url.host() == Some(borrow_host(&self.local_host)) && url.port() == self.local_port
127    }
128}
129
130fn borrow_host(host: &Host<String>) -> Host<&str> {
131    match host {
132        Host::Ipv4(ip) => Host::Ipv4(*ip),
133        Host::Ipv6(ip) => Host::Ipv6(*ip),
134        Host::Domain(ref domain) => Host::Domain(domain),
135    }
136}
137
138impl<'a> Dereference for PublicKeyId<'a> {
139    type Output = PublicKeyResponse;
140
141    fn url(&self) -> &Url {
142        self.0
143    }
144}
145
146impl PublicKey for SimplePublicKey {
147    fn id(&self) -> &Url {
148        &self.id
149    }
150
151    fn owner(&self) -> &Url {
152        &self.owner
153    }
154
155    fn public_key_pem(&self) -> &str {
156        &self.public_key_pem
157    }
158}