1use base64::{Engine, prelude::BASE64_STANDARD};
2use reqwest::{Response, header::HeaderValue};
3use serde::Deserialize;
4
5use crate::{
6 ApiError, Error,
7 api::auth,
8 challenge::{
9 CHALLENGE_ID_HEADER, CHALLENGE_METADATA_HEADER, CHALLENGE_TYPE_HEADER, Challenge,
10 ChallengeMetadata, ChallengeType, ChefChallengeMetadata,
11 },
12 client::Client,
13};
14
15const TOKEN_HEADER: &str = "x-csrf-token";
16
17#[derive(Debug, Deserialize)]
18pub struct ErrorJson {
19 code: u8,
20 message: String,
21}
22
23#[derive(Debug, Deserialize)]
24pub struct ErrorsJson {
25 errors: Vec<ErrorJson>,
26}
27
28#[derive(Debug, Deserialize)]
29pub struct DataErrorJson {
30 #[serde(rename = "isValid")]
31 is_valid: bool,
32 data: Option<String>, #[serde(rename = "error")]
34 message: String,
35}
36
37impl Client {
38 fn set_token(&mut self, token: &str) {
39 self.requestor
40 .default_headers
41 .insert(TOKEN_HEADER, HeaderValue::from_str(token).unwrap());
42 }
43
44 pub async fn ensure_token(&mut self) -> Result<(), Error> {
47 let result = self
48 .requestor
49 .client
50 .post(format!("{}//", auth::URL))
51 .headers(self.requestor.default_headers.clone())
52 .send()
53 .await;
54
55 let result = self.validate_response(result).await;
56
57 if let Err(Error::ApiError(ApiError::TokenValidation)) = result {
58 return Ok(());
59 }
60
61 if result.is_err() {
62 return Err(result.err().unwrap());
63 }
64
65 Ok(())
66 }
67
68 pub(crate) async fn validate_response(
73 &mut self,
74 result: Result<Response, reqwest::Error>,
75 ) -> Result<Response, Error> {
76 self.remove_challenge();
78
79 match result {
80 Ok(response) => {
81 let code = response.status().as_u16();
82
83 let token = response.headers().get(TOKEN_HEADER);
84 if let Some(token) = token {
85 self.set_token(&String::from_utf8_lossy(token.as_bytes()).to_string());
87 }
88
89 if code == 200 {
91 return Ok(response);
92 }
93
94 let challenge = {
96 let challenge_id = response.headers().get(CHALLENGE_ID_HEADER);
97 let challenge_type = response.headers().get(CHALLENGE_TYPE_HEADER);
98 let challenge_metadata_b64 = response.headers().get(CHALLENGE_METADATA_HEADER);
99
100 if let (Some(id), Some(kind), Some(metadata_b64)) =
101 (challenge_id, challenge_type, challenge_metadata_b64)
102 {
103 let kind = ChallengeType::from(kind.to_str().unwrap());
104 match kind {
105 ChallengeType::Chef => {
106 let _metadata: ChefChallengeMetadata = serde_json::from_slice(
107 BASE64_STANDARD
108 .decode(metadata_b64.to_str().unwrap())
109 .unwrap()
110 .as_slice(),
111 )
112 .unwrap();
113
114 todo!("Unsupported chef challenge");
115 }
116
117 _ => {
118 let metadata: ChallengeMetadata = serde_json::from_slice(
119 BASE64_STANDARD
120 .decode(metadata_b64.to_str().unwrap())
121 .unwrap()
122 .as_slice(),
123 )
124 .unwrap();
125
126 Some(Challenge {
127 id: id.to_str().unwrap().to_string(),
128 kind,
129 metadata,
130 })
131 }
132 }
133 } else {
134 None
135 }
136 };
137
138 let bytes = response.bytes().await.unwrap().to_owned();
139 let errors = if let Ok(errors) = serde_json::from_slice::<ErrorsJson>(&bytes) {
140 errors
141 } else if let Ok(error) = serde_json::from_slice::<ErrorJson>(&bytes) {
142 ErrorsJson {
143 errors: vec![error],
144 }
145 } else if let Ok(error) = serde_json::from_slice::<DataErrorJson>(&bytes) {
146 ErrorsJson {
147 errors: vec![ErrorJson {
148 code: 0,
149 message: error.message,
150 }],
151 }
152 } else {
153 ErrorsJson {
154 errors: vec![ErrorJson {
155 code: 0,
156 message: String::from_utf8_lossy(&bytes).to_string(),
157 }],
158 }
159 };
160
161 match code {
162 400 => {
163 let errors: Vec<ApiError> = errors
164 .errors
165 .iter()
166 .map(|x| match x.message.as_str() {
167 "Invalid challenge ID." => ApiError::InvalidChallengeId,
168 "User not found." => ApiError::UserNotFound,
169 "The user ID is invalid." => ApiError::InvalidUserId,
170 "The gender provided is invalid." => ApiError::InvalidGender,
171 "The two step verification challenge code is invalid." => {
172 ApiError::InvalidTwoStepVerificationCode
173 }
174
175 "Invalid display name." => ApiError::InvalidDisplayName,
176
177 "Request must contain a birthdate" => {
178 ApiError::RequestMissingArgument("Birthdate".to_string())
179 }
180
181 _ => ApiError::Unknown(code),
182 })
183 .collect();
184
185 if errors.len() == 1 {
186 Err(Error::ApiError(errors.first().unwrap().clone()))
187 } else {
188 Err(Error::ApiError(ApiError::Multiple(errors)))
189 }
190 }
191
192 401 => Err(Error::ApiError(ApiError::Unauthorized)),
193 403 => {
194 let errors: Vec<ApiError> = errors
195 .errors
196 .iter()
197 .map(|x| match x.message.as_str() {
198 "Token Validation Failed"
199 | "XSRF token invalid"
200 | "XSRF Token Validation Failed"
201 | "\"XSRF Token Validation Failed\"" => ApiError::TokenValidation,
202
203 "PIN is locked." => ApiError::PinIsLocked,
204 "Invalid birthdate change." => ApiError::InvalidBirthdate,
205
206 "Challenge is required to authorize the request" => {
207 ApiError::ChallengeRequired(challenge.clone().unwrap())
208 }
209
210 "Challenge failed to authorize request" => {
211 ApiError::ChallengeFailed
212 }
213
214 "You do not have permission to view the owners of this asset." => {
215 ApiError::PermissionError
216 }
217
218 "an internal error occurred" => ApiError::Internal,
219
220 _ => ApiError::Unknown(code),
222 })
223 .collect();
224
225 if errors.len() == 1 {
226 Err(Error::ApiError(errors.first().unwrap().clone()))
227 } else {
228 Err(Error::ApiError(ApiError::Multiple(errors)))
229 }
230 }
231
232 429 => Err(Error::ApiError(ApiError::Ratelimited)),
233 500 => Err(Error::ApiError(ApiError::Internal)),
234
235 _ => Err(Error::ApiError(ApiError::Unknown(code))),
236 }
237 }
238
239 Err(error) => Err(Error::ReqwestError(error)),
240 }
241 }
242}