oauth2-passkey 0.6.1

OAuth2 and Passkey authentication library for Rust web applications
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
use super::*;
use crate::passkey::main::types;
use crate::storage::{CacheKey, CachePrefix};
use crate::test_utils::init_test_environment;

// Create a module alias for our test utils
use crate::passkey::main::test_utils as passkey_test_utils;

fn create_test_authenticator_response(
    user_handle: Option<String>,
    auth_id: String,
) -> AuthenticatorResponse {
    // Note: This is a minimal mock for testing verify_user_handle
    // In real usage, AuthenticatorResponse has many more fields
    AuthenticatorResponse::new_for_test(
        "test_credential_id".to_string(),
        types::AuthenticatorAssertionResponse {
            client_data_json: "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0In0".to_string(), // {"type":"webauthn.get"}
            authenticator_data: "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAABA".to_string(),
            signature: "MEUCIQDsignature".to_string(),
            user_handle,
        },
        auth_id,
    )
}

fn create_test_passkey_credential(user_handle: String) -> PasskeyCredential {
    PasskeyCredential {
        sequence_number: None,
        credential_id: "test_credential_id".to_string(),
        user_id: "test_user_id".to_string(),
        public_key: "test_public_key".to_string(),
        aaguid: "test_aaguid".to_string(),
        rp_id: "localhost".to_string(),
        counter: 1,
        user: PublicKeyCredentialUserEntity {
            user_handle,
            name: "test_user".to_string(),
            display_name: "Test User".to_string(),
        },
        created_at: chrono::Utc::now(),
        updated_at: chrono::Utc::now(),
        last_used_at: chrono::Utc::now(),
    }
}

fn create_test_authenticator_data(counter: u32) -> AuthenticatorData {
    AuthenticatorData {
        rp_id_hash: vec![0; 32],
        flags: 0x01 | 0x04, // UP | UV flags set
        counter,
        raw_data: vec![],
    }
}

#[cfg(test)]
use serial_test::serial;

/// Test start authentication with no username
///
/// This test verifies that `start_authentication` can handle requests without a username
/// by generating anonymous authentication options. It validates that the function creates
/// proper authentication options with empty credentials list and valid challenge data.
#[tokio::test]
async fn test_start_authentication_no_username() {
    // Initialize test environment (configures global GENERIC_CACHE_STORE)
    init_test_environment().await;

    let result = start_authentication(None).await;
    assert!(result.is_ok());

    let auth_options = result.unwrap();
    assert!(auth_options.allow_credentials.is_empty());
    assert!(!auth_options.challenge.is_empty());
    assert!(!auth_options.auth_id.is_empty());
    assert_eq!(auth_options.rp_id, *crate::passkey::config::PASSKEY_RP_ID);
    assert_eq!(
        auth_options.user_verification,
        *crate::passkey::config::PASSKEY_USER_VERIFICATION
    );
}

/// Test start authentication generates unique IDs
///
/// This test verifies that `start_authentication` generates unique challenge and auth_id
/// values on each invocation to prevent replay attacks. It calls the function multiple
/// times and ensures all generated identifiers are unique.
#[tokio::test]
async fn test_start_authentication_generates_unique_ids() {
    // Initialize test environment (configures global GENERIC_CACHE_STORE)
    init_test_environment().await;

    let result1 = start_authentication(None).await;
    let result2 = start_authentication(None).await;

    assert!(result1.is_ok());
    assert!(result2.is_ok());

    let auth1 = result1.unwrap();
    let auth2 = result2.unwrap();

    // Ensure unique challenges and auth IDs
    assert_ne!(auth1.challenge, auth2.challenge);
    assert_ne!(auth1.auth_id, auth2.auth_id);
}

/// Test verify user handle real function matching handles
///
/// This test verifies that `verify_user_handle` correctly validates user handles when
/// the authenticator response and stored credential have matching user handle values.
/// It creates test data with matching handles and validates successful verification.
#[test]
fn test_verify_user_handle_real_function_matching_handles() {
    let auth_response = create_test_authenticator_response(
        Some("test_user_handle".to_string()),
        "test_auth_id".to_string(),
    );
    let credential = create_test_passkey_credential("test_user_handle".to_string());

    // Test both discoverable and non-discoverable cases
    let result1 = verify_user_handle(&auth_response, &credential, true);
    let result2 = verify_user_handle(&auth_response, &credential, false);

    assert!(
        result1.is_ok(),
        "Should succeed with matching handles (discoverable)"
    );
    assert!(
        result2.is_ok(),
        "Should succeed with matching handles (non-discoverable)"
    );
}

/// Test verify user handle real function mismatched handles
///
/// This test verifies that `verify_user_handle` correctly rejects authentication attempts
/// when the user handle in the authenticator response doesn't match the stored credential.
/// It tests both discoverable and non-discoverable credential scenarios with mismatched handles.
///
#[test]
fn test_verify_user_handle_real_function_mismatched_handles() {
    let auth_response = create_test_authenticator_response(
        Some("wrong_handle".to_string()),
        "test_auth_id".to_string(),
    );
    let credential = create_test_passkey_credential("correct_handle".to_string());

    // Test both discoverable and non-discoverable cases
    let result1 = verify_user_handle(&auth_response, &credential, true);
    let result2 = verify_user_handle(&auth_response, &credential, false);

    // Both should fail with Authentication error
    assert!(
        result1.is_err(),
        "Should fail with mismatched handles (discoverable)"
    );
    if let Err(PasskeyError::Authentication(msg)) = &result1 {
        assert!(
            msg.contains("User handle mismatch"),
            "Expected 'User handle mismatch' error but got: {msg}"
        );
    } else {
        panic!("Expected PasskeyError::Authentication but got: {result1:?}");
    }

    assert!(
        result2.is_err(),
        "Should fail with mismatched handles (non-discoverable)"
    );
    if let Err(PasskeyError::Authentication(msg)) = &result2 {
        assert!(
            msg.contains("User handle mismatch"),
            "Expected 'User handle mismatch' error but got: {msg}"
        );
    } else {
        panic!("Expected PasskeyError::Authentication but got: {result2:?}");
    }
}

/// Test verify user handle real function missing handle
///
/// This test verifies that `verify_user_handle` correctly handles cases where the user handle
/// is missing from the authenticator response. It tests that discoverable credentials require
/// a user handle while non-discoverable credentials can work without one.
///
#[test]
fn test_verify_user_handle_real_function_missing_handle() {
    let credential = create_test_passkey_credential("test_handle".to_string());

    // Test discoverable case (should fail)
    let auth_response_discoverable =
        create_test_authenticator_response(None, "test_auth_id".to_string());
    let result_discoverable = verify_user_handle(&auth_response_discoverable, &credential, true);

    assert!(
        result_discoverable.is_err(),
        "Should fail with missing user handle for discoverable credential"
    );
    if let Err(PasskeyError::Authentication(msg)) = &result_discoverable {
        assert!(
            msg.contains("Missing required user handle"),
            "Expected 'Missing required user handle' error but got: {msg}"
        );
    } else {
        panic!("Expected PasskeyError::Authentication but got: {result_discoverable:?}");
    }

    // Test non-discoverable case (should succeed)
    let auth_response_non_discoverable =
        create_test_authenticator_response(None, "test_auth_id".to_string());
    let result_non_discoverable =
        verify_user_handle(&auth_response_non_discoverable, &credential, false);

    assert!(
        result_non_discoverable.is_ok(),
        "Non-discoverable credential should allow missing user handle"
    );
}

/// Test verify user handle edge cases
///
/// This test verifies that `verify_user_handle` handles edge cases correctly, including
/// empty string user handles and mismatched empty vs non-empty handles. It tests various
/// boundary conditions to ensure robust handle validation.
#[test]
fn test_verify_user_handle_edge_cases() {
    // Test empty string user handle
    let auth_response_empty =
        create_test_authenticator_response(Some("".to_string()), "test_auth_id".to_string());
    let credential_empty = create_test_passkey_credential("".to_string());
    let result_empty = verify_user_handle(&auth_response_empty, &credential_empty, true);
    assert!(
        result_empty.is_ok(),
        "Should succeed with matching empty handles"
    );

    // Test empty vs non-empty mismatch
    let credential_non_empty = create_test_passkey_credential("non_empty".to_string());
    let result_mismatch = verify_user_handle(&auth_response_empty, &credential_non_empty, false);
    assert!(
        result_mismatch.is_err(),
        "Should fail with empty vs non-empty handle mismatch"
    );
}

/// Test verify counter authenticator no counter support
///
/// This test verifies that `verify_counter` handles authenticators that don't support
/// signature counters (counter = 0). It validates that the function succeeds without
/// updating the counter when the authenticator doesn't provide counter functionality.
#[tokio::test]
async fn test_verify_counter_authenticator_no_counter_support() {
    // Test case: authenticator doesn't support counters (counter = 0)
    let passkey = create_test_passkey_credential("test_user".to_string());
    let auth_data = create_test_authenticator_data(0);

    let result = verify_counter(
        CredentialId::new(passkey.credential_id.clone()).expect("Valid credential ID"),
        &auth_data,
        &passkey,
    )
    .await;
    assert!(result.is_ok());
    // Counter should not be updated when response counter is 0 (test passes if no DB error)
}

/// Test verify counter replay attack detection
///
/// This test verifies that `verify_counter` correctly detects replay attacks by rejecting
/// authentication attempts where the counter value is less than the stored counter.
/// It simulates a credential cloning attack scenario and validates proper error handling.
#[tokio::test]
async fn test_verify_counter_replay_attack_detection() {
    // Test case: counter is less than stored counter (replay attack)
    let mut passkey = create_test_passkey_credential("test_user".to_string());
    passkey.counter = 10;
    let auth_data = create_test_authenticator_data(5);

    let result = verify_counter(
        CredentialId::new(passkey.credential_id.clone()).expect("Valid credential ID"),
        &auth_data,
        &passkey,
    )
    .await;
    assert!(result.is_err());

    if let Err(PasskeyError::Authentication(msg)) = result {
        assert!(msg.contains("credential cloning detected"));
    } else {
        panic!("Expected Authentication error");
    }
}

/// Test verify counter equal counter replay attack
///
/// This test verifies that `verify_counter` correctly detects replay attacks when the
/// counter value equals the stored counter. Equal counters indicate potential replay
/// attacks and should be rejected to maintain security.
#[tokio::test]
async fn test_verify_counter_equal_counter_replay_attack() {
    // Test case: counter equals stored counter (still a replay attack)
    let mut passkey = create_test_passkey_credential("test_user".to_string());
    passkey.counter = 10;
    let auth_data = create_test_authenticator_data(10);

    let result = verify_counter(
        CredentialId::new(passkey.credential_id.clone()).expect("Valid credential ID"),
        &auth_data,
        &passkey,
    )
    .await;
    assert!(result.is_err());

    if let Err(PasskeyError::Authentication(msg)) = result {
        assert!(msg.contains("credential cloning detected"));
    } else {
        panic!("Expected Authentication error");
    }
}

/// Test verify counter valid increment
///
/// This test verifies that `verify_counter` accepts valid authentication attempts where
/// the counter value is greater than the stored counter. It validates that legitimate
/// counter increments are properly accepted and processed.
#[tokio::test]
async fn test_verify_counter_valid_increment() {
    // Test case: counter is greater than stored counter (valid)
    let mut passkey = create_test_passkey_credential("test_user".to_string());
    passkey.counter = 10;
    let auth_data = create_test_authenticator_data(15);

    let result = verify_counter_without_db(&auth_data, &passkey).await;
    assert!(result.is_ok());
}

/// Test verify counter zero to positive
///
/// This test verifies that `verify_counter` handles the transition from zero counter
/// to positive values, which occurs during the first use of a counter-supporting
/// authenticator. It validates this legitimate counter initialization scenario.
#[tokio::test]
async fn test_verify_counter_zero_to_positive() {
    // Test case: counter going from 0 to positive (first use of counter-supporting authenticator)
    let mut passkey = create_test_passkey_credential("test_user".to_string());
    passkey.counter = 0; // Stored counter is 0 (authenticator didn't support counters before)
    let auth_data = create_test_authenticator_data(1); // Now receiving counter value 1

    let result = verify_counter_without_db(&auth_data, &passkey).await;
    assert!(result.is_ok());
}

/// Test verify counter large increment
///
/// This test verifies that `verify_counter` accepts large but valid counter increments.
/// Large increments can occur with heavily used authenticators and should be accepted
/// as long as they're greater than the stored counter value.
#[tokio::test]
async fn test_verify_counter_large_increment() {
    // Test case: large counter increment (should still be valid)
    let mut passkey = create_test_passkey_credential("test_user".to_string());
    passkey.counter = 100;
    let auth_data = create_test_authenticator_data(1000);

    let result = verify_counter_without_db(&auth_data, &passkey).await;
    assert!(result.is_ok());
}

/// Test-only version of verify_counter that checks counter logic without DB access.
#[cfg(test)]
async fn verify_counter_without_db(
    auth_data: &AuthenticatorData,
    stored_credential: &PasskeyCredential,
) -> Result<(), PasskeyError> {
    let auth_counter = auth_data.counter;

    if auth_counter == 0 {
        tracing::info!("Authenticator does not support counters (received counter=0)");
    } else if auth_counter <= stored_credential.counter {
        return Err(PasskeyError::Authentication(
            "Counter value decreased - possible credential cloning detected. For more details, run with RUST_LOG=debug".into(),
        ));
    }

    Ok(())
}

// Tests for verify_signature function

fn create_test_parsed_client_data(challenge: &str) -> ParsedClientData {
    ParsedClientData {
        challenge: challenge.to_string(),
        origin: "https://example.com".to_string(),
        type_: "webauthn.get".to_string(),
        raw_data: b"test_client_data".to_vec(),
    }
}

fn create_test_authenticator_data_with_raw(counter: u32, raw_data: Vec<u8>) -> AuthenticatorData {
    AuthenticatorData {
        rp_id_hash: vec![0; 32],
        flags: 0x01 | 0x04, // UP | UV flags set
        counter,
        raw_data,
    }
}

/// Test verify signature invalid public key format
///
/// This test verifies that `verify_signature` returns appropriate errors when given
/// invalid base64-encoded public key data. It tests the function's ability to handle
/// malformed public key inputs and return proper error messages.
#[tokio::test]
async fn test_verify_signature_invalid_public_key_format() {
    // Test case: invalid base64 public key
    let auth_response = create_test_authenticator_response(
        Some("test_user".to_string()),
        "test_auth_id".to_string(),
    );
    let client_data = create_test_parsed_client_data("test_challenge");
    let auth_data = create_test_authenticator_data_with_raw(1, vec![0; 37]);

    let mut credential = create_test_passkey_credential("test_user".to_string());
    credential.public_key = "invalid_base64!".to_string(); // Invalid base64

    let result = verify_signature(&auth_response, &client_data, &auth_data, &credential).await;
    assert!(result.is_err());

    if let Err(PasskeyError::Format(msg)) = result {
        assert!(msg.contains("Invalid public key"));
    } else {
        panic!("Expected Format error for invalid public key");
    }
}

/// Test verify signature invalid signature format
///
/// This test verifies that `verify_signature` returns appropriate errors when given
/// invalid base64-encoded signature data. It tests the function's ability to handle
/// malformed signature inputs and return proper error messages.
#[tokio::test]
async fn test_verify_signature_invalid_signature_format() {
    // Test case: invalid base64 signature
    let mut auth_response = create_test_authenticator_response(
        Some("test_user".to_string()),
        "test_auth_id".to_string(),
    );
    auth_response.response.signature = "invalid_base64!".to_string(); // Invalid base64

    let client_data = create_test_parsed_client_data("test_challenge");
    let auth_data = create_test_authenticator_data_with_raw(1, vec![0; 37]);
    let mut credential = create_test_passkey_credential("test_user".to_string());
    // Use a valid base64 string for public key
    credential.public_key = crate::utils::base64url_encode(vec![0; 64]).unwrap();

    let result = verify_signature(&auth_response, &client_data, &auth_data, &credential).await;
    assert!(result.is_err());

    match result {
        Err(error) => {
            if let PasskeyError::Format(ref msg) = error {
                assert!(msg.contains("Invalid signature"));
            } else {
                panic!("Expected Format error for invalid signature format, got: {error:?}");
            }
        }
        Ok(_) => panic!("Expected error but got success"),
    }
}

/// Test verify signature verification failure
///
/// This test verifies that `verify_signature` correctly rejects authentication attempts
/// with valid format but incorrect signature data. It tests the cryptographic signature
/// verification process and ensures invalid signatures are properly rejected.
#[tokio::test]
async fn test_verify_signature_verification_failure() {
    // Test case: valid format but signature verification fails
    let auth_response = create_test_authenticator_response(
        Some("test_user".to_string()),
        "test_auth_id".to_string(),
    );
    let client_data = create_test_parsed_client_data("test_challenge");
    let auth_data = create_test_authenticator_data_with_raw(1, vec![0; 37]);

    let mut credential = create_test_passkey_credential("test_user".to_string());
    // Use a valid base64 string but invalid public key data
    credential.public_key = crate::utils::base64url_encode(vec![0; 64]).unwrap();

    let result = verify_signature(&auth_response, &client_data, &auth_data, &credential).await;
    assert!(result.is_err());

    if let Err(PasskeyError::Verification(msg)) = result {
        assert!(msg.contains("Signature verification failed"));
    } else {
        panic!("Expected Verification error for signature mismatch");
    }
}

/// Test verify signature empty signature
///
/// This test verifies that `verify_signature` handles empty signature inputs correctly
/// by returning appropriate errors. It tests the function's validation of required
/// signature data and ensures empty signatures are properly rejected.
#[tokio::test]
async fn test_verify_signature_empty_signature() {
    // Test case: empty signature
    let mut auth_response = create_test_authenticator_response(
        Some("test_user".to_string()),
        "test_auth_id".to_string(),
    );
    auth_response.response.signature = "".to_string(); // Empty signature

    let client_data = create_test_parsed_client_data("test_challenge");
    let auth_data = create_test_authenticator_data_with_raw(1, vec![0; 37]);
    let mut credential = create_test_passkey_credential("test_user".to_string());
    // Use a valid base64 string for public key
    credential.public_key = crate::utils::base64url_encode(vec![0; 64]).unwrap();

    let result = verify_signature(&auth_response, &client_data, &auth_data, &credential).await;
    assert!(result.is_err());

    match result {
        Err(error) => {
            // Empty signature should be a verification error since empty string is valid base64
            if let PasskeyError::Verification(ref msg) = error {
                assert!(msg.contains("Signature verification failed"));
            } else {
                panic!("Expected Verification error for empty signature, got: {error:?}");
            }
        }
        Ok(_) => panic!("Expected error but got success"),
    }
}

/// Test verify signature empty public key
///
/// This test verifies that `verify_signature` handles empty public key inputs correctly
/// by returning appropriate errors. It tests the function's validation of required
/// public key data and ensures empty keys are properly rejected.
#[tokio::test]
async fn test_verify_signature_empty_public_key() {
    // Test case: empty public key
    let auth_response = create_test_authenticator_response(
        Some("test_user".to_string()),
        "test_auth_id".to_string(),
    );
    let client_data = create_test_parsed_client_data("test_challenge");
    let auth_data = create_test_authenticator_data_with_raw(1, vec![0; 37]);

    let mut credential = create_test_passkey_credential("test_user".to_string());
    credential.public_key = "".to_string(); // Empty public key

    let result = verify_signature(&auth_response, &client_data, &auth_data, &credential).await;
    assert!(result.is_err());

    match result {
        Err(error) => {
            if let PasskeyError::Verification(ref msg) = error {
                assert!(msg.contains("Signature verification failed"));
            } else {
                panic!("Expected Verification error for empty public key, got: {error:?}");
            }
        }
        Ok(_) => panic!("Expected error but got success"),
    }
}

/// Test verify signature malformed data structures
///
/// This test verifies that `verify_signature` robustly handles various types of malformed
/// input data structures. It tests the function's error handling capabilities with
/// corrupted or invalid data to ensure proper validation and error reporting.
#[tokio::test]
async fn test_verify_signature_malformed_data_structures() {
    // Test case: test with various malformed data to ensure robust error handling
    let auth_response = create_test_authenticator_response(
        Some("test_user".to_string()),
        "test_auth_id".to_string(),
    );
    let client_data = create_test_parsed_client_data("test_challenge");

    // Test with empty raw data in auth_data
    let auth_data_empty = create_test_authenticator_data_with_raw(1, vec![]);
    let credential = create_test_passkey_credential("test_user".to_string());

    let result =
        verify_signature(&auth_response, &client_data, &auth_data_empty, &credential).await;
    assert!(result.is_err());

    // Should fail at verification stage since we have empty auth data
    match result {
        Err(PasskeyError::Format(_)) | Err(PasskeyError::Verification(_)) => {
            // Either error type is acceptable for malformed data
        }
        _ => panic!("Expected Format or Verification error for malformed data"),
    }
}

/// Test finish authentication integration
///
/// This test verifies the complete authentication flow by testing the integration
/// between multiple authentication components. It validates the end-to-end process
/// of finishing authentication with proper credential verification and database updates.
#[tokio::test]
#[serial]
async fn test_finish_authentication_integration_test() {
    // Initialize test environment
    init_test_environment().await;

    // Setup test credential
    let credential_id = "test_credential_id_123";
    let user_id = "test_user_id_123";
    let user_handle = "test_user_handle_123";
    let public_key = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEckXwaEBJmwp0EVElviOu9HLgrk3TA/RG4hxcXGYkcCKZ0FIwSkFS6YmGAhRC1nckV0/KQ0/Qpw8WTgK2KQEteA==";
    let aaguid = "f8e2d612-b2cc-4536-a028-ec advocating1951db";

    // Insert test credential with user
    let credential_data = passkey_test_utils::TestCredentialData::new(
        credential_id,
        user_id,
        user_handle,
        "Test User",
        "Test Display Name",
        public_key,
        aaguid,
        42,
    );
    let result = passkey_test_utils::insert_test_user_and_credential(credential_data).await;
    if let Err(e) = &result {
        println!("Error inserting test credential: {e:?}");
    }
    assert!(
        result.is_ok(),
        "Failed to insert test credential: {result:?}"
    );

    // Verify that the credential was inserted correctly
    let get_result = PasskeyStore::get_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await;
    assert!(get_result.is_ok(), "Failed to retrieve test credential");
    let credential_option = get_result.unwrap();
    assert!(credential_option.is_some(), "Credential should exist");

    let credential = credential_option.unwrap();
    assert_eq!(credential.user_id, user_id);
    assert_eq!(credential.user.user_handle, user_handle);

    // Clean up test data
    let cleanup_result = passkey_test_utils::cleanup_test_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await;
    assert!(cleanup_result.is_ok(), "Failed to clean up test credential");

    // Verify credential was deleted
    let verify_deleted = PasskeyStore::get_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await;
    assert!(
        verify_deleted.unwrap().is_none(),
        "Credential should be deleted after cleanup"
    );
}

/// Test start authentication integration
///
/// This test verifies the complete authentication initialization flow by testing
/// the integration of authentication options generation, challenge storage, and
/// credential preparation. It validates the full authentication startup process.
#[tokio::test]
#[serial]
async fn test_start_authentication_integration() {
    use crate::passkey::main::test_utils as passkey_test_utils;
    use crate::storage::GENERIC_CACHE_STORE;
    use crate::test_utils::init_test_environment;

    init_test_environment().await;

    // Create test credential in the store
    let credential_id = "auth_test_credential_id";
    let user_id = "auth_test_user_id";
    let user_handle = "auth_test_user_handle";
    let public_key = "test_public_key_auth";
    let username = "auth_test_user";

    // Insert test credential with user
    let credential_data = passkey_test_utils::TestCredentialData::new(
        credential_id,
        user_id,
        user_handle,
        username,
        "Auth Test User",
        public_key,
        "test_aaguid",
        10, // Counter
    );
    let insert_result = passkey_test_utils::insert_test_user_and_credential(credential_data).await;
    assert!(insert_result.is_ok(), "Failed to insert test credential");

    // Call start_authentication with the username
    let auth_options = super::start_authentication(Some(username.to_string())).await;
    assert!(auth_options.is_ok(), "Failed to start authentication");

    let options = auth_options.unwrap();

    // Verify that options include the credential
    assert!(
        !options.allow_credentials.is_empty(),
        "No credentials found in options"
    );
    assert_eq!(options.allow_credentials[0].id, credential_id);

    // Verify that a challenge was stored in cache
    let auth_id = options.auth_id;
    let cache_prefix = CachePrefix::new("authentication".to_string()).unwrap();
    let cache_key = CacheKey::new(auth_id.clone()).unwrap();
    let cache_get = GENERIC_CACHE_STORE
        .lock()
        .await
        .get(cache_prefix, cache_key)
        .await;
    assert!(cache_get.is_ok());
    assert!(cache_get.unwrap().is_some(), "Challenge should be in cache");

    // Clean up
    let cache_prefix = CachePrefix::new("authentication".to_string()).unwrap();
    let cache_key = CacheKey::new(auth_id.clone()).unwrap();
    let remove_cache = passkey_test_utils::remove_from_cache(cache_prefix, cache_key).await;
    assert!(remove_cache.is_ok(), "Failed to clean up cache");

    let remove_credential = passkey_test_utils::cleanup_test_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await;
    assert!(remove_credential.is_ok(), "Failed to clean up credential");
}

/// Test verify counter and update
///
/// This test verifies the counter verification and database update functionality
/// in a complete integration scenario. It tests both the counter validation logic
/// and the database persistence of updated counter values.
#[tokio::test]
#[serial]
async fn test_verify_counter_and_update() {
    use crate::passkey::main::test_utils as passkey_test_utils;
    use crate::test_utils::init_test_environment;

    init_test_environment().await;

    // Setup test credential with initial counter
    let credential_id = "counter_test_credential_id";
    let initial_counter = 10;

    // Insert test credential with user
    let credential_data = passkey_test_utils::TestCredentialData::new(
        credential_id,
        "counter_test_user_id",
        "counter_test_user_handle",
        "counter_test_user",
        "Counter Test User",
        "test_public_key",
        "test_aaguid",
        initial_counter,
    );
    let insert_result = passkey_test_utils::insert_test_user_and_credential(credential_data).await;
    assert!(insert_result.is_ok(), "Failed to insert test credential");

    // Get the credential to pass to counter verification
    let credential = crate::passkey::PasskeyStore::get_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await
    .expect("Failed to get credential")
    .expect("Credential not found");

    // Create authenticator data using the existing test helper
    let auth_data = create_test_authenticator_data(initial_counter + 5);

    // Verify counter - should pass and update
    let verify_result = super::verify_counter(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
        &auth_data,
        &credential,
    )
    .await;
    assert!(verify_result.is_ok(), "Counter verification failed");

    // Check that counter was updated in the store
    let updated_credential = crate::passkey::PasskeyStore::get_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await
    .expect("Failed to get credential")
    .expect("Credential not found");

    assert_eq!(
        updated_credential.counter,
        initial_counter + 5,
        "Counter was not updated correctly"
    );

    // Test with counter that didn't increase (should fail)
    let auth_data_same = create_test_authenticator_data(initial_counter);

    let verify_result_2 = super::verify_counter(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
        &auth_data_same,
        &updated_credential,
    )
    .await;
    assert!(
        verify_result_2.is_err(),
        "Should fail with non-increasing counter"
    );

    // Clean up
    let cleanup = passkey_test_utils::cleanup_test_credential(
        CredentialId::new(credential_id.to_string()).expect("Valid credential ID"),
    )
    .await;
    assert!(cleanup.is_ok(), "Failed to clean up test credential");
}

/// Test verify user handle
///
/// This test verifies the user handle validation functionality for non-discoverable
/// credentials without user handles. It tests the authentication flow for credentials
/// that don't require user handle validation.
#[tokio::test]
async fn test_verify_user_handle() {
    // Test for non-discoverable credential without user handle
    let stored_credential = create_test_passkey_credential("test_user".to_string());

    // Case 1: Non-discoverable credential without user handle (should pass)
    let auth_response_no_handle =
        create_test_authenticator_response(None, "test_auth_id".to_string());
    let result1 = super::verify_user_handle(&auth_response_no_handle, &stored_credential, false);
    assert!(
        result1.is_ok(),
        "Non-discoverable credential without handle should pass"
    );

    // Case 2: Non-discoverable credential with matching user handle (should pass)
    let auth_response_matching = create_test_authenticator_response(
        Some(stored_credential.user.user_handle.clone()),
        "test_auth_id".to_string(),
    );
    let result2 = super::verify_user_handle(&auth_response_matching, &stored_credential, false);
    assert!(
        result2.is_ok(),
        "Non-discoverable credential with matching handle should pass"
    );

    // Case 3: Discoverable credential without user handle (should fail)
    let result3 = super::verify_user_handle(&auth_response_no_handle, &stored_credential, true);
    assert!(
        result3.is_err(),
        "Discoverable credential without handle should fail"
    );

    // Case 4: Credential with mismatched user handle (should fail)
    let auth_response_mismatched = create_test_authenticator_response(
        Some("different_user_handle".to_string()),
        "test_auth_id".to_string(),
    );
    let result4 = super::verify_user_handle(&auth_response_mismatched, &stored_credential, false);
    assert!(
        result4.is_err(),
        "Credential with mismatched handle should fail"
    );
}