remoteit_api/auth.rs
1//! Contains functions related to request signing for the remote.it API.
2//! They are used by this lib, but you can also use them to implement your own abstraction.
3
4use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
5use base64::Engine;
6use bon::builder;
7use chrono::Utc;
8use reqwest::Method;
9use ring::hmac;
10
11/// You probably don't want to use this function directly, unless you are implementing your own abstraction over the remote.it API.
12///
13/// Signs the given `message` with the given `key` with the HMAC algorithm and base64-encodes the result.
14///
15/// # Returns
16/// Base64 encoded HMAC signature.
17#[must_use]
18pub fn create_signature(key: &[u8], message: &str) -> String {
19 let signing_key = hmac::Key::new(hmac::HMAC_SHA256, key);
20 let signature = hmac::sign(&signing_key, message.as_bytes());
21 BASE64_STANDARD.encode(signature.as_ref())
22}
23
24/// You probably don't want to use this function directly, unless you are implementing your own abstraction over the remote.it API.
25///
26/// Create the value to use in the `Authorization` header for requests to the remote.it API.
27///
28/// This function is used by [`R3Client::send_remoteit_graphql_request`] and [`R3Client::send_remoteit_graphql_request_async`] to authorize requests to the remote.it API.
29///
30/// # Returns
31/// A [`String`] which should be set as the value for the `Authorization` header for sending requests to the remote.it API.
32///
33/// # Example
34/// ```
35/// use reqwest::Method;
36/// use remoteit_api::Credentials;
37/// use remoteit_api::GRAPHQL_PATH;
38/// let credentials: Credentials = Credentials::load_from_disk()
39/// .custom_credentials_path(".env.remoteit")
40/// .call()
41/// .expect("Couldn't load credentials!")
42/// .take_profile("default")
43/// .expect("Couldn't parse secret access key!")
44/// .expect("Profile with given name does not exist!");
45/// let date = remoteit_api::auth::get_date();
46/// let auth_header = remoteit_api::auth::build_auth_header()
47/// .key_id(credentials.access_key_id())
48/// .key(credentials.key())
49/// .content_type("application/json")
50/// .method(&Method::POST)
51/// .path(GRAPHQL_PATH)
52/// .date(&date)
53/// .call();
54/// ```
55///
56#[builder]
57pub fn build_auth_header(
58 key_id: &str,
59 key: &[u8],
60 content_type: &str,
61 method: &Method,
62 path: &str,
63 date: &str,
64) -> String {
65 let signature_params =
66 format!(
67 "(request-target): {} {path}\nhost: api.remote.it\ndate: {date}\ncontent-type: {content_type}",
68 method.to_string().to_lowercase()
69 );
70 #[cfg(debug_assertions)]
71 dbg!(&signature_params);
72 let signature = create_signature(key, &signature_params);
73 format!(
74 "Signature keyId=\"{key_id}\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type\",signature=\"{signature}\"")
75}
76
77/// You probably don't want to use this function directly, unless you are implementing your own abstraction for making requests to the remote.it API.
78///
79/// Creates a date string (now) to be used for signing requests to the remote.it API.
80///
81/// This function is used by [`R3Client::send_remoteit_graphql_request`] and [`R3Client::send_remoteit_graphql_request_async`].
82///
83/// # Returns
84/// A date string (now) in the format required by the remote.it API.
85#[allow(clippy::must_use_candidate)]
86pub fn get_date() -> String {
87 Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string()
88}