1use std::fmt;
2
3use base64::{engine::general_purpose, Engine as _};
4use chrono::Utc;
5use reqwest::{
6 header::{HeaderMap, HeaderValue},
7 RequestBuilder,
8};
9use thiserror::Error;
10
11#[derive(Error, Debug)]
12pub enum RequestError {
13 #[error("reqwest failed")]
14 ReqwestError(#[from] reqwest::Error),
15 #[error("unable to create authorization")]
16 AuthConstructionError,
17 #[error("bootstrapping encrypted requst failed.")]
18 ReKeyError,
19 #[error("handling the response failed")]
20 HandlingResponse(#[from] crate::client::ResponseError),
21 #[error("the argument provided was not one that can be handled")]
22 InvalidArgument,
23 #[error("the request could not be encrypted")]
24 EncryptionError,
25 #[error("the token provided has expired, and could not be renewed")]
26 TokenExpired,
27}
28
29#[derive(Debug, Clone)]
56pub struct Request<UT, RT>
57where
58 UT: UpdateTokenTrait,
59 RT: RequestTrait,
60{
61 pub client: reqwest::Client,
62 pub endpoint: String,
63 pub token: Option<crate::Token>,
64 pub ut: Option<UT>,
65 pub rt: Option<RT>,
66 ek: Option<crate::rocket::ExportableEncryptionKeyData>,
67}
68
69#[derive(Debug, Clone)]
70pub enum Method {
71 Get,
72 Post,
73 Put,
74 Patch,
75 Delete,
76}
77
78impl fmt::Display for Method {
79 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
80 write!(f, "{:?}", self)
81 }
82}
83
84pub trait UpdateTokenTrait: Send + Sync {
85 fn token_update(&self, _token: crate::Token) -> bool {
88 return true;
89 }
90}
91
92pub trait RequestTrait: Send + Sync {
93 fn before(&self, builder: RequestBuilder) -> RequestBuilder {
95 return builder;
96 }
97
98 fn after(&self, _response: crate::client::Response) {
100 return;
101 }
102}
103
104impl<UT: UpdateTokenTrait, RT: RequestTrait> Request<UT, RT> {
105 pub fn new_simple(
107 client: reqwest::Client,
108 endpoint: &str,
109 token: Option<crate::Token>,
110 ) -> Self {
111 return Self::new(client, endpoint, token, None, None);
112 }
113
114 pub fn new(
116 client: reqwest::Client,
117 endpoint: &str,
118 token: Option<crate::Token>,
119 ut: Option<UT>,
120 rt: Option<RT>,
121 ) -> Self {
122 Self {
123 client,
124 endpoint: endpoint.to_string(),
125 token,
126 ut,
127 rt,
128 ek: None,
129 }
130 }
131
132 pub fn update_token(&mut self, token: Option<crate::Token>) {
134 self.token = token.clone();
135
136 match &self.ut {
137 Some(callback) => match token {
138 Some(token) => {
139 callback.token_update(token);
140 }
141 None => {}
142 },
143 None => {}
144 };
145 }
146
147 #[async_recursion::async_recursion]
151 pub async fn rekey(&mut self, hashid: Option<String>) -> Result<bool, RequestError> {
152 let kp = crate::Keypair::new();
153 let mut headers = HeaderMap::new();
154 headers.insert(
155 "Content-Type",
156 HeaderValue::from_str(&"application/json").unwrap(),
157 );
158
159 match hashid.clone() {
160 Some(hashid) => {
161 headers.insert(
162 "Accept",
163 HeaderValue::from_str(&"application/vnd.ncryptf+json").unwrap(),
164 );
165 headers.insert("X-HashId", HeaderValue::from_str(&hashid).unwrap());
166 let pk = general_purpose::STANDARD.encode(kp.get_public_key());
167 headers.insert("X-PubKey", HeaderValue::from_str(&pk).unwrap());
168 }
169 _ => {
170 headers.insert(
171 "Accept",
172 HeaderValue::from_str(&"application/json").unwrap(),
173 );
174 }
175 };
176
177 let furi = format!("{}{}", self.endpoint, "/ncryptf/ek");
178 let builder = self.client.clone().get(furi).headers(headers);
179
180 match self.do_request(builder, kp).await {
181 Ok(response) => match response.status {
182 reqwest::StatusCode::OK => match serde_json::from_str::<
183 crate::rocket::ExportableEncryptionKeyData,
184 >(&response.body.unwrap())
185 {
186 Ok(ek) => {
187 self.ek = Some(ek.clone());
188 match hashid.clone() {
189 Some(_) => return Ok(true),
190 _ => return self.rekey(Some(ek.hash_id)).await,
191 }
192 }
193 Err(_error) => return Err(RequestError::ReKeyError),
194 },
195 _ => return Err(RequestError::ReKeyError),
196 },
197 Err(_error) => return Err(RequestError::ReKeyError),
198 };
199 }
200
201 pub async fn get(&mut self, url: &str) -> Result<crate::client::Response, RequestError> {
203 return self.execute(Method::Get, url, None).await;
204 }
205
206 pub async fn delete(
208 &mut self,
209 url: &str,
210 payload: Option<&str>,
211 ) -> Result<crate::client::Response, RequestError> {
212 return self.execute(Method::Delete, url, payload).await;
213 }
214
215 pub async fn patch(
217 &mut self,
218 url: &str,
219 payload: Option<&str>,
220 ) -> Result<crate::client::Response, RequestError> {
221 return self.execute(Method::Patch, url, payload).await;
222 }
223
224 pub async fn post(
226 &mut self,
227 url: &str,
228 payload: Option<&str>,
229 ) -> Result<crate::client::Response, RequestError> {
230 return self.execute(Method::Post, url, payload).await;
231 }
232
233 pub async fn put(
235 &mut self,
236 url: &str,
237 payload: Option<&str>,
238 ) -> Result<crate::client::Response, RequestError> {
239 return self.execute(Method::Put, url, payload).await;
240 }
241
242 #[async_recursion::async_recursion]
250 async fn execute(
251 &mut self,
252 method: Method,
253 url: &str,
254 payload: Option<&'async_recursion str>,
255 ) -> Result<crate::client::Response, RequestError> {
256 let payload_actual = match payload {
257 Some(payload) => payload,
258 None => "",
259 };
260
261 match &self.ek {
262 Some(ek) => {
263 if ek.is_expired() {
264 match self.rekey(None).await {
265 Ok(_) => {}
266 Err(error) => return Err(error),
267 };
268 }
269 }
270 _ => match self.rekey(None).await {
271 Ok(_) => {}
272 Err(error) => return Err(error),
273 },
274 };
275
276 let auth: Option<crate::Authorization> = match self.token.clone() {
277 Some(mut token) => {
278 let expiration_limit = chrono::Utc::now().timestamp() + 120;
280 if token.expires_at <= expiration_limit {
281 let refresh_token = token.refresh_token;
282 self.token = None;
284
285 match self
286 .post(
287 format!("/ncryptf/token/refresh?refresh_token={}", refresh_token)
288 .as_str(),
289 None,
290 )
291 .await
292 {
293 Ok(response) => match response.status {
294 reqwest::StatusCode::OK => match response.into::<crate::Token>() {
295 Ok(tt) => {
296 self.update_token(Some(tt.clone()));
297 token = self.token.clone().unwrap();
298 }
299 Err(_error) => return Err(RequestError::TokenExpired),
300 },
301 _ => return Err(RequestError::TokenExpired),
302 },
303 Err(_error) => return Err(RequestError::TokenExpired),
304 };
305 }
306
307 match crate::Authorization::from(
309 method.to_string().to_uppercase(),
310 url.to_string().clone(),
311 token.clone(),
312 Utc::now(),
313 payload_actual.to_string(),
314 None,
315 None,
316 ) {
317 Ok(auth) => Some(auth),
318 Err(_error) => return Err(RequestError::AuthConstructionError),
319 }
320 }
321 None => None,
322 };
323
324 let kp = crate::Keypair::new();
325
326 let mut headers = HeaderMap::new();
327 headers.insert(
328 "Accept",
329 HeaderValue::from_str(&"application/vnd.ncryptf+json").unwrap(),
330 );
331 headers.insert(
333 "X-PubKey",
334 HeaderValue::from_str(&general_purpose::STANDARD.encode(kp.get_public_key())).unwrap(),
335 );
336 headers.insert(
337 "X-HashId",
338 HeaderValue::from_str(&self.ek.clone().unwrap().hash_id).unwrap(),
339 );
340
341 match auth {
342 Some(auth) => {
343 headers.insert(
344 "Authorization",
345 HeaderValue::from_str(auth.get_header().as_str()).unwrap(),
346 );
347 }
348 _ => {}
349 }
350
351 let furi = format!("{}{}", self.endpoint, url);
352 let mut builder: reqwest::RequestBuilder = match method {
353 Method::Get => self.client.clone().get(furi),
354 Method::Post => self.client.clone().post(furi),
355 Method::Put => self.client.clone().put(furi),
356 Method::Delete => self.client.clone().delete(furi),
357 Method::Patch => self.client.clone().patch(furi),
358 };
359
360 match payload_actual {
361 "" => {
362 headers.insert(
363 "Content-Type",
364 HeaderValue::from_str(&"application/json").unwrap(),
365 );
366 }
367 _ => {
368 headers.insert(
369 "Content-Type",
370 HeaderValue::from_str(&"application/vnd.ncryptf+json").unwrap(),
371 );
372 let sk = match self.token.clone() {
373 Some(token) => token.signature,
374 None => {
375 let sk = crate::Signature::new();
376 sk.get_secret_key()
377 }
378 };
379
380 let mut request = crate::Request::from(kp.get_secret_key(), sk).unwrap();
381 match request.encrypt(
382 payload_actual.to_string(),
383 self.ek.as_ref().unwrap().clone().get_public_key().unwrap(),
384 ) {
385 Ok(body) => {
386 builder = builder.body(general_purpose::STANDARD.encode(body));
387 }
388 Err(_error) => return Err(RequestError::EncryptionError),
389 }
390 }
391 }
392
393 builder = match &self.rt {
395 Some(rt) => rt.before(builder),
396 None => builder,
397 };
398 builder = builder.headers(headers);
399
400 match self.do_request(builder, kp).await {
401 Ok(response) => match &self.rt {
402 Some(rt) => {
403 rt.after(response.clone());
404 return Ok(response);
405 }
406 None => return Ok(response),
407 },
408 Err(error) => return Err(error),
409 };
410 }
411
412 async fn do_request(
414 &mut self,
415 builder: reqwest::RequestBuilder,
416 kp: crate::Keypair,
417 ) -> Result<crate::client::Response, RequestError> {
418 match builder.send().await {
419 Ok(response) => {
420 if self.ek.is_some() {
423 if self.ek.clone().unwrap().ephemeral || self.ek.clone().unwrap().is_expired() {
424 self.ek = None;
425 }
426 }
427
428 let result = match crate::client::Response::new(response, kp.get_secret_key()).await
429 {
430 Ok(response) => response,
431 Err(error) => return Err(RequestError::HandlingResponse(error)),
432 };
433
434 let hash_id = self.get_header_by_name(result.headers.get("x-hashid"));
436 let expires_at =
437 self.get_header_by_name(result.headers.get("x-public-key-expiration"));
438 let public_key = self.get_key_string_by_result_or_header(
439 result.pk.clone(),
440 result.headers.get("x-public-key"),
441 );
442 let signature_key = self.get_key_string_by_result_or_header(
443 result.sk.clone(),
444 result.headers.get("x-signature-key"),
445 );
446 if hash_id.is_some()
447 && expires_at.is_some()
448 && public_key.is_some()
449 && signature_key.is_some()
450 {
451 let xp = expires_at.unwrap().parse::<i64>();
452 if xp.is_ok() {
453 self.ek = Some(crate::rocket::ExportableEncryptionKeyData {
454 public: public_key.unwrap(),
455 signature: signature_key.unwrap(),
456 hash_id: hash_id.unwrap(),
457 ephemeral: false,
458 expires_at: xp.unwrap(),
459 });
460 }
461 }
462
463 return Ok(result);
464 }
465 Err(error) => Err(RequestError::ReqwestError(error)),
466 }
467 }
468
469 fn get_key_string_by_result_or_header(
471 &self,
472 key: Option<Vec<u8>>,
473 header: Option<&HeaderValue>,
474 ) -> Option<String> {
475 match key {
476 Some(key) => Some(general_purpose::STANDARD.encode(key)),
478 None => match header {
480 Some(header) => match header.to_str() {
481 Ok(s) => Some(s.to_string()),
483 Err(_) => None,
484 },
485 None => None,
486 },
487 }
488 }
489
490 fn get_header_by_name(&self, header: Option<&HeaderValue>) -> Option<String> {
492 match header {
493 Some(h) => match h.to_str() {
494 Ok(s) => Some(s.to_string()),
495 Err(_) => None,
496 },
497 None => None,
498 }
499 }
500}