Version 0.7.0 #1
10
app/Contracts/FormRequestDto.php
Normal file
10
app/Contracts/FormRequestDto.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Contracts;
|
||||
|
||||
use App\Dto\Request\Dto;
|
||||
|
||||
interface FormRequestDto
|
||||
{
|
||||
public function getDto(): Dto;
|
||||
}
|
27
app/Dto/Request/Authorization.php
Normal file
27
app/Dto/Request/Authorization.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request;
|
||||
|
||||
final readonly class Authorization extends Dto
|
||||
{
|
||||
public function __construct(
|
||||
private string $email,
|
||||
private string $password,
|
||||
private bool $remember = false
|
||||
) { }
|
||||
|
||||
public function getEmail(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getRemember(): bool
|
||||
{
|
||||
return $this->remember;
|
||||
}
|
||||
}
|
8
app/Dto/Request/Dto.php
Normal file
8
app/Dto/Request/Dto.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Dto\Request;
|
||||
|
||||
abstract readonly class Dto
|
||||
{
|
||||
|
||||
}
|
47
app/Http/Controllers/AuthController.php
Normal file
47
app/Http/Controllers/AuthController.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\AuthorizationRequest;
|
||||
use App\Services\AuthService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
final class AuthController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AuthService $authService
|
||||
) { }
|
||||
|
||||
public function login(): View
|
||||
{
|
||||
return view('public/login');
|
||||
}
|
||||
|
||||
public function authorization(AuthorizationRequest $request)
|
||||
{
|
||||
$authorization = $request->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'));
|
||||
}
|
||||
}
|
13
app/Http/Controllers/Controller.php
Normal file
13
app/Http/Controllers/Controller.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
|
||||
}
|
23
app/Http/Controllers/Private/ProfileController.php
Normal file
23
app/Http/Controllers/Private/ProfileController.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Private;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class ProfileController extends Controller
|
||||
{
|
||||
public function profile(): View
|
||||
{
|
||||
return view('private/profile/profile', [
|
||||
'user' => Auth::user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function settings(): View
|
||||
{
|
||||
return view('private/profile/settings', [
|
||||
'user' => Auth::user()
|
||||
]);
|
||||
}
|
||||
}
|
31
app/Http/Requests/AuthorizationRequest.php
Normal file
31
app/Http/Requests/AuthorizationRequest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Contracts\FormRequestDto;
|
||||
use App\Dto\Request\Authorization;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
final class AuthorizationRequest extends FormRequest implements FormRequestDto
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'email' => ['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)
|
||||
);
|
||||
}
|
||||
}
|
14
app/Repositories/UserRepository.php
Normal file
14
app/Repositories/UserRepository.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
final readonly class UserRepository
|
||||
{
|
||||
public function getUserByEmail(string $email): ?User
|
||||
{
|
||||
return User::query()->where('email', Str::lower($email))->first();
|
||||
}
|
||||
}
|
40
app/Services/AuthService.php
Normal file
40
app/Services/AuthService.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Dto\Request\Authorization;
|
||||
use App\Repositories\UserRepository;
|
||||
use App\ServiceResults\ServiceResultArray;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
final class AuthService extends Service
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepository $userRepository
|
||||
) { }
|
||||
|
||||
public function authorization(Authorization $authorization): ServiceResultError | ServiceResultArray
|
||||
{
|
||||
$user = $this->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'));
|
||||
}
|
||||
}
|
@ -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
|
||||
|
18
app/View/Components/Public/Layout.php
Normal file
18
app/View/Components/Public/Layout.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\View\Components\Public;
|
||||
|
||||
use Illuminate\View\Component;
|
||||
use Illuminate\View\View;
|
||||
|
||||
final class Layout extends Component
|
||||
{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('public.layout.app');
|
||||
}
|
||||
}
|
8
config/rate_limiting.php
Normal file
8
config/rate_limiting.php
Normal file
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
return [
|
||||
/**
|
||||
* Max limit of the hour.
|
||||
*/
|
||||
'login_max_request' => env('LOGIN_MAX_REQUEST', 50),
|
||||
'login_max_email_request' => env('LOGIN_MAX_EMAIL_REQUEST', 10),
|
||||
];
|
@ -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.',
|
||||
];
|
||||
|
@ -6,4 +6,6 @@ return [
|
||||
'failed' => 'Неверное имя пользователя или пароль.',
|
||||
'password' => 'Некорректный пароль.',
|
||||
'throttle' => 'Слишком много попыток входа. Пожалуйста, попробуйте ещё раз через :seconds секунд.',
|
||||
'success' => 'Пользователь вошел в систему.',
|
||||
'disabled' => 'Отключили пользователя.',
|
||||
];
|
||||
|
28
resources/views/public/layout/app.blade.php
Normal file
28
resources/views/public/layout/app.blade.php
Normal file
@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<title>@yield('meta_title', '')</title>
|
||||
<meta name="keywords" content="@yield('meta_keywords', '')" />
|
||||
<meta name="description" content="@yield('meta_description', '')" />
|
||||
|
||||
@vite('resources/volt/scss/app.scss')
|
||||
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<!-- Section -->
|
||||
<section class="vh-lg-100 mt-5 mt-lg-0 bg-soft d-flex align-items-center">
|
||||
<div class="container">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@vite('resources/volt/js/app.js')
|
||||
</body>
|
||||
</html>
|
60
resources/views/public/login.blade.php
Normal file
60
resources/views/public/login.blade.php
Normal file
@ -0,0 +1,60 @@
|
||||
@section('meta_title', __('Sign in to our platform'))
|
||||
<x-public.layout>
|
||||
<div class="row justify-content-center form-bg-image" data-background-lg="{{ Vite::asset('resources/volt/images/illustrations/signin.svg') }}">
|
||||
<div class="col-12 d-flex align-items-center justify-content-center">
|
||||
<div class="bg-white shadow border-0 rounded border-light p-4 p-lg-5 w-100 fmxw-500">
|
||||
<div class="text-center text-md-center mb-4 mt-md-0">
|
||||
<h1 class="mb-0 h3">{{ __('Sign in to our platform') }}</h1>
|
||||
</div>
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
<form action="{{ route('authorization') }}" class="mt-4" method="post">
|
||||
@csrf
|
||||
<!-- Form -->
|
||||
<div class="form-group mb-4">
|
||||
<label for="email">{{ __('Your Email') }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="basic-addon1">
|
||||
<svg class="icon icon-xs text-gray-600" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z"></path><path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"></path></svg>
|
||||
</span>
|
||||
<input type="email" name="email" class="form-control" placeholder="example@company.com" id="email" autofocus required>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of Form -->
|
||||
<div class="form-group">
|
||||
<!-- Form -->
|
||||
<div class="form-group mb-4">
|
||||
<label for="password">{{ __('Your Password') }}</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" id="basic-addon2">
|
||||
<svg class="icon icon-xs text-gray-600" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"></path></svg>
|
||||
</span>
|
||||
<input type="password" name="password" placeholder="Password" class="form-control" id="password" required>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End of Form -->
|
||||
<div class="d-flex justify-content-between align-items-top mb-4">
|
||||
<div class="form-check">
|
||||
<input name="remember" type="hidden" value="0">
|
||||
<input class="form-check-input" name="remember" type="checkbox" value="1" id="remember">
|
||||
<label class="form-check-label mb-0" for="remember">
|
||||
{{ __('Remember me') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-gray-800">{{ __('Sign in') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-public.layout>
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user