ring_client/client/wrapper/
authentication.rs

1use std::sync::Arc;
2
3use chrono::DateTime;
4
5use crate::authentication::{AuthenticationError, Credentials};
6use crate::client::authentication::Tokens;
7use crate::Client;
8
9impl Client {
10    /// Login to Ring using a set of credentials.
11    ///
12    /// These credentials can either be:
13    /// * A username and password ([`Credentials::User`])
14    /// * A refresh token ([`Credentials::RefreshToken`])
15    ///
16    /// # Example
17    ///
18    /// ## Login with a Username and Password
19    /// ```no_run
20    /// use ring_client::Client;
21    ///
22    /// use ring_client::authentication::Credentials;
23    /// use ring_client::AuthenticationError;
24    /// use ring_client::OperatingSystem;
25    ///
26    /// # tokio_test::block_on(async {
27    ///   let client = Client::new("Home Automation", "mock-system-id", OperatingSystem::Ios);
28    ///
29    ///   let credentials = Credentials::User {
30    ///     username: "username".to_string(),
31    ///     password: "password".to_string(),
32    ///   };
33    ///
34    ///   let attempt = client.login(credentials).await;
35    ///
36    ///   if let Err(AuthenticationError::MfaCodeRequired) = attempt {
37    ///     // The user needs to enter a 2FA code.
38    ///     client.respond_to_challenge("123456").await.expect("Providing a valid 2FA code should not fail");
39    ///   }
40    ///   else {
41    ///     // The login was successful!
42    ///   }
43    /// # })
44    /// ```
45    ///
46    /// ## Login with a Refresh Token
47    ///
48    /// If the user has previosuly logged in, Ring will have issued a refresh token. This token
49    /// can be used on subsequent login attempts to avoid having to complete the full login flow
50    /// (2FA, etc).
51    ///
52    /// Refresh tokens can be retrieved using [`Client::get_refresh_token`] after a successful
53    /// login.
54    ///
55    /// ```no_run
56    /// use ring_client::Client;
57    ///
58    /// use ring_client::authentication::Credentials;
59    /// use ring_client::OperatingSystem;
60    ///
61    /// # tokio_test::block_on(async {
62    ///    let client = Client::new("Home Automation", "mock-system-id", OperatingSystem::Ios);
63    ///
64    ///    let refresh_token = Credentials::RefreshToken("".to_string());
65    ///
66    ///    client.login(refresh_token).await.expect("Logging in with a valid refresh token should not fail");
67    /// # })
68    /// ```
69    ///
70    /// # Errors
71    ///
72    /// Returns an error logging in was unsuccessful and a Two Factor Authentication (2FA)
73    /// challenge was not issued.
74    pub async fn login(&self, credentials: Credentials) -> Result<(), AuthenticationError> {
75        let mut lock = self.user.write().await;
76        let user = lock.insert(credentials);
77
78        match user {
79            Credentials::User { username, password } => {
80                self.tokens.write().await.replace(Arc::new(
81                    self.auth.login(username, password, &self.system_id).await?,
82                ));
83            }
84            Credentials::RefreshToken(ref refresh_token) => {
85                self.tokens.write().await.replace(Arc::new(
86                    self.auth
87                        .refresh_tokens(Arc::new(Tokens::new(
88                            String::new(),
89                            DateTime::default(),
90                            refresh_token.to_string(),
91                        )))
92                        .await?,
93                ));
94            }
95        };
96
97        self.api
98            .set_session(
99                &self.display_name,
100                &self.system_id,
101                &*self
102                    .refresh_tokens_if_needed()
103                    .await
104                    .map_err(|_| AuthenticationError::SessionFailed)?,
105            )
106            .await
107            .map_err(|_| AuthenticationError::SessionFailed)?;
108
109        Ok(())
110    }
111
112    /// Respond to a challenge issued by Ring during the authentication process.
113    ///
114    /// This is typically used to handle Two Factor Authentication (2FA) challenges
115    ///
116    /// # Errors
117    ///
118    /// Returns an error if the challenge could not be completed.
119    pub async fn respond_to_challenge(&self, code: &str) -> Result<(), AuthenticationError> {
120        if let Some(Credentials::User { username, password }) = self.user.read().await.as_ref() {
121            self.tokens.write().await.replace(Arc::new(
122                self.auth
123                    .respond_to_challenge(username, password, &self.system_id, code)
124                    .await?,
125            ));
126
127            self.api
128                .set_session(
129                    &self.display_name,
130                    &self.system_id,
131                    &*self
132                        .refresh_tokens_if_needed()
133                        .await
134                        .map_err(|_| AuthenticationError::SessionFailed)?,
135                )
136                .await
137                .map_err(|_| AuthenticationError::SessionFailed)?;
138        }
139
140        Ok(())
141    }
142
143    /// Get the refresh token issued by Ring for the current session.
144    ///
145    /// If [`Credentials::RefreshToken`] was used to login initially, this will return the
146    /// same token.
147    pub async fn get_refresh_token(&self) -> Option<String> {
148        if let Some(refresh_token) = self.tokens.read().await.as_ref() {
149            return Some(refresh_token.refresh_token.to_string());
150        }
151
152        None
153    }
154}