diff --git a/app/Contracts/FormRequestDto.php b/app/Contracts/FormRequestDto.php new file mode 100644 index 0000000..3b0d99e --- /dev/null +++ b/app/Contracts/FormRequestDto.php @@ -0,0 +1,10 @@ +email; + } + + public function getPassword(): string + { + return $this->password; + } + + public function getRemember(): bool + { + return $this->remember; + } +} diff --git a/app/Dto/Request/Dto.php b/app/Dto/Request/Dto.php new file mode 100644 index 0000000..944c7dc --- /dev/null +++ b/app/Dto/Request/Dto.php @@ -0,0 +1,8 @@ +getDto(); + $result = $this->authService->authorization($authorization); + if (!$result->isSuccess()) { + if ($result->getCode() === Response::HTTP_UNAUTHORIZED) { + Log::warning('Unauthorized ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']'); + } + return redirect()->route('login')->withInput()->withErrors($result->getMessage()); + } + $request->session()->regenerate(); + Log::notice('Logged in ' . $authorization->getEmail() . ' [' . $request->getClientIp() . ']'); + return redirect()->route('home'); + } + + public function logout(Request $request): RedirectResponse + { + Auth::logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); + return redirect(route('login')); + } +} diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php new file mode 100644 index 0000000..44fe5b4 --- /dev/null +++ b/app/Http/Controllers/Controller.php @@ -0,0 +1,13 @@ + Auth::user() + ]); + } + + public function settings(): View + { + return view('private/profile/settings', [ + 'user' => Auth::user() + ]); + } +} diff --git a/app/Http/Requests/AuthorizationRequest.php b/app/Http/Requests/AuthorizationRequest.php new file mode 100644 index 0000000..f4ed7af --- /dev/null +++ b/app/Http/Requests/AuthorizationRequest.php @@ -0,0 +1,31 @@ + ['required', 'email', 'max:255'], + 'password' => ['required', 'min:3'], + 'remember' => ['nullable', 'boolean'], + ]; + } + + public function getDto(): Authorization + { + return new Authorization( + email: $this->input('email'), + password: $this->input('password'), + remember: (bool) $this->input('remember', false) + ); + } +} diff --git a/app/Repositories/UserRepository.php b/app/Repositories/UserRepository.php new file mode 100644 index 0000000..c8022e9 --- /dev/null +++ b/app/Repositories/UserRepository.php @@ -0,0 +1,14 @@ +where('email', Str::lower($email))->first(); + } +} diff --git a/app/Services/AuthService.php b/app/Services/AuthService.php new file mode 100644 index 0000000..03dc196 --- /dev/null +++ b/app/Services/AuthService.php @@ -0,0 +1,40 @@ +userRepository->getUserByEmail($authorization->getEmail()); + if (is_null($user)) { + return $this->errUnauthorized(__('auth.failed')); + } + if (Hash::check($authorization->getPassword(), $user->password) !== true) { + return $this->errUnauthorized(__('auth.password')); + } + if ($user->is_active === false) { + return $this->errFobidden(__('auth.disabled')); + } + + try { + Auth::login($user, $authorization->getRemember()); + } catch (\Throwable $e) { + report($e); + return $this->errService(__('Server Error')); + } + + return $this->ok(__('auth.success')); + } +} diff --git a/app/Services/Service.php b/app/Services/Service.php index 10fb348..d844c35 100644 --- a/app/Services/Service.php +++ b/app/Services/Service.php @@ -5,32 +5,38 @@ namespace App\Services; use App\ServiceResults\ServiceResultArray; use App\ServiceResults\ServiceResultError; +use Illuminate\Http\Response; abstract class Service { final protected function errValidate(string $message, array $errors = []): ServiceResultError { - return $this->error(422, $message, $errors); + return $this->error(Response::HTTP_UNPROCESSABLE_ENTITY, $message, $errors); } final protected function errFobidden(string $message): ServiceResultError { - return $this->error(403, $message); + return $this->error(Response::HTTP_FORBIDDEN, $message); } final protected function errNotFound(string $message): ServiceResultError { - return $this->error(404, $message); + return $this->error(Response::HTTP_NOT_FOUND, $message); } final protected function errService(string $message): ServiceResultError { - return $this->error(500, $message); + return $this->error(Response::HTTP_INTERNAL_SERVER_ERROR, $message); } final protected function notAcceptable(string $message): ServiceResultError { - return $this->error(406, $message); + return $this->error(Response::HTTP_NOT_ACCEPTABLE, $message); + } + + final protected function errUnauthorized(string $message): ServiceResultError + { + return $this->error(Response::HTTP_UNAUTHORIZED, $message); } final protected function ok(string $message = 'OK'): ServiceResultArray diff --git a/app/View/Components/Public/Layout.php b/app/View/Components/Public/Layout.php new file mode 100644 index 0000000..be8689e --- /dev/null +++ b/app/View/Components/Public/Layout.php @@ -0,0 +1,18 @@ + env('LOGIN_MAX_REQUEST', 50), + 'login_max_email_request' => env('LOGIN_MAX_EMAIL_REQUEST', 10), +]; diff --git a/lang/en/auth.php b/lang/en/auth.php index 6db4982..cbe6067 100644 --- a/lang/en/auth.php +++ b/lang/en/auth.php @@ -6,4 +6,6 @@ return [ 'failed' => 'These credentials do not match our records.', 'password' => 'The password is incorrect.', 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'success' => 'The user is logged in.', + 'disabled' => 'Disabled the user.', ]; diff --git a/lang/ru/auth.php b/lang/ru/auth.php index 38ca958..0f6d38e 100644 --- a/lang/ru/auth.php +++ b/lang/ru/auth.php @@ -6,4 +6,6 @@ return [ 'failed' => 'Неверное имя пользователя или пароль.', 'password' => 'Некорректный пароль.', 'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте ещё раз через :seconds секунд.', + 'success' => 'Пользователь вошел в систему.', + 'disabled' => 'Отключили пользователя.', ]; diff --git a/resources/views/public/layout/app.blade.php b/resources/views/public/layout/app.blade.php new file mode 100644 index 0000000..352a8c6 --- /dev/null +++ b/resources/views/public/layout/app.blade.php @@ -0,0 +1,28 @@ + + + + + + + @yield('meta_title', '') + + + + @vite('resources/volt/scss/app.scss') + + + + + +
+ +
+
+ {{ $slot }} +
+
+
+ + @vite('resources/volt/js/app.js') + + diff --git a/resources/views/public/login.blade.php b/resources/views/public/login.blade.php new file mode 100644 index 0000000..f83ca1b --- /dev/null +++ b/resources/views/public/login.blade.php @@ -0,0 +1,60 @@ +@section('meta_title', __('Sign in to our platform')) + +
+
+
+
+

{{ __('Sign in to our platform') }}

+
+ @if ($errors->any()) +
+
    + @foreach ($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif +
+ @csrf + +
+ +
+ + + + +
+
+ +
+ +
+ +
+ + + + +
+
+ +
+
+ + + +
+
+
+
+ +
+
+
+
+
+
diff --git a/routes/web.php b/routes/web.php index 83469ef..daac5b0 100644 --- a/routes/web.php +++ b/routes/web.php @@ -12,3 +12,17 @@ use Illuminate\Support\Facades\Route; | be assigned to the "web" middleware group. Make something great! | */ + +Route::middleware('guest')->group(function () { + Route::get('login', [\App\Http\Controllers\AuthController::class, 'login'])->name('login'); + Route::middleware(['throttle:login'])->post('login', [\App\Http\Controllers\AuthController::class, 'authorization'])->name('authorization'); +}); +Route::middleware(['auth', 'verified'])->group(function () { + Route::post('logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout'); + Route::get('/', [\App\Http\Controllers\Private\DashboardController::class, 'index'])->name('home'); + Route::prefix('profile')->as('profile.') + ->group(function () { + Route::get('/', [\App\Http\Controllers\Private\ProfileController::class, 'profile'])->name('edit'); + Route::get('settings', [\App\Http\Controllers\Private\ProfileController::class, 'settings'])->name('settings'); + }); +});