LoginTest.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. namespace Tests\Feature\Http\Auth;
  3. use App\Http\Controllers\Auth\LoginController;
  4. use App\Http\Middleware\RejectIfAuthenticated;
  5. use App\Http\Middleware\RejectIfDemoMode;
  6. use App\Http\Middleware\RejectIfReverseProxy;
  7. use App\Http\Middleware\SkipIfAuthenticated;
  8. use App\Models\User;
  9. use Illuminate\Support\Carbon;
  10. use Illuminate\Support\Facades\Config;
  11. use PHPUnit\Framework\Attributes\CoversClass;
  12. use Tests\FeatureTestCase;
  13. /**
  14. * LoginTest test class
  15. */
  16. #[CoversClass(LoginController::class)]
  17. #[CoversClass(RejectIfAuthenticated::class)]
  18. #[CoversClass(RejectIfReverseProxy::class)]
  19. #[CoversClass(RejectIfDemoMode::class)]
  20. #[CoversClass(SkipIfAuthenticated::class)]
  21. class LoginTest extends FeatureTestCase
  22. {
  23. /**
  24. * @var \App\Models\User|\Illuminate\Contracts\Auth\Authenticatable
  25. */
  26. protected $user;
  27. private const PASSWORD = 'password';
  28. private const WRONG_PASSWORD = 'wrong_password';
  29. /**
  30. * @test
  31. */
  32. public function setUp() : void
  33. {
  34. parent::setUp();
  35. $this->user = User::factory()->create();
  36. }
  37. /**
  38. * @test
  39. */
  40. public function test_user_login_returns_success()
  41. {
  42. $response = $this->json('POST', '/user/login', [
  43. 'email' => $this->user->email,
  44. 'password' => self::PASSWORD,
  45. ])
  46. ->assertOk()
  47. ->assertJsonFragment([
  48. 'message' => 'authenticated',
  49. 'name' => $this->user->name,
  50. ])
  51. ->assertJsonStructure([
  52. 'message',
  53. 'name',
  54. 'preferences',
  55. ]);
  56. }
  57. /**
  58. * @test
  59. *
  60. * @covers \App\Rules\CaseInsensitiveEmailExists
  61. */
  62. public function test_user_login_with_uppercased_email_returns_success()
  63. {
  64. $response = $this->json('POST', '/user/login', [
  65. 'email' => strtoupper($this->user->email),
  66. 'password' => self::PASSWORD,
  67. ])
  68. ->assertOk()
  69. ->assertJsonFragment([
  70. 'message' => 'authenticated',
  71. 'name' => $this->user->name,
  72. ])
  73. ->assertJsonStructure([
  74. 'message',
  75. 'name',
  76. 'preferences',
  77. ]);
  78. }
  79. /**
  80. * @test
  81. *
  82. * @covers \App\Http\Middleware\SkipIfAuthenticated
  83. */
  84. public function test_user_login_already_authenticated_is_rejected()
  85. {
  86. $response = $this->json('POST', '/user/login', [
  87. 'email' => $this->user->email,
  88. 'password' => self::PASSWORD,
  89. ]);
  90. $response = $this->actingAs($this->user, 'web-guard')
  91. ->json('POST', '/user/login', [
  92. 'email' => $this->user->email,
  93. 'password' => self::PASSWORD,
  94. ])
  95. ->assertStatus(400)
  96. ->assertJsonStructure([
  97. 'message',
  98. ]);
  99. }
  100. /**
  101. * @test
  102. */
  103. public function test_user_login_with_missing_data_returns_validation_error()
  104. {
  105. $response = $this->json('POST', '/user/login', [
  106. 'email' => '',
  107. 'password' => '',
  108. ])
  109. ->assertStatus(422)
  110. ->assertJsonValidationErrors([
  111. 'email',
  112. 'password',
  113. ]);
  114. }
  115. /**
  116. * @test
  117. *
  118. * @covers \App\Exceptions\Handler
  119. */
  120. public function test_user_login_with_invalid_credentials_returns_unauthorized()
  121. {
  122. $response = $this->json('POST', '/user/login', [
  123. 'email' => $this->user->email,
  124. 'password' => self::WRONG_PASSWORD,
  125. ])
  126. ->assertStatus(401)
  127. ->assertJson([
  128. 'message' => 'unauthorized',
  129. ]);
  130. }
  131. /**
  132. * @test
  133. */
  134. public function test_too_many_login_attempts_with_invalid_credentials_returns_too_many_request_error()
  135. {
  136. $throttle = 8;
  137. Config::set('auth.throttle.login', $throttle);
  138. $post = [
  139. 'email' => $this->user->email,
  140. 'password' => self::WRONG_PASSWORD,
  141. ];
  142. for ($i = 0; $i < $throttle - 1; $i++) {
  143. $this->json('POST', '/user/login', $post);
  144. }
  145. $this->json('POST', '/user/login', $post)
  146. ->assertUnauthorized();
  147. $this->json('POST', '/user/login', $post)
  148. ->assertStatus(429);
  149. }
  150. /**
  151. * @test
  152. */
  153. public function test_user_logout_returns_validation_success()
  154. {
  155. $response = $this->json('POST', '/user/login', [
  156. 'email' => $this->user->email,
  157. 'password' => self::PASSWORD,
  158. ]);
  159. $response = $this->actingAs($this->user, 'web-guard')
  160. ->json('GET', '/user/logout')
  161. ->assertOk()
  162. ->assertExactJson([
  163. 'message' => 'signed out',
  164. ]);
  165. }
  166. /**
  167. * @test
  168. *
  169. * @covers \App\Http\Middleware\KickOutInactiveUser
  170. * @covers \App\Http\Middleware\LogUserLastSeen
  171. */
  172. public function test_user_logout_after_inactivity_returns_teapot()
  173. {
  174. // Set the autolock period to 1 minute
  175. $this->user['preferences->kickUserAfter'] = 1;
  176. $this->user->save();
  177. $response = $this->json('POST', '/user/login', [
  178. 'email' => $this->user->email,
  179. 'password' => self::PASSWORD,
  180. ]);
  181. // Ping a protected endpoint to log last_seen_at time
  182. $response = $this->actingAs($this->user, 'api-guard')
  183. ->json('GET', '/api/v1/twofaccounts');
  184. $this->travelTo(Carbon::now()->addMinutes(2));
  185. $response = $this->actingAs($this->user, 'api-guard')
  186. ->json('GET', '/api/v1/twofaccounts')
  187. ->assertStatus(418);
  188. }
  189. }