1#![deny(missing_docs)]
4use apub_core::{
5 deliver::Deliver,
6 digest::{Digest, DigestBuilder, DigestFactory},
7 repo::{Dereference, Repo},
8 session::{Session, SessionError},
9 signature::{PrivateKey, Sign},
10};
11use http_signature_normalization_reqwest::{
12 digest::{DigestCreate, SignExt},
13 prelude::{Sign as _, SignError},
14};
15use reqwest::header::{ACCEPT, CONTENT_TYPE, DATE};
16use reqwest_middleware::ClientWithMiddleware;
17use std::time::SystemTime;
18use url::Url;
19
20pub use http_signature_normalization_reqwest::Config as SignatureConfig;
21
22pub struct ReqwestClient<Crypto> {
46 client: ClientWithMiddleware,
47 config: SignatureConfig,
48 crypto: Crypto,
49}
50
51#[derive(Debug, thiserror::Error)]
53pub enum SignatureError<E: std::error::Error + Send> {
54 #[error(transparent)]
56 Sign(#[from] SignError),
57
58 #[error(transparent)]
60 Reqwest(#[from] reqwest::Error),
61
62 #[error(transparent)]
64 Signer(E),
65}
66
67#[derive(Debug, thiserror::Error)]
69pub enum ReqwestError<E: std::error::Error + Send> {
70 #[error("Session indicated request should not procede")]
72 Session(#[from] SessionError),
73
74 #[error(transparent)]
76 Reqwest(#[from] reqwest::Error),
77
78 #[error(transparent)]
80 Middleware(#[from] reqwest_middleware::Error),
81
82 #[error(transparent)]
84 Json(#[from] serde_json::Error),
85
86 #[error("Invalid response code: {0}")]
88 Status(u16),
89
90 #[error(transparent)]
92 SignatureError(#[from] SignatureError<E>),
93}
94
95type SignTraitError<S> = <<S as PrivateKey>::Signer as Sign>::Error;
96
97struct DigestWrapper<D>(D);
98
99impl<D> DigestCreate for DigestWrapper<D>
100where
101 D: Digest + Clone,
102{
103 const NAME: &'static str = D::NAME;
104
105 fn compute(&mut self, input: &[u8]) -> String {
106 self.0.clone().digest(input)
107 }
108}
109
110impl<Crypto> ReqwestClient<Crypto>
111where
112 Crypto: PrivateKey,
113 SignTraitError<Crypto>: std::error::Error,
114{
115 pub fn new(client: ClientWithMiddleware, config: SignatureConfig, crypto: Crypto) -> Self {
117 Self {
118 client,
119 config,
120 crypto,
121 }
122 }
123
124 async fn do_fetch<Id>(
125 &self,
126 url: &Url,
127 ) -> Result<Option<<Id as Dereference>::Output>, ReqwestError<SignTraitError<Crypto>>>
128 where
129 Id: Dereference,
130 {
131 let request = self
132 .client
133 .get(url.as_str())
134 .header(ACCEPT, "application/activity+json")
135 .header(DATE, httpdate::fmt_http_date(SystemTime::now()))
136 .signature(&self.config, self.crypto.key_id(), {
137 let sign = self.crypto.signer();
138
139 move |signing_string| sign.sign(signing_string).map_err(SignatureError::Signer)
140 })?;
141
142 let response = self.client.execute(request).await?;
143
144 Ok(Some(response.json().await?))
145 }
146}
147
148#[async_trait::async_trait(?Send)]
149impl<Crypto> Repo for ReqwestClient<Crypto>
150where
151 Crypto: PrivateKey + Send + Sync,
152 SignTraitError<Crypto>: std::error::Error,
153{
154 type Error = ReqwestError<SignTraitError<Crypto>>;
155
156 async fn fetch<D: Dereference, S: Session>(
157 &self,
158 id: D,
159 session: S,
160 ) -> Result<Option<D::Output>, Self::Error> {
161 apub_core::session::guard(self.do_fetch::<D>(id.url()), id.url(), session).await
162 }
163}
164
165#[async_trait::async_trait(?Send)]
166impl<Crypto> Deliver for ReqwestClient<Crypto>
167where
168 Crypto: DigestFactory + PrivateKey + Send + Sync,
169 <Crypto as DigestFactory>::Digest: DigestBuilder + Clone,
170 SignTraitError<Crypto>: std::error::Error,
171{
172 type Error = ReqwestError<SignTraitError<Crypto>>;
173
174 async fn deliver<T: serde::ser::Serialize, S: Session>(
175 &self,
176 inbox: &Url,
177 activity: &T,
178 session: S,
179 ) -> Result<(), Self::Error> {
180 apub_core::session::guard(
181 async move {
182 let activity_string = serde_json::to_string(activity)?;
183
184 let request = self
185 .client
186 .post(inbox.as_str())
187 .header(CONTENT_TYPE, "application/activity+json")
188 .header(ACCEPT, "application/activity+json")
189 .header(DATE, httpdate::fmt_http_date(SystemTime::now()))
190 .signature_with_digest(
191 self.config.clone(),
192 self.crypto.key_id(),
193 DigestWrapper(Crypto::Digest::build()),
194 activity_string,
195 {
196 let signer = self.crypto.signer();
197 move |signing_string| {
198 signer.sign(signing_string).map_err(SignatureError::Signer)
199 }
200 },
201 )
202 .await?;
203
204 let response = self.client.execute(request).await?;
205
206 if !response.status().is_success() {
207 return Err(ReqwestError::Status(response.status().as_u16()));
208 }
209
210 Ok(())
211 },
212 inbox,
213 session,
214 )
215 .await
216 }
217}