From 888eb12771478c874e645dd703cb60ffebc4eed2 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Tue, 16 Nov 2021 17:45:32 +0200 Subject: [PATCH] Revert "Clean auth services. (#427)" (#432) This reverts commit a0eb60366e9bb23ccd51e054074e782e7a4ae97a. --- src/Http/Controllers/AuthController.php | 42 ++++ src/Services/AuthService.php | 121 ++++++++++++ src/Services/Concerns/AuthenticatesUsers.php | 172 ++++++++++++++++ src/Services/Concerns/ResetsPasswords.php | 178 +++++++++++++++++ src/Services/Concerns/ThrottlesLogins.php | 120 ++++++++++++ src/Services/ForgotPasswordService.php | 80 ++++++++ src/Services/LoginService.php | 45 +++++ src/Services/LogoutService.php | 28 +++ src/Services/RegisterService.php | 93 +++++++++ src/Services/ResetPasswordService.php | 26 +++ src/Services/RestifyService.php | 10 + .../AuthServiceForgotPasswordTest.php | 183 ++++++++++++++++++ .../AuthServiceRegisterTest.php | 143 ++++++++++++++ .../ResetPasswordRequestTest.php | 39 ++++ .../RestifyLoginRequestTest.php | 38 ++++ .../RestifyPasswordEmailRequestTest.php | 37 ++++ 16 files changed, 1355 insertions(+) create mode 100644 src/Http/Controllers/AuthController.php create mode 100644 src/Services/AuthService.php create mode 100644 src/Services/Concerns/AuthenticatesUsers.php create mode 100644 src/Services/Concerns/ResetsPasswords.php create mode 100644 src/Services/Concerns/ThrottlesLogins.php create mode 100644 src/Services/ForgotPasswordService.php create mode 100644 src/Services/LoginService.php create mode 100644 src/Services/LogoutService.php create mode 100644 src/Services/RegisterService.php create mode 100644 src/Services/ResetPasswordService.php create mode 100644 src/Services/RestifyService.php create mode 100644 tests/Feature/Authentication/AuthServiceForgotPasswordTest.php create mode 100644 tests/Feature/Authentication/AuthServiceRegisterTest.php create mode 100644 tests/Feature/Authentication/ResetPasswordRequestTest.php create mode 100644 tests/Feature/Authentication/RestifyLoginRequestTest.php create mode 100644 tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php diff --git a/src/Http/Controllers/AuthController.php b/src/Http/Controllers/AuthController.php new file mode 100644 index 00000000..d5b0b448 --- /dev/null +++ b/src/Http/Controllers/AuthController.php @@ -0,0 +1,42 @@ +authService = $authService; + } + + public function login(Request $request) + { + return $this->authService->login($request); + } + + public function register(Request $request) + { + return $this->authService->register($request); + } + + public function verify(Request $request, $id, $hash = null) + { + return $this->authService->verify($request, $id, $hash); + } + + public function forgotPassword(Request $request) + { + return $this->authService->forgotPassword($request); + } + + public function resetPassword(Request $request) + { + return $this->authService->resetPassword($request); + } +} diff --git a/src/Services/AuthService.php b/src/Services/AuthService.php new file mode 100644 index 00000000..24871994 --- /dev/null +++ b/src/Services/AuthService.php @@ -0,0 +1,121 @@ +userQuery()->query()->findOrFail($id); + + if ($user instanceof Sanctumable && ! hash_equals((string) $hash, sha1($user->getEmailForVerification()))) { + throw new AuthorizationException('Invalid hash'); + } + + if ($user instanceof MustVerifyEmail && $user->markEmailAsVerified()) { + event(new Verified($user)); + } + + return $user; + } + + public function resetPassword(Request $request) + { + return ResetPasswordService::make($request, $this); + } + + /** + * @return PasswordBroker + */ + public function broker() + { + return Password::broker(); + } + + /** + * Returns query for User model and validate if it exists. + * + * @return Model + * @throws SanctumUserException + * @throws EntityNotFoundException + */ + public function userQuery() + { + $userClass = Config::get('auth.providers.users.model'); + + try { + $container = Container::getInstance(); + $userInstance = $container->make($userClass); + $this->validateUserModel($userInstance); + + return $userInstance; + } catch (BindingResolutionException $e) { + throw new EntityNotFoundException("The model $userClass from he follow configuration -> 'auth.providers.users.model' cannot be instantiated (may be an abstract class).", $e->getCode(), $e); + } catch (ReflectionException $e) { + throw new EntityNotFoundException("The model from the follow configuration -> 'auth.providers.users.model' doesn't exists.", $e->getCode(), $e); + } + } + + /** + * @param $userInstance + * @throws SanctumUserException + */ + public function validateUserModel($userInstance) + { + if (config('restify.auth.provider') === 'sanctum' && false === $userInstance instanceof Sanctumable) { + throw new SanctumUserException(__("User is not implementing Binaryk\LaravelRestify\Contracts\Sanctumable contract. User should use 'Laravel\Sanctum\HasApiTokens' trait to provide")); + } + } + + public function logout(Request $request) + { + return LogoutService::make($request); + } +} diff --git a/src/Services/Concerns/AuthenticatesUsers.php b/src/Services/Concerns/AuthenticatesUsers.php new file mode 100644 index 00000000..98d42249 --- /dev/null +++ b/src/Services/Concerns/AuthenticatesUsers.php @@ -0,0 +1,172 @@ +redirectTo(); + } + + return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home'; + } + + /** + * Handle a login request to the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response + */ + public function login(Request $request) + { + $this->validateLogin($request); + + // If the class is using the ThrottlesLogins trait, we can automatically throttle + // the login attempts for this application. We'll key this by the username and + // the IP address of the client making these requests into this application. + if ($this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + + return $this->sendLockoutResponse($request); + } + + if ($this->attemptLogin($request)) { + return $this->sendLoginResponse($request); + } + + // If the login attempt was unsuccessful we will increment the number of attempts + // to login and redirect the user back to the login form. Of course, when this + // user surpasses their maximum number of attempts they will get locked out. + $this->incrementLoginAttempts($request); + + return $this->sendFailedLoginResponse($request); + } + + /** + * Validate the user login request. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function validateLogin(Request $request) + { + $request->validate([ + $this->username() => 'required|string', + 'password' => 'required|string', + ]); + } + + /** + * Attempt to log the user into the application. + * + * @param \Illuminate\Http\Request $request + * @return bool + */ + protected function attemptLogin(Request $request) + { + return $this->guard()->attempt( + $this->credentials($request), + $request->has('remember') + ); + } + + /** + * Get the needed authorization credentials from the request. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function credentials(Request $request) + { + return $request->only($this->username(), 'password'); + } + + /** + * Send the response after the user was authenticated. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + protected function sendLoginResponse(Request $request) + { + $request->session()->regenerate(); + + $this->clearLoginAttempts($request); + + return $this->authenticated($request, $this->guard()->user()) + ?: redirect()->intended($this->redirectPath()); + } + + /** + * The user has been authenticated. + * + * @param \Illuminate\Http\Request $request + * @param mixed $user + * @return mixed + */ + protected function authenticated(Request $request, $user) + { + // + } + + /** + * Get the failed login response instance. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendFailedLoginResponse(Request $request) + { + throw ValidationException::withMessages([ + $this->username() => [trans('auth.failed')], + ]); + } + + /** + * Get the login username to be used by the controller. + * + * @return string + */ + public function username() + { + return 'email'; + } + + /** + * Log the user out of the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function logout(Request $request) + { + $this->guard()->logout(); + + $request->session()->invalidate(); + + return redirect('/'); + } + + /** + * Get the guard to be used during authentication. + * + * @return \Illuminate\Contracts\Auth\StatefulGuard + */ + protected function guard() + { + return Auth::guard(); + } +} diff --git a/src/Services/Concerns/ResetsPasswords.php b/src/Services/Concerns/ResetsPasswords.php new file mode 100644 index 00000000..a0ca6582 --- /dev/null +++ b/src/Services/Concerns/ResetsPasswords.php @@ -0,0 +1,178 @@ +redirectTo(); + } + + return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home'; + } + + /** + * Display the password reset view for the given token. + * + * If no token is present, display the link request form. + * + * @param \Illuminate\Http\Request $request + * @param string|null $token + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function showResetForm(Request $request, $token = null) + { + return view('auth.passwords.reset')->with( + ['token' => $token, 'email' => $request->email] + ); + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function reset(Request $request) + { + $request->validate($this->rules(), $this->validationErrorMessages()); + + // Here we will attempt to reset the user's password. If it is successful we + // will update the password on an actual user model and persist it to the + // database. Otherwise we will parse the error and return the response. + $response = $this->broker()->reset( + $this->credentials($request), + function ($user, $password) { + $this->resetPassword($user, $password); + } + ); + + // If the password was successfully reset, we will redirect the user back to + // the application's home authenticated view. If there is an error we can + // redirect them back to where they came from with their error message. + return $response == Password::PASSWORD_RESET + ? $this->sendResetResponse($response) + : $this->sendResetFailedResponse($request, $response); + } + + /** + * Get the password reset validation rules. + * + * @return array + */ + protected function rules() + { + return [ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|confirmed|min:6', + ]; + } + + /** + * Get the password reset validation error messages. + * + * @return array + */ + protected function validationErrorMessages() + { + return []; + } + + /** + * Get the password reset credentials from the request. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function credentials(Request $request) + { + return $request->only( + 'email', + 'password', + 'password_confirmation', + 'token' + ); + } + + /** + * Reset the given user's password. + * + * @param \Illuminate\Contracts\Auth\CanResetPassword $user + * @param string $password + * @return void + */ + protected function resetPassword($user, $password) + { + $user->password = Hash::make($password); + + $user->setRememberToken(Str::random(60)); + + $user->save(); + + event(new PasswordReset($user)); + + $this->guard()->login($user); + } + + /** + * Get the response for a successful password reset. + * + * @param string $response + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendResetResponse($response) + { + return response()->json([ + 'status' => trans($response), + ]); + } + + /** + * Get the response for a failed password reset. + * + * @param \Illuminate\Http\Request + * @param string $response + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendResetFailedResponse(Request $request, $response) + { + return response()->json(['email' => [ + trans($response), + ]])->setStatusCode(400); + } + + /** + * Get the broker to be used during password reset. + * + * @return \Illuminate\Contracts\Auth\PasswordBroker + */ + public function broker() + { + return Password::broker(); + } + + /** + * Get the guard to be used during password reset. + * + * @return \Illuminate\Contracts\Auth\StatefulGuard + */ + protected function guard() + { + return Auth::guard(); + } +} diff --git a/src/Services/Concerns/ThrottlesLogins.php b/src/Services/Concerns/ThrottlesLogins.php new file mode 100644 index 00000000..6ff6de98 --- /dev/null +++ b/src/Services/Concerns/ThrottlesLogins.php @@ -0,0 +1,120 @@ +limiter()->tooManyAttempts( + $this->throttleKey($request), + $this->maxAttempts(), + $this->decayMinutes() + ); + } + + /** + * Increment the login attempts for the user. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function incrementLoginAttempts(Request $request) + { + $this->limiter()->hit($this->throttleKey($request)); + } + + /** + * Redirect the user after determining they are locked out. + * + * @param \Illuminate\Http\Request $request + * @return void + * @throws \Illuminate\Validation\ValidationException + */ + protected function sendLockoutResponse(Request $request) + { + $seconds = $this->limiter()->availableIn( + $this->throttleKey($request) + ); + + throw ValidationException::withMessages([ + $this->username() => [Lang::get('auth.throttle', ['seconds' => $seconds])], + ])->status(423); + } + + /** + * Clear the login locks for the given user credentials. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function clearLoginAttempts(Request $request) + { + $this->limiter()->clear($this->throttleKey($request)); + } + + /** + * Fire an event when a lockout occurs. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function fireLockoutEvent(Request $request) + { + event(new Lockout($request)); + } + + /** + * Get the throttle key for the given request. + * + * @param \Illuminate\Http\Request $request + * @return string + */ + protected function throttleKey(Request $request) + { + return Str::lower($request->input($this->username())).'|'.$request->ip(); + } + + /** + * Get the rate limiter instance. + * + * @return \Illuminate\Cache\RateLimiter + */ + protected function limiter() + { + return app(RateLimiter::class); + } + + /** + * Get the maximum number of attempts to allow. + * + * @return int + */ + public function maxAttempts() + { + return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5; + } + + /** + * Get the number of minutes to throttle for. + * + * @return int + */ + public function decayMinutes() + { + return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1; + } +} diff --git a/src/Services/ForgotPasswordService.php b/src/Services/ForgotPasswordService.php new file mode 100644 index 00000000..c324d091 --- /dev/null +++ b/src/Services/ForgotPasswordService.php @@ -0,0 +1,80 @@ +getEmailForPasswordReset(), $withToken); + + return url($withEmail); + }); + + return resolve(static::class)->sendResetLinkEmail($request); + } + + /** + * Send a reset link to the given user. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\RedirectResponse + */ + public function sendResetLinkEmail(Request $request) + { + $request->validate(['email' => 'required|email']); + + // We will send the password reset link to this user. Once we have attempted + // to send the link, we will examine the response then see the message we + // need to show to the user. Finally, we'll send out a proper response. + $response = $this->broker()->sendResetLink( + $request->only('email') + ); + + return $response == Password::RESET_LINK_SENT + ? $this->sendResetLinkResponse($response) + : $this->sendResetLinkFailedResponse($request, $response); + } + + public function broker() + { + return Password::broker(); + } + + /** + * Get the response for a successful password reset link. + * + * @param string $response + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendResetLinkResponse($response) + { + return response()->json([ + 'status' => trans($response), + ]); + } + + /** + * Get the response for a failed password reset link. + * + * @param \Illuminate\Http\Request + * @param string $response + * @return \Illuminate\Http\RedirectResponse + */ + protected function sendResetLinkFailedResponse(Request $request, $response) + { + return response()->json([ + 'errors' => [ + 'email' => [ + trans($response), + ], + ], + ])->setStatusCode(400); + } +} diff --git a/src/Services/LoginService.php b/src/Services/LoginService.php new file mode 100644 index 00000000..5d2dd415 --- /dev/null +++ b/src/Services/LoginService.php @@ -0,0 +1,45 @@ +login($request); + } + + public function login(Request $request) + { + $this->validateLogin($request); + + // If the class is using the ThrottlesLogins trait, we can automatically throttle + // the login attempts for this application. We'll key this by the username and + // the IP address of the client making these requests into this application. + if (method_exists($this, 'hasTooManyLoginAttempts') && + $this->hasTooManyLoginAttempts($request)) { + $this->fireLockoutEvent($request); + + return $this->sendLockoutResponse($request); + } + + if ($this->attemptLogin($request)) { + event(new UserLoggedIn($this->guard()->user())); + + return $this->guard()->user()->createToken('login'); + } + + // If the login attempt was unsuccessful we will increment the number of attempts + // to login and redirect the user back to the login form. Of course, when this + // user surpasses their maximum number of attempts they will get locked out. + $this->incrementLoginAttempts($request); + + return $this->sendFailedLoginResponse($request); + } +} diff --git a/src/Services/LogoutService.php b/src/Services/LogoutService.php new file mode 100644 index 00000000..a9d68b3a --- /dev/null +++ b/src/Services/LogoutService.php @@ -0,0 +1,28 @@ +logout($request); + } + + throw new AuthenticatableUserException(__('User is not authenticated.')); + } +} diff --git a/src/Services/RegisterService.php b/src/Services/RegisterService.php new file mode 100644 index 00000000..52a657f4 --- /dev/null +++ b/src/Services/RegisterService.php @@ -0,0 +1,93 @@ +all(); + + $this->validateRegister($payload); + + $builder = $this->authService->userQuery(); + + if (false === $builder instanceof Authenticatable) { + throw AuthenticatableUserException::wrongInstance(); + } + + $userData = array_merge($payload, [ + 'password' => Hash::make(data_get($payload, 'password')), + ]); + + if (is_callable(static::$creating)) { + $user = call_user_func(static::$creating, $userData); + } else { + $user = $builder->query()->create($userData); + } + + if ($user instanceof Authenticatable) { + event(new Registered($user)); + } + + return $user; + } + + public static function make(Request $request, AuthService $authService) + { + return resolve(static::class) + ->usingAuthService($authService) + ->register($request); + } + + public function validateRegister(array $payload) + { + try { + if (class_exists(static::$registerFormRequest) && (new \ReflectionClass(static::$registerFormRequest))->isInstantiable()) { + $validator = Validator::make($payload, (new static::$registerFormRequest)->rules(), (new static::$registerFormRequest)->messages()); + if ($validator->fails()) { + throw new ValidationException($validator); + } + } + } catch (ReflectionException $e) { + $concrete = static::$registerFormRequest; + + throw new BindingResolutionException("Target class [$concrete] does not exist.", 0, $e); + } + + return true; + } + + protected function usingAuthService(AuthService $service) + { + $this->authService = $service; + + return $this; + } +} diff --git a/src/Services/ResetPasswordService.php b/src/Services/ResetPasswordService.php new file mode 100644 index 00000000..0bbf5acd --- /dev/null +++ b/src/Services/ResetPasswordService.php @@ -0,0 +1,26 @@ +reset($request); + } + + protected function usingAuthService(AuthService $authService) + { + $this->authService = $authService; + + return $this; + } +} diff --git a/src/Services/RestifyService.php b/src/Services/RestifyService.php new file mode 100644 index 00000000..c05612bd --- /dev/null +++ b/src/Services/RestifyService.php @@ -0,0 +1,10 @@ + + */ +class RestifyService +{ +} diff --git a/tests/Feature/Authentication/AuthServiceForgotPasswordTest.php b/tests/Feature/Authentication/AuthServiceForgotPasswordTest.php new file mode 100644 index 00000000..93cf51a8 --- /dev/null +++ b/tests/Feature/Authentication/AuthServiceForgotPasswordTest.php @@ -0,0 +1,183 @@ + + */ +class AuthServiceForgotPasswordTest extends IntegrationTest +{ + use MailTracking; + use InteractsWithContainer; + + /** + * @var AuthService + */ + protected $authService; + + protected function setUp(): void + { + parent::setUp(); + $this->setUpMailTracking(); + RegisterService::$registerFormRequest = null; + $this->authService = resolve(AuthService::class); + $this->app['config']->set('restify.auth.provider', 'sanctum'); + $this->app['config']->set('restify.auth.frontend_app_url', 'https://laravel-restify.dev'); + $this->app['config']->set('restify.auth.password_reset_url', 'https://laravel-restify.dev/password/reset?token={token}&email={email}'); + } + + public function test_email_was_sent_and_contain_token() + { + Notification::fake(); + + $user = $this->register(); + $request = new Request([], []); + $request->merge(['email' => $user->email]); + + $this->authService->forgotPassword($request); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) { + $this->assertNotEmpty($notification->token); + + return true; + }); + } + + public function test_email_was_sent_and_has_default_or_custom_url_callback() + { + Notification::fake(); + + $user = $this->register(); + $request = new Request([], []); + $request->merge(['email' => $user->email]); + + $this->authService->forgotPassword($request); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $this->assertEquals( + "https://laravel-restify.dev/password/reset?token={$notification->token}&email={$user->email}", + call_user_func($notification::$createUrlCallback, $user, $notification->token), + ); + + return true; + }); + + $this->authService->forgotPassword( + $request, + 'https://subdomain.domain.test/password/reset?token={token}&email={email}', + ); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $this->assertEquals( + "https://subdomain.domain.test/password/reset?token={$notification->token}&email={$user->email}", + call_user_func($notification::$createUrlCallback, $user, $notification->token), + ); + + return true; + }); + } + + public function test_reset_password_invalid_payload() + { + $this->expectException(ValidationException::class); + $request = new Request([], []); + $request->merge([ + 'email' => null, + 'password' => 'password', + 'password_confirmation' => 'password', + 'token' => 'secret', + ]); + $this->authService->resetPassword($request); + } + + public function test_reset_password_successfully() + { + Notification::fake(); + $user = $this->register(); + + $request = new Request([], []); + $request->merge(['email' => $user->email]); + $this->authService->verify($request, $user->id, sha1($user->email)); + + $this->authService->forgotPassword($request); + + Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { + $token = $notification->token; + $password = Str::random(10); + + $request = new Request([], []); + $request->merge([ + 'email' => $user->email, + 'password' => $password, + 'password_confirmation' => $password, + 'token' => $token, + ]); + + $this->authService->resetPassword($request); + + Event::assertDispatched(PasswordReset::class, function ($e) use ($user) { + $this->assertEquals($e->user->email, $user->email); + + return $e->user instanceof User; + }); + + $request = new Request([], []); + $request->merge([ + 'email' => $user->email, + 'password' => $password, + ]); + + $this->authService->login($request); + + Event::assertDispatched(UserLoggedIn::class, function ($e) use ($user) { + $this->assertEquals($e->user->email, $user->email); + + return $e->user instanceof User; + }); + + return true; + }); + } + + public function register() + { + Event::fake([ + Registered::class, + PasswordReset::class, + UserLoggedIn::class, + ]); + + $this->app->instance(User::class, new User); + + $request = new Request([], []); + + $user = [ + 'name' => 'Eduard Lupacescu', + 'email' => 'eduard.lupacescu@binarcode.com', + 'password' => 'secret!', + 'remember_token' => Str::random(10), + ]; + + $request->merge($user); + + $this->authService->register($request); + + return User::query()->get()->last(); + } +} diff --git a/tests/Feature/Authentication/AuthServiceRegisterTest.php b/tests/Feature/Authentication/AuthServiceRegisterTest.php new file mode 100644 index 00000000..3496cc28 --- /dev/null +++ b/tests/Feature/Authentication/AuthServiceRegisterTest.php @@ -0,0 +1,143 @@ + + */ +class AuthServiceRegisterTest extends IntegrationTest +{ + use InteractsWithContainer; + /** + * @var AuthService + */ + protected $authService; + + protected function setUp(): void + { + parent::setUp(); + $this->authService = resolve(AuthService::class); + } + + public function test_user_query_throw_container_does_not_have_model_reflection_exception() + { + $this->app['config']->set('auth.providers.users.model', null); + $this->expectException(EntityNotFoundException::class); + $this->authService->userQuery(); + } + + public function test_user_query_throw_container_cannot_instantiate_abstract_model() + { + $this->app['config']->set('auth.providers.users.model', LaravelRestifyModel::class); + $this->expectException(EntityNotFoundException::class); + $this->authService->userQuery(); + } + + public function test_register_successfully() + { + Event::fake([ + Registered::class, + ]); + + $this->app->instance(User::class, new User); + + $user = [ + 'name' => 'Eduard Lupacescu', + 'email' => 'eduard.lupacescu@binarcode.com', + 'password' => 'password', + 'password_confirmation' => 'password', + 'remember_token' => Str::random(10), + ]; + + $request = new Request([], []); + + $request->merge($user); + + $this->authService->register($request); + + Event::assertDispatched(Registered::class, function ($e) use ($user) { + $this->assertEquals($e->user->email, $user['email']); + + return $e->user instanceof \Binaryk\LaravelRestify\Tests\Fixtures\User\User; + }); + + $lastUser = User::query()->get()->last(); + + $this->assertEquals($lastUser->email, $user['email']); + } + + public function test_verify_user_throw_hash_not_match() + { + Event::fake([ + Registered::class, + ]); + + $this->app->instance(User::class, new User); + + $user = [ + 'name' => 'Eduard Lupacescu', + 'email' => 'eduard.lupacescu@binarcode.com', + 'password' => 'password', + 'password_confirmation' => 'password', + 'remember_token' => Str::random(10), + ]; + + $request = new Request([], []); + + $request->merge($user); + + $this->authService->register($request); + $lastUser = User::query()->get()->last(); + + $this->expectException(AuthorizationException::class); + $this->authService->verify($request, $lastUser->id, sha1('random@email.com')); + } + + public function test_verify_user_successfully() + { + Event::fake([ + Verified::class, + Registered::class, + ]); + + $this->app->instance(User::class, new User); + + $user = [ + 'name' => 'Eduard Lupacescu', + 'email' => 'eduard.lupacescu@binarcode.com', + 'password' => 'password', + 'password_confirmation' => 'password', + 'remember_token' => Str::random(10), + ]; + + $request = new Request([], []); + + $request->merge($user); + + $this->authService->register($request); + $lastUser = User::query()->get()->last(); + + $this->assertNull($lastUser->email_verified_at); + $this->authService->verify($request, $lastUser->id, sha1('eduard.lupacescu@binarcode.com')); + $lastUser->refresh(); + $this->assertNotNull($lastUser->email_verified_at); + Event::assertDispatched(Verified::class, function ($e) use ($user) { + $this->assertEquals($e->user->email, $user['email']); + + return $e->user instanceof \Binaryk\LaravelRestify\Tests\Fixtures\User\User; + }); + } +} diff --git a/tests/Feature/Authentication/ResetPasswordRequestTest.php b/tests/Feature/Authentication/ResetPasswordRequestTest.php new file mode 100644 index 00000000..97552355 --- /dev/null +++ b/tests/Feature/Authentication/ResetPasswordRequestTest.php @@ -0,0 +1,39 @@ + + */ +class ResetPasswordRequestTest extends IntegrationTest +{ + /** @var ResetPasswordRequest */ + private $subject; + + protected function setUp(): void + { + parent::setUp(); + + $this->subject = new ResetPasswordRequest; + } + + public function testRules() + { + $this->assertEquals( + [ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|confirmed|min:8', + ], + $this->subject->rules() + ); + } + + public function testAuthorize() + { + $this->assertTrue($this->subject->authorize()); + } +} diff --git a/tests/Feature/Authentication/RestifyLoginRequestTest.php b/tests/Feature/Authentication/RestifyLoginRequestTest.php new file mode 100644 index 00000000..1d609a85 --- /dev/null +++ b/tests/Feature/Authentication/RestifyLoginRequestTest.php @@ -0,0 +1,38 @@ + + */ +class RestifyLoginRequestTest extends IntegrationTest +{ + /** @var RestifyLoginRequest */ + private $subject; + + protected function setUp(): void + { + parent::setUp(); + + $this->subject = new RestifyLoginRequest; + } + + public function testRules() + { + $this->assertEquals( + [ + 'email' => 'required|email', + 'password' => 'required|min:6', + ], + $this->subject->rules() + ); + } + + public function testAuthorize() + { + $this->assertTrue($this->subject->authorize()); + } +} diff --git a/tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php b/tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php new file mode 100644 index 00000000..600767d1 --- /dev/null +++ b/tests/Feature/Authentication/RestifyPasswordEmailRequestTest.php @@ -0,0 +1,37 @@ + + */ +class RestifyPasswordEmailRequestTest extends IntegrationTest +{ + /** @var RestifyPasswordEmailRequest */ + private $subject; + + protected function setUp(): void + { + parent::setUp(); + + $this->subject = new RestifyPasswordEmailRequest; + } + + public function testRules() + { + $this->assertEquals( + [ + 'email' => 'required|email', + ], + $this->subject->rules() + ); + } + + public function testAuthorize() + { + $this->assertTrue($this->subject->authorize()); + } +}