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
15use crate::authentication::HiveAuth;
16use crate::client::api::HiveApi;
17use crate::client::authentication::{Tokens, User};
18use std::sync::Arc;
19use tokio::sync::Mutex;
20
21/// Client used to authenticate and interact with Hive.
22#[derive(Debug)]
23pub struct Client {
24    auth: HiveAuth,
25    api: HiveApi,
26    user: Mutex<Option<User>>,
27    tokens: Mutex<Option<Arc<Tokens>>>,
28    friendly_name: String,
29}
30
31impl Client {
32    /// Create a new client.
33    ///
34    /// The friendly name is used to identify the client in the
35    /// [Trusted Device page](https://community.hivehome.com/s/article/2FA-2-factor-Authentication) of the Hive app if
36    /// the user is authenticating for the first time (does not have a trusted device during [`Client::login`])
37    pub async fn new(friendly_name: &str) -> Self {
38        Self {
39            auth: HiveAuth::new().await,
40            api: HiveApi::new(),
41            user: Mutex::new(None),
42            tokens: Mutex::new(None),
43            friendly_name: friendly_name.to_string(),
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use dotenvy_macro::dotenv;
52
53    #[tokio::test]
54    async fn test_cognito_authentication_and_device_confirmation() {
55        let mut client = Client::new("Home Automation").await;
56
57        let user = User::new(
58            dotenv!("MOCK_USER_EMAIL"),
59            dotenv!("MOCK_USER_PASSWORD"),
60            None,
61        );
62
63        let trusted_device = client
64            .login(user)
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        client.logout().await;
75    }
76
77    #[tokio::test]
78    async fn test_cognito_authentication_refresh() {
79        let mut client = Client::new("Home Automation").await;
80
81        let user = User::new(
82            dotenv!("MOCK_USER_EMAIL"),
83            dotenv!("MOCK_USER_PASSWORD"),
84            None,
85        );
86
87        client.login(user).await.expect("Login should succeed");
88
89        let current_tokens = {
90            // Update the tokens to simulate an expiration
91
92            let mut tokens = client.tokens.lock().await;
93
94            let current_tokens = tokens.clone().unwrap();
95
96            let replacement_tokens = Arc::new(Tokens::new(
97                current_tokens.id_token.to_string(),
98                current_tokens.access_token.to_string(),
99                current_tokens.refresh_token.to_string(),
100                -1000,
101            ));
102            tokens.replace(Arc::clone(&replacement_tokens));
103
104            replacement_tokens
105        };
106
107        let refreshed_tokens = client
108            .refresh_tokens_if_needed()
109            .await
110            .expect("Refresh tokens should succeed");
111
112        assert_ne!(current_tokens.access_token, refreshed_tokens.access_token);
113        assert_eq!(current_tokens.refresh_token, refreshed_tokens.refresh_token);
114        assert!(current_tokens.expires_at < refreshed_tokens.expires_at);
115
116        client.logout().await;
117    }
118}