Skip to main content

hive_client/client/
mod.rs

1mod api;
2mod wrapper;
3
4/// Support for the Hive Authentication API.
5pub mod authentication;
6
7pub use api::actions;
8pub use api::devices;
9pub use api::products;
10pub use api::weather;
11
12pub use api::ApiError;
13pub use authentication::AuthenticationError;
14
15#[doc(hidden)]
16pub use authentication::RefreshError;
17
18use crate::authentication::HiveAuth;
19use crate::client::api::HiveApi;
20use crate::client::authentication::{Tokens, User};
21use std::sync::Arc;
22use tokio::sync::{Mutex, RwLock};
23
24/// Client used to authenticate and interact with Hive.
25#[derive(Debug)]
26pub struct Client {
27    auth: RwLock<Option<HiveAuth>>,
28    api: HiveApi,
29    user: Mutex<Option<User>>,
30    tokens: Mutex<Option<Arc<Tokens>>>,
31    friendly_name: String,
32}
33
34impl Client {
35    /// Create a new client.
36    ///
37    /// The friendly name is used to identify the client in the
38    /// [Trusted Device page](https://community.hivehome.com/s/article/2FA-2-factor-Authentication) of the Hive app if
39    /// the user is authenticating for the first time (does not have a trusted device during [`Client::login`])
40    #[must_use]
41    pub fn new(friendly_name: &str) -> Self {
42        Self {
43            auth: RwLock::new(None),
44            api: HiveApi::new(),
45            user: Mutex::new(None),
46            tokens: Mutex::new(None),
47            friendly_name: friendly_name.to_string(),
48        }
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use dotenvy_macro::dotenv;
56
57    #[tokio::test]
58    async fn test_cognito_authentication_and_device_confirmation() {
59        let mut client = Client::new("Home Automation");
60
61        let user = User::new(dotenv!("MOCK_USER_EMAIL"), dotenv!("MOCK_USER_PASSWORD"));
62
63        let trusted_device = client
64            .login(user, None)
65            .await
66            .expect("Login should succeed")
67            .expect("A trusted device should've been returned");
68
69        assert!(!trusted_device.device_key.is_empty());
70        assert!(!trusted_device.device_group_key.is_empty());
71        assert!(!trusted_device.device_password.is_empty());
72        assert!(trusted_device.device_key.starts_with(dotenv!("REGION")));
73
74        println!("Hello: {:?}", client.get_devices().await);
75
76        client.logout().await;
77    }
78
79    #[tokio::test]
80    async fn test_cognito_authentication_refresh() {
81        let mut client = Client::new("Home Automation");
82
83        let user = User::new(dotenv!("MOCK_USER_EMAIL"), dotenv!("MOCK_USER_PASSWORD"));
84
85        client
86            .login(user, None)
87            .await
88            .expect("Login should succeed");
89
90        let current_tokens = {
91            // Update the tokens to simulate an expiration
92
93            let mut tokens = client.tokens.lock().await;
94
95            let current_tokens = tokens.clone().expect("Tokens should be present");
96
97            let replacement_tokens = Arc::new(Tokens::new(
98                current_tokens.id_token.to_string(),
99                current_tokens.access_token.to_string(),
100                current_tokens.refresh_token.to_string(),
101                -1000,
102            ));
103            tokens.replace(Arc::clone(&replacement_tokens));
104
105            replacement_tokens
106        };
107
108        let refreshed_tokens = client
109            .refresh_tokens_if_needed()
110            .await
111            .expect("Refresh tokens should succeed");
112
113        assert_ne!(current_tokens.access_token, refreshed_tokens.access_token);
114        assert_eq!(current_tokens.refresh_token, refreshed_tokens.refresh_token);
115        assert!(current_tokens.expires_at < refreshed_tokens.expires_at);
116
117        client.logout().await;
118    }
119}