WebAuthnRecoveryControllerTest.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. <?php
  2. namespace Tests\Feature\Http\Auth;
  3. use App\Models\User;
  4. use Database\Factories\UserFactory;
  5. use Illuminate\Support\Facades\Date;
  6. use Illuminate\Support\Facades\DB;
  7. use Tests\FeatureTestCase;
  8. /**
  9. * @covers \App\Http\Controllers\Auth\WebAuthnRecoveryController
  10. * @covers \App\Extensions\WebauthnCredentialBroker
  11. * @covers \App\Http\Requests\WebauthnRecoveryRequest
  12. * @covers \App\Providers\AuthServiceProvider
  13. */
  14. class WebAuthnRecoveryControllerTest extends FeatureTestCase
  15. {
  16. /**
  17. * @var \App\Models\User
  18. */
  19. protected $user;
  20. /**
  21. * @var
  22. */
  23. protected $now;
  24. const STORED_TOKEN_VALUE = '$2y$10$P6q8rl8te5QaO1EdpyJcNO0s9VFlVgf62KaItQhrPTskxfyu97mlW';
  25. const ACTUAL_TOKEN_VALUE = '9e583e3fb6c32034164ac62415c9657dcbd1fb861b434340b08a94c2075cac66';
  26. const CREDENTIAL_ID = '-VOLFKPY-_FuMI_sJ7gMllK76L3VoRUINj6lL_Z3qDg';
  27. /**
  28. * @test
  29. */
  30. public function setUp(): void
  31. {
  32. parent::setUp();
  33. $this->user = User::factory()->create();
  34. Date::setTestNow($this->now = Date::create(2022, 11, 16, 9, 4));
  35. DB::table('webauthn_recoveries')->insert([
  36. 'email' => $this->user->email,
  37. 'token' => self::STORED_TOKEN_VALUE,
  38. 'created_at' => $this->now->toDateTimeString(),
  39. ]);
  40. }
  41. /**
  42. * @test
  43. */
  44. public function test_recover_fails_if_no_recovery_is_set()
  45. {
  46. DB::table('webauthn_recoveries')->delete();
  47. $this->json('POST', '/webauthn/recover', [
  48. 'token' => self::ACTUAL_TOKEN_VALUE,
  49. 'email' => $this->user->email,
  50. 'password' => UserFactory::USER_PASSWORD,
  51. ])
  52. ->assertStatus(422)
  53. ->assertJsonValidationErrors('token');
  54. }
  55. /**
  56. * @test
  57. */
  58. public function test_recover_with_wrong_token_returns_validation_error()
  59. {
  60. $response = $this->json('POST', '/webauthn/recover', [
  61. 'token' => 'wrong_token',
  62. 'email' => $this->user->email,
  63. 'password' => UserFactory::USER_PASSWORD,
  64. ])
  65. ->assertStatus(422)
  66. ->assertJsonMissingValidationErrors('email')
  67. ->assertJsonValidationErrors('token');
  68. }
  69. /**
  70. * @test
  71. */
  72. public function test_recover_with_expired_token_returns_validation_error()
  73. {
  74. Date::setTestNow($now = Date::create(2020, 01, 01, 16, 30));
  75. DB::table('webauthn_recoveries')->delete();
  76. DB::table('webauthn_recoveries')->insert([
  77. 'token' => self::STORED_TOKEN_VALUE,
  78. 'email' => $this->user->email,
  79. 'created_at' => $now->clone()->subHour()->subSecond()->toDateTimeString(),
  80. ]);
  81. $this->json('POST', '/webauthn/recover', [
  82. 'token' => self::ACTUAL_TOKEN_VALUE,
  83. 'email' => $this->user->email,
  84. 'password' => UserFactory::USER_PASSWORD,
  85. ])
  86. ->assertStatus(422)
  87. ->assertJsonValidationErrors('token');
  88. }
  89. /**
  90. * @test
  91. */
  92. public function test_recover_with_invalid_password_returns_authentication_error()
  93. {
  94. $this->json('POST', '/webauthn/recover', [
  95. 'token' => self::ACTUAL_TOKEN_VALUE,
  96. 'email' => $this->user->email,
  97. 'password' => 'bad_password',
  98. ])
  99. ->assertStatus(401);
  100. }
  101. /**
  102. * @test
  103. */
  104. public function test_recover_returns_validation_error_when_no_user_exists()
  105. {
  106. $this->json('POST', '/webauthn/recover', [
  107. 'token' => self::ACTUAL_TOKEN_VALUE,
  108. 'email' => 'no@user.com',
  109. 'password' => UserFactory::USER_PASSWORD,
  110. ])
  111. ->assertStatus(422)
  112. ->assertJsonMissingValidationErrors('password')
  113. ->assertJsonMissingValidationErrors('token')
  114. ->assertJsonValidationErrors('email');
  115. }
  116. /**
  117. * @test
  118. */
  119. public function test_recover_returns_success()
  120. {
  121. $response = $this->json('POST', '/webauthn/recover', [
  122. 'token' => self::ACTUAL_TOKEN_VALUE,
  123. 'email' => $this->user->email,
  124. 'password' => UserFactory::USER_PASSWORD,
  125. ])
  126. ->assertStatus(200);
  127. $this->assertDatabaseMissing('webauthn_recoveries', [
  128. 'token' => self::STORED_TOKEN_VALUE,
  129. ]);
  130. $this->assertDatabaseMissing('options', [
  131. 'key' => 'useWebauthnOnly',
  132. ]);
  133. }
  134. /**
  135. * @test
  136. */
  137. public function test_revoke_all_credentials_clear_registered_credentials()
  138. {
  139. DB::table('webauthn_credentials')->insert([
  140. 'id' => self::CREDENTIAL_ID,
  141. 'authenticatable_type' => \App\Models\User::class,
  142. 'authenticatable_id' => $this->user->id,
  143. 'user_id' => 'e8af6f703f8042aa91c30cf72289aa07',
  144. 'counter' => 0,
  145. 'rp_id' => 'http://localhost',
  146. 'origin' => 'http://localhost',
  147. 'aaguid' => '00000000-0000-0000-0000-000000000000',
  148. 'attestation_format' => 'none',
  149. 'public_key' => 'eyJpdiI6Imp0U0NVeFNNbW45KzEvMXpad2p2SUE9PSIsInZhbHVlIjoic0VxZ2I1WnlHM2lJakhkWHVkK2kzMWtibk1IN2ZlaExGT01qOElXMDdRTjhnVlR0TDgwOHk1S0xQUy9BQ1JCWHRLNzRtenNsMml1dVQydWtERjFEU0h0bkJGT2RwUXE1M1JCcVpablE2Y2VGV2YvVEE2RGFIRUE5L0x1K0JIQXhLVE1aNVNmN3AxeHdjRUo2V0hwREZSRTJYaThNNnB1VnozMlVXZEVPajhBL3d3ODlkTVN3bW54RTEwSG0ybzRQZFFNNEFrVytUYThub2IvMFRtUlBZamoyZElWKzR1bStZQ1IwU3FXbkYvSm1FU2FlMTFXYUo0SG9kc1BDME9CNUNKeE9IelE5d2dmNFNJRXBKNUdlVzJ3VHUrQWJZRFluK0hib0xvVTdWQ0ZISjZmOWF3by83aVJES1dxbU9Zd1lhRTlLVmhZSUdlWmlBOUFtcTM2ZVBaRWNKNEFSQUhENk5EaC9hN3REdnVFbm16WkRxekRWOXd4cVcvZFdKa2tlWWJqZWlmZnZLS0F1VEVCZEZQcXJkTExiNWRyQmxsZWtaSDRlT3VVS0ZBSXFBRG1JMjRUMnBKRXZxOUFUa2xxMjg2TEplUzdscVo2UytoVU5SdXk1OE1lcFN6aU05ZkVXTkdIM2tKM3Q5bmx1TGtYb1F5bGxxQVR3K3BVUVlia1VybDFKRm9lZDViNzYraGJRdmtUb2FNTEVGZmZYZ3lYRDRiOUVjRnJpcTVvWVExOHJHSTJpMnVBZ3E0TmljbUlKUUtXY2lSWDh1dE5MVDNRUzVRSkQrTjVJUU8rSGhpeFhRRjJvSEdQYjBoVT0iLCJtYWMiOiI5MTdmNWRkZGE5OTEwNzQ3MjhkYWVhYjRlNjk0MWZlMmI5OTQ4YzlmZWI1M2I4OGVkMjE1MjMxNjUwOWRmZTU2IiwidGFnIjoiIn0=',
  150. 'updated_at' => now(),
  151. 'created_at' => now(),
  152. ]);
  153. $response = $this->json('POST', '/webauthn/recover', [
  154. 'token' => self::ACTUAL_TOKEN_VALUE,
  155. 'email' => $this->user->email,
  156. 'password' => UserFactory::USER_PASSWORD,
  157. 'revokeAll' => true,
  158. ])
  159. ->assertStatus(200);
  160. $this->assertDatabaseMissing('webauthn_credentials', [
  161. 'authenticatable_id' => $this->user->id,
  162. ]);
  163. }
  164. }