Add another dead code path for validating 2FA code (I need to try with a phone number 2FA account...)
Dadoum

Dadoum commited on 2026-09-23 20:14:53
Showing 2 changed files, with 62 additions and 14 deletions.

... ...
@@ -288,39 +288,39 @@ pub async fn login(
288 288
     let response_dict = response_plist
289 289
         .get("Response")
290 290
         .and_then(|response| response.as_dictionary())
291
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
291
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
292 292
 
293 293
     let status = response_dict
294 294
         .get("Status")
295 295
         .and_then(|status| status.as_dictionary())
296
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
296
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
297 297
 
298 298
     parse_status(status).map_err(AuthError::Apple)?;
299 299
 
300 300
     let iteration_count = response_dict
301 301
         .get("i")
302 302
         .and_then(|iteration_count| iteration_count.as_unsigned_integer())
303
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
303
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
304 304
 
305 305
     let salt = response_dict
306 306
         .get("s")
307 307
         .and_then(|salt| salt.as_data())
308
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
308
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
309 309
 
310 310
     let selected_protocol = response_dict
311 311
         .get("sp")
312 312
         .and_then(|selected_protocol| selected_protocol.as_string())
313
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
313
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
314 314
 
315 315
     let cookie = response_dict
316 316
         .get("c")
317 317
         .and_then(|cookie| cookie.as_string())
318
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
318
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
319 319
 
320 320
     let b = response_dict
321 321
         .get("B")
322 322
         .and_then(|b| b.as_data())
323
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
323
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
324 324
 
325 325
     let hashed_password: Vec<u8> = match selected_protocol {
326 326
         // SRP with a 2048/4096-bit long A.
... ...
@@ -385,14 +385,14 @@ pub async fn login(
385 385
     let response_dict = response_plist
386 386
         .get("Response")
387 387
         .and_then(|response| response.as_dictionary())
388
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
388
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
389 389
 
390 390
     // println!("Response: {response_dict:?}");
391 391
 
392 392
     let status = response_dict
393 393
         .get("Status")
394 394
         .and_then(|status| status.as_dictionary())
395
-        .ok_or(AuthError::Structure(response_plist.clone()))?;
395
+        .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
396 396
 
397 397
     parse_status(status)?;
398 398
 
... ...
@@ -410,7 +410,7 @@ pub async fn login(
410 410
             let server_reply = response_dict
411 411
                 .get("M2")
412 412
                 .and_then(|server_reply| server_reply.as_data())
413
-                .ok_or(AuthError::Structure(response_plist.clone()))?;
413
+                .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
414 414
 
415 415
             verifier
416 416
                 .verify_server(server_reply)
... ...
@@ -463,7 +463,7 @@ pub async fn login(
463 463
                 .get("au")
464 464
                 .and_then(|action_url| action_url.as_string())
465 465
                 .map(|action_url| action_url.to_string())
466
-                .ok_or(AuthError::Structure(response_plist))?;
466
+                .ok_or_else(|| AuthError::Structure(response_plist))?;
467 467
 
468 468
             Ok(AuthOutcome::SecondaryActionRequired(
469 469
                 server_provided_data,
... ...
@@ -475,7 +475,7 @@ pub async fn login(
475 475
             let sim = status
476 476
                 .get("X-Apple-I-MD-DATA")
477 477
                 .and_then(|sim| sim.as_string())
478
-                .ok_or(AuthError::Structure(response_plist.clone()))?;
478
+                .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
479 479
 
480 480
             let sim = BASE64_STANDARD
481 481
                 .decode(sim)
... ...
@@ -487,7 +487,7 @@ pub async fn login(
487 487
             let url_switching_data = status
488 488
                 .get("X-Apple-I-Data")
489 489
                 .and_then(|data| data.as_string())
490
-                .ok_or(AuthError::Structure(response_plist.clone()))?;
490
+                .ok_or_else(|| AuthError::Structure(response_plist.clone()))?;
491 491
 
492 492
             Ok(AuthOutcome::UrlSwitchingRequired(
493 493
                 url_switching_data.to_string(),
... ...
@@ -495,3 +495,51 @@ pub async fn login(
495 495
         }
496 496
     }
497 497
 }
498
+
499
+#[derive(Error, Debug)]
500
+pub enum ValidateCodeError {
501
+    #[error("The provided code is not valid.")]
502
+    InvalidCode,
503
+    #[error("Could not log-in to Apple servers: {0}")]
504
+    Apple(#[from] AppleError),
505
+    #[error("Cannot generate device authentication data: {0}")]
506
+    Anisette(#[from] ADIError),
507
+    #[error("Network error: {0}")]
508
+    Network(#[from] reqwest::Error),
509
+    // vvv Internal errors vvv
510
+    #[error("Invalid URL bag")]
511
+    InvalidURLBag,
512
+    #[error("Cannot parse server response: {0}")]
513
+    Parsing(plist::Error),
514
+}
515
+
516
+pub async fn validate_code(
517
+    http_session: &AnisetteHTTPSession<'_, '_>,
518
+    second_factor_code: u32
519
+) -> Result<(), ValidateCodeError> {
520
+    let validate_code_url = http_session
521
+        .url_bag()
522
+        .get("validateCode")
523
+        .and_then(Value::as_string)
524
+        .ok_or(ValidateCodeError::InvalidURLBag)?;
525
+
526
+    let response = http_session
527
+        .anisette_request_builder(Method::POST, validate_code_url)?
528
+        .header("security-code", second_factor_code.to_string())
529
+        .send()
530
+        .await?
531
+        .error_for_status()?
532
+        .bytes()
533
+        .await?;
534
+
535
+    let response_plist: Dictionary =
536
+        plist::from_bytes(&response).map_err(ValidateCodeError::Parsing)?;
537
+
538
+    parse_status(&response_plist)
539
+        .map_err(|err|
540
+            match err {
541
+                AppleError { code: -21669, .. } => ValidateCodeError::InvalidCode,
542
+                err => ValidateCodeError::Apple(err)
543
+            }
544
+        )
545
+}
... ...
@@ -30,7 +30,7 @@ pub struct DeviceData {
30 30
     #[serde(rename = "dn")]
31 31
     pub device_name: Option<String>,
32 32
     #[serde(rename = "event")]
33
-    pub event: Option<String>,
33
+    pub event: Option<String>, // liveness
34 34
     #[serde(rename = "imei")]
35 35
     pub imei: Option<String>,
36 36
     #[serde(rename = "loc")]
37 37