Skip to main content

mauth_client/
sign_outgoing.rs

1use crate::MAuthInfo;
2use chrono::prelude::*;
3use reqwest::{Request, header::HeaderValue};
4use thiserror::Error;
5
6impl MAuthInfo {
7    /// This method determines how to sign the request automatically while respecting the
8    /// `v2_only_sign_requests` flag in the config file. It always signs with the V2 algorithm and
9    /// signature, and will also sign with the V1 algorithm, if the configuration permits.
10    ///
11    /// Note that, as the request signature includes a timestamp, the request must be sent out
12    /// shortly after the signature takes place.
13    ///
14    /// Note that it will need to read the entire body in order to sign it, so it will not
15    /// work properly if any of the streaming body types are used.
16    pub fn sign_request(&self, req: &mut Request) -> Result<(), SigningError> {
17        self.sign_request_v2(req)?;
18        if self.sign_with_v1_also {
19            self.sign_request_v1(req)?;
20        }
21        Ok(())
22    }
23
24    /// Sign a provided request using the MAuth V2 protocol. The signature consists of 2 headers
25    /// containing both a timestamp and a signature string, and will be added to the headers of the
26    /// request. It is required to pass a `body_digest` computed by the
27    /// [`build_body_with_digest`](#method.build_body_with_digest) method, even if the request is
28    /// an empty-body GET.
29    ///
30    /// Note that, as the request signature includes a timestamp, the request must be sent out
31    /// shortly after the signature takes place.
32    ///
33    /// Also note that it will need to read the entire body in order to sign it, so it will not
34    /// work properly if any of the streaming body types are used.
35    pub fn sign_request_v2(&self, req: &mut Request) -> Result<(), SigningError> {
36        let timestamp_str = Utc::now().timestamp().to_string();
37        let body_data = match req.body() {
38            None => &[],
39            Some(reqwest_body) => reqwest_body.as_bytes().unwrap_or(&[]),
40        };
41        let some_string = self.signer.sign_string(
42            2,
43            req.method().as_str(),
44            req.url().path(),
45            req.url().query().unwrap_or(""),
46            body_data,
47            timestamp_str.clone(),
48        )?;
49        self.set_headers_v2(req, some_string, &timestamp_str);
50        Ok(())
51    }
52
53    pub(crate) fn set_headers_v2(&self, req: &mut Request, signature: String, timestamp_str: &str) {
54        let sig_head_str = format!("MWSV2 {}:{};", self.app_id, &signature);
55        let headers = req.headers_mut();
56        headers.insert("MCC-Time", HeaderValue::from_str(timestamp_str).unwrap());
57        headers.insert(
58            "MCC-Authentication",
59            HeaderValue::from_str(&sig_head_str).unwrap(),
60        );
61    }
62
63    /// Sign a provided request using the MAuth V1 protocol. The signature consists of 2 headers
64    /// containing both a timestamp and a signature string, and will be added to the headers of the
65    /// request. It is required to pass a `body`, even if the request is an empty-body GET.
66    ///
67    /// Note that, as the request signature includes a timestamp, the request must be sent out
68    /// shortly after the signature takes place.
69    ///
70    /// Also note that it will need to read the entire body in order to sign it, so it will not
71    /// work properly if any of the streaming body types are used.
72    pub fn sign_request_v1(&self, req: &mut Request) -> Result<(), SigningError> {
73        let timestamp_str = Utc::now().timestamp().to_string();
74
75        let body_data = match req.body() {
76            None => &[],
77            Some(reqwest_body) => reqwest_body.as_bytes().unwrap_or(&[]),
78        };
79
80        let sig = self.signer.sign_string(
81            1,
82            req.method().as_str(),
83            req.url().path(),
84            req.url().query().unwrap_or(""),
85            body_data,
86            timestamp_str.clone(),
87        )?;
88
89        let headers = req.headers_mut();
90        headers.insert("X-MWS-Time", HeaderValue::from_str(&timestamp_str).unwrap());
91        headers.insert("X-MWS-Authentication", HeaderValue::from_str(&sig).unwrap());
92        Ok(())
93    }
94}
95
96/// All of the errors that can take place while attempting to sign a request
97#[derive(Debug, Error)]
98pub enum SigningError {
99    #[error("Unable to handle the URL as the format was invalid: {0}")]
100    UrlEncodingError(std::string::FromUtf8Error),
101}
102
103impl From<mauth_core::error::Error> for SigningError {
104    fn from(err: mauth_core::error::Error) -> SigningError {
105        match err {
106            mauth_core::error::Error::UrlEncodingError(url_err) => {
107                SigningError::UrlEncodingError(url_err)
108            }
109            _ => panic!("should not be possible to get this error type from signing a request"),
110        }
111    }
112}