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, ×tamp_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(×tamp_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}