WebAuthnDeviceLostControllerTest.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <?php
  2. namespace Tests\Feature\Http\Auth;
  3. use App\Extensions\WebauthnCredentialBroker;
  4. use App\Http\Controllers\Auth\WebAuthnDeviceLostController;
  5. use App\Http\Requests\WebauthnDeviceLostRequest;
  6. use App\Models\User;
  7. use App\Notifications\WebauthnRecoveryNotification;
  8. use App\Providers\AuthServiceProvider;
  9. use App\Rules\CaseInsensitiveEmailExists;
  10. use Illuminate\Support\Facades\Lang;
  11. use Illuminate\Support\Facades\Notification;
  12. use PHPUnit\Framework\Attributes\CoversClass;
  13. use PHPUnit\Framework\Attributes\CoversMethod;
  14. use PHPUnit\Framework\Attributes\Test;
  15. use Tests\FeatureTestCase;
  16. /**
  17. * WebAuthnDeviceLostControllerTest test class
  18. */
  19. #[CoversClass(WebAuthnDeviceLostController::class)]
  20. #[CoversClass(WebauthnRecoveryNotification::class)]
  21. #[CoversClass(WebauthnCredentialBroker::class)]
  22. #[CoversClass(WebauthnDeviceLostRequest::class)]
  23. #[CoversClass(AuthServiceProvider::class)]
  24. #[CoversMethod(CaseInsensitiveEmailExists::class, 'validate')]
  25. class WebAuthnDeviceLostControllerTest extends FeatureTestCase
  26. {
  27. /**
  28. * @var \App\Models\User
  29. */
  30. protected $user;
  31. public function setUp() : void
  32. {
  33. parent::setUp();
  34. $this->user = User::factory()->create();
  35. }
  36. #[Test]
  37. public function test_sendRecoveryEmail_sends_notification_on_success()
  38. {
  39. Notification::fake();
  40. $response = $this->json('POST', '/webauthn/lost', [
  41. 'email' => $this->user->email,
  42. ]);
  43. Notification::assertSentTo($this->user, WebauthnRecoveryNotification::class);
  44. $response->assertStatus(200)
  45. ->assertJsonStructure([
  46. 'message',
  47. ]);
  48. $this->assertDatabaseHas(config('auth.passwords.webauthn.table'), [
  49. 'email' => $this->user->email,
  50. ]);
  51. }
  52. #[Test]
  53. public function test_WebauthnRecoveryNotification_renders_to_email()
  54. {
  55. $mail = (new WebauthnRecoveryNotification('test_token'))->toMail($this->user)->render();
  56. $this->assertStringContainsString(
  57. 'http://localhost/webauthn/recover?token=test_token&amp;email=' . urlencode($this->user->email),
  58. $mail
  59. );
  60. $this->assertStringContainsString(
  61. Lang::get('Recover Account'),
  62. $mail
  63. );
  64. $this->assertStringContainsString(
  65. Lang::get(
  66. 'You are receiving this email because we received an account recovery request for your account.'
  67. ),
  68. $mail
  69. );
  70. $this->assertStringContainsString(
  71. Lang::get(
  72. 'This recovery link will expire in :count minutes.',
  73. ['count' => config('auth.passwords.webauthn.expire')]
  74. ),
  75. $mail
  76. );
  77. $this->assertStringContainsString(
  78. Lang::get('If you did not request an account recovery, no further action is required.'),
  79. $mail
  80. );
  81. }
  82. #[Test]
  83. public function test_sendRecoveryEmail_does_not_send_anything_to_unknown_email()
  84. {
  85. Notification::fake();
  86. $response = $this->json('POST', '/webauthn/lost', [
  87. 'email' => 'bad@email.com',
  88. ]);
  89. Notification::assertNothingSent();
  90. $response->assertStatus(422)
  91. ->assertJsonValidationErrors([
  92. 'email',
  93. ]);
  94. $this->assertDatabaseMissing(config('auth.passwords.webauthn.table'), [
  95. 'email' => 'bad@email.com',
  96. ]);
  97. }
  98. #[Test]
  99. public function test_sendRecoveryEmail_does_not_send_anything_to_invalid_email()
  100. {
  101. Notification::fake();
  102. $response = $this->json('POST', '/webauthn/lost', [
  103. 'email' => 'bad@email.com',
  104. ]);
  105. Notification::assertNothingSent();
  106. $response->assertStatus(422)
  107. ->assertJsonValidationErrors([
  108. 'email',
  109. ]);
  110. $this->assertDatabaseMissing(config('auth.passwords.webauthn.table'), [
  111. 'email' => 'bad@email.com',
  112. ]);
  113. }
  114. #[Test]
  115. public function test_sendRecoveryEmail_does_not_send_anything_to_not_WebAuthnAuthenticatable()
  116. {
  117. $mock = $this->mock(\App\Extensions\WebauthnCredentialBroker::class)->makePartial();
  118. $mock->shouldReceive('getUser')
  119. ->andReturn(new \Illuminate\Foundation\Auth\User());
  120. Notification::fake();
  121. $response = $this->json('POST', '/webauthn/lost', [
  122. 'email' => $this->user->email,
  123. ]);
  124. Notification::assertNothingSent();
  125. $response->assertStatus(422)
  126. ->assertJsonValidationErrors([
  127. 'email',
  128. ]);
  129. }
  130. #[Test]
  131. public function test_sendRecoveryEmail_is_throttled()
  132. {
  133. Notification::fake();
  134. $response = $this->json('POST', '/webauthn/lost', [
  135. 'email' => $this->user->email,
  136. ]);
  137. Notification::assertSentTo($this->user, WebauthnRecoveryNotification::class);
  138. $response->assertStatus(200)
  139. ->assertJsonStructure([
  140. 'message',
  141. ]);
  142. $this->assertDatabaseHas(config('auth.passwords.webauthn.table'), [
  143. 'email' => $this->user->email,
  144. ]);
  145. $this->json('POST', '/webauthn/lost', [
  146. 'email' => $this->user->email,
  147. ])
  148. ->assertStatus(422)
  149. ->assertJsonValidationErrorfor('email')
  150. ->assertJsonFragment([
  151. 'message' => __('passwords.throttled'),
  152. ]);
  153. }
  154. #[Test]
  155. public function test_error_if_no_broker_is_set()
  156. {
  157. $this->app['config']->set('auth.passwords.webauthn', null);
  158. $this->json('POST', '/webauthn/lost', [
  159. 'email' => $this->user->email,
  160. ])
  161. ->assertStatus(500);
  162. }
  163. }