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}