1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//! Contains functions related to request signing for the remote.it API.
//! They are used by this lib, but you can also use them to implement your own abstraction.


use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
use base64::Engine;
use bon::builder;
use chrono::Utc;
use reqwest::Method;
use ring::hmac;

/// You probably don't want to use this function directly, unless you are implementing your own abstraction over the remote.it API.
///
/// Signs the given `message` with the given `key` with the HMAC algorithm and base64-encodes the result.
///
/// # Returns
/// Base64 encoded HMAC signature.
pub fn create_signature(key: &[u8], message: &str) -> String {
    let signing_key = hmac::Key::new(hmac::HMAC_SHA256, key);
    let signature = hmac::sign(&signing_key, message.as_bytes());
    BASE64_STANDARD.encode(signature.as_ref())
}

/// You probably don't want to use this function directly, unless you are implementing your own abstraction over the remote.it API.
///
/// Create the value to use in the `Authorization` header for requests to the remote.it API.
///
/// 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.
///
/// # Returns
/// A [`String`] which should be set as the value for the `Authorization` header for sending requests to the remote.it API.
///
/// # Example
/// ```
/// use reqwest::Method;
/// use remoteit_api::credentials::Credentials;
/// use remoteit_api::GRAPHQL_PATH;
/// let credentials = Credentials::load_from_disk().call().unwrap().take_profile("default").unwrap().unwrap();
/// let date = remoteit_api::auth::get_date();
/// let auth_header = remoteit_api::auth::build_auth_header()
///     .key_id(credentials.access_key_id())
///     .key(credentials.key())
///     .content_type("application/json")
///     .method(&Method::POST)
///     .path(GRAPHQL_PATH)
///     .date(&date)
///     .call();
/// ```
///
#[builder]
pub fn build_auth_header(
    key_id: &str,
    key: &[u8],
    content_type: &str,
    method: &Method,
    path: &str,
    date: &str,
) -> String {
    let signature_params =
        format!(
            "(request-target): {} {path}\nhost: api.remote.it\ndate: {date}\ncontent-type: {content_type}",
            method.to_string().to_lowercase()
        );
    #[cfg(debug_assertions)]
    dbg!(&signature_params);
    let signature = create_signature(key, &signature_params);
    format!(
        "Signature keyId=\"{key_id}\",algorithm=\"hmac-sha256\",headers=\"(request-target) host date content-type\",signature=\"{signature}\"")
}

/// 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.
///
/// Creates a date string (now) to be used for signing requests to the remote.it API.
///
/// This function is used by [`R3Client::send_remoteit_graphql_request`] and [`R3Client::send_remoteit_graphql_request_async`].
///
/// # Returns
/// A date string (now) in the format required by the remote.it API.
pub fn get_date() -> String {
    Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string()
}