barter_integration/protocol/http/private/mod.rs
1use self::encoder::Encoder;
2use super::{BuildStrategy, rest::RestRequest};
3use crate::error::SocketError;
4use hmac::Mac;
5
6/// Implementations for encoding signatures generated by a [`RequestSigner`].
7pub mod encoder;
8
9/// API specific signing logic used by a [`RequestSigner`].
10#[allow(clippy::needless_lifetimes)]
11pub trait Signer {
12 /// Configuration required to sign the [`RestRequest`]s for this API server.
13 type Config<'a>
14 where
15 Self: 'a;
16
17 /// Generates a [`Self::Config`] for this [`RestRequest`] and
18 /// [`RequestBuilder`](reqwest::RequestBuilder).
19 ///
20 /// # Examples
21 ///
22 /// ## Private REST Request: FTX
23 /// ```rust,ignore
24 /// fn config<Request>(&self, _: Request, _: &RequestBuilder) -> Self::Config
25 /// where
26 /// Request: RestRequest
27 /// {
28 /// FtxSignConfig {
29 /// api_key: self.api_key.as_str(),
30 /// time: Utc::now(),
31 /// method: Request::method(),
32 /// path: Request::path()
33 /// }
34 /// }
35 /// ```
36 fn config<'a, Request>(
37 &'a self,
38 request: Request,
39 builder: &reqwest::RequestBuilder,
40 ) -> Result<Self::Config<'a>, SocketError>
41 where
42 Request: RestRequest;
43
44 /// Generate the bytes to sign from the provided [`Self::Config`].
45 ///
46 /// # Examples
47 ///
48 /// ## Private REST Request: FTX
49 /// ```rust,ignore
50 /// fn add_bytes_to_sign<M>(mac: &mut M, config: &Self::Config) {
51 /// mac.update(config.time.to_string().as_bytes());
52 /// mac.update(config.method.as_str().as_bytes());
53 /// mac.update(config.path.as_bytes());
54 /// }
55 /// ```
56 fn add_bytes_to_sign<M>(mac: &mut M, config: &Self::Config<'_>)
57 where
58 M: Mac;
59
60 /// Build a signed [`reqwest::Request`] from the provided [`Self::Config`],
61 /// [`RequestBuilder`](reqwest::RequestBuilder), and generated cryptographic signature `String`.
62 ///
63 /// # Examples
64 ///
65 /// ## Private REST Request: FTX
66 /// ```rust,ignore
67 /// fn build_signed_request(config: Self::Config, builder: RequestBuilder, signature: String) -> Result<reqwest::Request, SocketError> {
68 /// // Add Ftx required Headers & build reqwest::Request
69 /// builder
70 /// .header(HEADER_FTX_KEY, &self.api_key)
71 /// .header(HEADER_FTX_SIGN, &signature)
72 /// .header(HEADER_FTX_TS, &time)
73 /// .build()
74 /// .map_err(SocketError::from)
75 /// }
76 /// ```
77 fn build_signed_request<'a>(
78 config: Self::Config<'a>,
79 builder: reqwest::RequestBuilder,
80 signature: String,
81 ) -> Result<reqwest::Request, SocketError>;
82}
83
84/// Generically signs Http [`RestRequest`]s utilising API specific [`Signer`] logic, a hashable
85/// [`Mac`], and a signature [`Encoder`].
86#[derive(Debug, Copy, Clone)]
87pub struct RequestSigner<Sig, Hmac, SigEncoder> {
88 signer: Sig,
89 mac: Hmac,
90 encoder: SigEncoder,
91}
92
93impl<Sig, Hmac, SigEncoder> BuildStrategy for RequestSigner<Sig, Hmac, SigEncoder>
94where
95 Sig: Signer,
96 Hmac: Mac + Clone,
97 SigEncoder: Encoder,
98{
99 fn build<Request>(
100 &self,
101 request: Request,
102 builder: reqwest::RequestBuilder,
103 ) -> Result<reqwest::Request, SocketError>
104 where
105 Request: RestRequest,
106 {
107 // Build configuration required for generating signed requests
108 let config = self.signer.config(request, &builder)?;
109
110 // Update Mac state & finalise bytes
111 let mut mac = self.mac.clone();
112 Sig::add_bytes_to_sign(&mut mac, &config);
113 let bytes_to_encode = mac.finalize().into_bytes();
114
115 // Encode signature from Mac bytes
116 let signature = self.encoder.encode(bytes_to_encode);
117
118 Sig::build_signed_request(config, builder, signature)
119 }
120}
121
122impl<Sig, Hmac, SigEncoder> RequestSigner<Sig, Hmac, SigEncoder> {
123 /// Construct a new [`Self`] using the provided API specific configuration.
124 pub fn new(signer: Sig, mac: Hmac, encoder: SigEncoder) -> Self {
125 Self {
126 signer,
127 mac,
128 encoder,
129 }
130 }
131}