hive_client/client/authentication/challenge/
mod.rs1use crate::authentication::user::{AuthDevice, UntrustedDevice};
2use crate::client::authentication::DeviceClient;
3use crate::client::authentication::HiveAuth;
4use crate::client::authentication::Tokens;
5use crate::client::authentication::User;
6use crate::AuthenticationError;
7use aws_sdk_cognitoidentityprovider::operation::respond_to_auth_challenge::RespondToAuthChallengeOutput;
8use aws_sdk_cognitoidentityprovider::types::{
9 AuthenticationResultType, ChallengeNameType, NewDeviceMetadataType,
10};
11use std::collections::HashMap;
12use std::fmt::Debug;
13use std::sync::Arc;
14
15mod device_password_verifier;
16mod device_srp_auth;
17mod password_verifier;
18mod sms_mfa;
19
20#[derive(Debug)]
21#[non_exhaustive]
22pub enum ChallengeRequest {
25 SmsMfa,
31
32 #[doc(hidden)]
36 PasswordVerifier,
37
38 Unsupported(String),
41}
42
43#[derive(Debug)]
44#[non_exhaustive]
45pub enum ChallengeResponse {
47 SmsMfa(String),
50 #[doc(hidden)]
51 PasswordVerifier(HashMap<String, String>),
52 #[doc(hidden)]
53 DeviceSrpAuth,
54 #[doc(hidden)]
55 DevicePasswordVerifier(HashMap<String, String>),
56}
57
58impl HiveAuth {
59 pub(crate) async fn respond_to_challenge(
60 &self,
61 user: &User,
62 challenge_response: ChallengeResponse,
63 ) -> Result<(Tokens, Option<UntrustedDevice>), AuthenticationError> {
64 let response = {
65 let mut session = self.session.write().await;
66 let Some(session) = session.as_mut() else {
67 unreachable!("Login session should have been started in order to have a have received challenge which needs to be responded to.")
68 };
69
70 log::info!(
71 "Responding to challenge for {:?}. Challenge response is: {:?}",
72 user.username,
73 &challenge_response
74 );
75
76 let response = match challenge_response {
77 ChallengeResponse::PasswordVerifier(parameters) => {
78 let lock = self.get_user_srp_client(user).await;
79 let client = &*lock.read().await;
80
81 password_verifier::respond_to_challenge(
82 user,
83 &self.cognito,
84 client
85 .as_ref()
86 .ok_or(AuthenticationError::NoAuthenticationInProgress)?,
87 session,
88 parameters,
89 )
90 .await?
91 }
92 ChallengeResponse::DeviceSrpAuth => {
93 let client = self
94 .get_device_srp_client(
95 &session.0,
96 &AuthDevice::Trusted(Arc::clone(user.device.as_ref().expect(
97 "Device details should be set to use device SRP authentication",
98 ))),
99 )
100 .await;
101
102 let Some(DeviceClient::Tracked(client)) = &*client.read().await else {
103 unreachable!(
104 "Device must be tracked in order to be responding to DevicePasswordVerifier challenges."
105 )
106 };
107
108 device_srp_auth::handle_challenge(user, &self.cognito, client, session).await?
109 }
110 ChallengeResponse::DevicePasswordVerifier(parameters) => {
111 let client = self
112 .get_device_srp_client(
113 &session.0,
114 &AuthDevice::Trusted(Arc::clone(user.device.as_ref().expect(
115 "Device details should be set to use device SRP authentication",
116 ))),
117 )
118 .await;
119
120 let Some(DeviceClient::Tracked(client)) = &*client.read().await else {
121 unreachable!(
122 "Device must be tracked in order to be responding to DevicePasswordVerifier challenges."
123 )
124 };
125
126 device_password_verifier::handle_challenge(
127 user,
128 &self.cognito,
129 client,
130 session,
131 parameters,
132 )
133 .await?
134 }
135 ChallengeResponse::SmsMfa(code) => {
136 sms_mfa::handle_challenge(user, &self.cognito, session, &code).await?
137 }
138 };
139
140 session.1.clone_from(&response.session);
143
144 response
145 };
146
147 self.handle_challenge_response(response, user).await
148 }
149
150 async fn handle_challenge_response(
151 &self,
152 response: RespondToAuthChallengeOutput,
153 user: &User,
154 ) -> Result<(Tokens, Option<UntrustedDevice>), AuthenticationError> {
155 match &response.challenge_name {
156 None => {
157 if let Some(AuthenticationResultType {
158 id_token: Some(id_token),
159 access_token: Some(access_token),
160 refresh_token: Some(refresh_token),
161 expires_in,
162 new_device_metadata,
163 ..
164 }) = response.authentication_result
165 {
166 let mut untrusted_device: Option<UntrustedDevice> = None;
167 if let Some(NewDeviceMetadataType {
168 device_key: Some(device_key),
169 device_group_key: Some(device_group_key),
170 ..
171 }) = new_device_metadata
172 {
173 untrusted_device =
174 Some(UntrustedDevice::new(&device_group_key, &device_key));
175 }
176
177 Ok((
178 Tokens::new(id_token, access_token, refresh_token, expires_in),
179 untrusted_device,
180 ))
181 } else {
182 Err(AuthenticationError::AccessTokenNotValid)
183 }
184 }
185 Some(ChallengeNameType::DeviceSrpAuth) => {
186 Box::pin(self.respond_to_challenge(user, ChallengeResponse::DeviceSrpAuth)).await
187 }
188 Some(ChallengeNameType::DevicePasswordVerifier) => {
189 Box::pin(self.respond_to_challenge(
190 user,
191 ChallengeResponse::DevicePasswordVerifier(
192 response.challenge_parameters.unwrap_or_default(),
193 ),
194 ))
195 .await
196 }
197 Some(ChallengeNameType::SmsMfa) => {
198 Err(AuthenticationError::NextChallenge(ChallengeRequest::SmsMfa))
199 }
200 Some(name) => Err(AuthenticationError::UnsupportedChallenge(name.to_string())),
201 }
202 }
203}