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 for error in &errors.errors {
162 dbg!(error);
163 }
164
165 match code {
166 400 => {
167 let errors: Vec<ApiError> = errors
168 .errors
169 .iter()
170 .map(|x| match x.message.as_str() {
171 "Invalid challenge ID." => ApiError::InvalidChallengeId,
172 "User not found." => ApiError::UserNotFound,
173 "The user ID is invalid." => ApiError::InvalidUserId,
174 "The gender provided is invalid." => ApiError::InvalidGender,
175 "The two step verification challenge code is invalid." => {
176 ApiError::InvalidTwoStepVerificationCode
177 }
178
179 "Invalid display name." => ApiError::InvalidDisplayName,
180
181 "Request must contain a birthdate" => {
182 ApiError::RequestMissingArgument("Birthdate".to_string())
183 }
184
185 _ => ApiError::Unknown(code),
186 })
187 .collect();
188
189 if errors.len() == 1 {
190 Err(Error::ApiError(errors.first().unwrap().clone()))
191 } else {
192 Err(Error::ApiError(ApiError::Multiple(errors)))
193 }
194 }
195
196 401 => Err(Error::ApiError(ApiError::Unauthorized)),
197 403 => {
198 let errors: Vec<ApiError> = errors
199 .errors
200 .iter()
201 .map(|x| match x.message.as_str() {
202 "Token Validation Failed"
203 | "XSRF token invalid"
204 | "XSRF Token Validation Failed"
205 | "\"XSRF Token Validation Failed\"" => ApiError::TokenValidation,
206
207 "PIN is locked." => ApiError::PinIsLocked,
208 "Invalid birthdate change." => ApiError::InvalidBirthdate,
209
210 "Challenge is required to authorize the request" => {
211 ApiError::ChallengeRequired(challenge.clone().unwrap())
212 }
213
214 "Challenge failed to authorize request" => {
215 ApiError::ChallengeFailed
216 }
217
218 "You do not have permission to view the owners of this asset." => {
219 ApiError::PermissionError
220 }
221
222 "an internal error occurred" => ApiError::Internal,
223
224 _ => ApiError::Unknown(code),
226 })
227 .collect();
228
229 if errors.len() == 1 {
230 Err(Error::ApiError(errors.first().unwrap().clone()))
231 } else {
232 Err(Error::ApiError(ApiError::Multiple(errors)))
233 }
234 }
235
236 429 => Err(Error::ApiError(ApiError::Ratelimited)),
237 500 => Err(Error::ApiError(ApiError::Internal)),
238
239 _ => Err(Error::ApiError(ApiError::Unknown(code))),
240 }
241 }
242
243 Err(error) => Err(Error::ReqwestError(error)),
244 }
245 }
246}