diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index ed62a8bf3..807f1e2f9 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -9,9 +9,9 @@ use App\Http\Controllers\Controller; use App\Http\Requests\Auth\LoginLoginRequest; use App\Models\User; use App\Services\PasswordBreachService; +use App\Support\Auth\AuthenticatesUsers; use Illuminate\Auth\AuthenticationException; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Arr; @@ -43,6 +43,14 @@ class LoginController extends Controller */ protected string $redirectTo = '/'; + /** + * Get the login username to be used (form field name; value may be email or username). + */ + public function username(): string + { + return 'username'; + } + /** * Create a new controller instance. * diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index e98794018..713e92e77 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -10,7 +10,7 @@ use App\Models\Invitation; use App\Models\Settings; use App\Models\User; use App\Rules\ValidEmailDomain; -use Illuminate\Foundation\Auth\RegistersUsers; +use App\Support\Auth\RegistersUsers; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php index 481507e16..72b911cbf 100644 --- a/app/Http/Controllers/Auth/ResetPasswordController.php +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -7,23 +7,12 @@ namespace App\Http\Controllers\Auth; use App\Http\Controllers\Controller; use App\Jobs\SendPasswordResetEmail; use App\Models\User; -use Illuminate\Foundation\Auth\ResetsPasswords; +use App\Support\Auth\RedirectsUsers; use Illuminate\Http\Request; class ResetPasswordController extends Controller { - /* - |-------------------------------------------------------------------------- - | Password Reset Controller - |-------------------------------------------------------------------------- - | - | This controller is responsible for handling password reset requests - | and uses a simple trait to include this behavior. You're free to - | explore this trait and override any methods you wish to tweak. - | - */ - - use ResetsPasswords; + use RedirectsUsers; /** * Where to redirect users after resetting their password. diff --git a/app/Support/Auth/AuthenticatesUsers.php b/app/Support/Auth/AuthenticatesUsers.php new file mode 100644 index 000000000..3b475df69 --- /dev/null +++ b/app/Support/Auth/AuthenticatesUsers.php @@ -0,0 +1,115 @@ +validate([ + $this->username() => 'required|string', + 'password' => 'required|string', + ]); + } + + /** + * Attempt to log the user into the application. + */ + protected function attemptLogin(Request $request): bool + { + return $this->guard()->attempt( + $this->credentials($request), $request->boolean('remember') + ); + } + + /** + * Get the needed authorization credentials from the request. + * + * @return array + */ + protected function credentials(Request $request): array + { + return $request->only($this->username(), 'password'); + } + + /** + * Send the response after the user was authenticated. + */ + protected function sendLoginResponse(Request $request): \Illuminate\Http\RedirectResponse|JsonResponse + { + $request->session()->regenerate(); + + $this->clearLoginAttempts($request); + + if ($response = $this->authenticated($request, $this->guard()->user())) { + return $response; + } + + return $request->wantsJson() + ? new JsonResponse([], 204) + : redirect()->intended($this->redirectPath()); + } + + /** + * The user has been authenticated. + * + * @param mixed $user + * @return \Illuminate\Http\RedirectResponse|JsonResponse|null + */ + protected function authenticated(Request $request, $user): mixed + { + return null; + } + + /** + * Get the failed login response instance. + * + * @throws ValidationException + */ + protected function sendFailedLoginResponse(Request $request): never + { + throw ValidationException::withMessages([ + $this->username() => [trans('auth.failed')], + ]); + } + + /** + * Get the login username to be used by the controller. + */ + public function username(): string + { + return 'email'; + } + + /** + * Get the guard to be used during authentication. + */ + protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard + { + return Auth::guard(); + } +} diff --git a/app/Support/Auth/RedirectsUsers.php b/app/Support/Auth/RedirectsUsers.php new file mode 100644 index 000000000..d18f06f14 --- /dev/null +++ b/app/Support/Auth/RedirectsUsers.php @@ -0,0 +1,20 @@ +redirectTo(); + } + + return isset($this->redirectTo) ? $this->redirectTo : '/home'; + } +} diff --git a/app/Support/Auth/RegistersUsers.php b/app/Support/Auth/RegistersUsers.php new file mode 100644 index 000000000..2b00c491f --- /dev/null +++ b/app/Support/Auth/RegistersUsers.php @@ -0,0 +1,64 @@ +validator($request->all())->validate(); + + event(new Registered($user = $this->create($request->all()))); + + $this->guard()->login($user); + + if ($response = $this->registered($request, $user)) { + return $response; + } + + return $request->wantsJson() + ? new JsonResponse([], 201) + : redirect($this->redirectPath()); + } + + /** + * Get the guard to be used during registration. + */ + protected function guard(): \Illuminate\Contracts\Auth\StatefulGuard + { + return Auth::guard(); + } + + /** + * The user has been registered. + * + * @param mixed $user + * @return \Illuminate\Http\RedirectResponse|JsonResponse|null + */ + protected function registered(Request $request, $user): mixed + { + return null; + } +} diff --git a/app/Support/Auth/ThrottlesLogins.php b/app/Support/Auth/ThrottlesLogins.php new file mode 100644 index 000000000..a9bf763d2 --- /dev/null +++ b/app/Support/Auth/ThrottlesLogins.php @@ -0,0 +1,102 @@ +limiter()->tooManyAttempts( + $this->throttleKey($request), $this->maxAttempts() + ); + } + + /** + * Increment the login attempts for the user. + */ + protected function incrementLoginAttempts(Request $request): void + { + $this->limiter()->hit( + $this->throttleKey($request), $this->decayMinutes() * 60 + ); + } + + /** + * Redirect the user after determining they are locked out. + * + * @throws ValidationException + */ + protected function sendLockoutResponse(Request $request): never + { + $seconds = $this->limiter()->availableIn( + $this->throttleKey($request) + ); + + throw ValidationException::withMessages([ + $this->username() => [trans('auth.throttle', [ + 'seconds' => $seconds, + 'minutes' => (int) ceil($seconds / 60), + ])], + ])->status(Response::HTTP_TOO_MANY_REQUESTS); + } + + /** + * Clear the login locks for the given user credentials. + */ + protected function clearLoginAttempts(Request $request): void + { + $this->limiter()->clear($this->throttleKey($request)); + } + + /** + * Fire an event when a lockout occurs. + */ + protected function fireLockoutEvent(Request $request): void + { + event(new Lockout($request)); + } + + /** + * Get the throttle key for the given request. + */ + protected function throttleKey(Request $request): string + { + return Str::transliterate(Str::lower($request->input($this->username()).'|'.$request->ip())); + } + + /** + * Get the rate limiter instance. + */ + protected function limiter(): RateLimiter + { + return app(RateLimiter::class); + } + + /** + * Get the maximum number of attempts to allow. + */ + public function maxAttempts(): int + { + return isset($this->maxAttempts) ? $this->maxAttempts : 5; + } + + /** + * Get the number of minutes to throttle for. + */ + public function decayMinutes(): int + { + return isset($this->decayMinutes) ? $this->decayMinutes : 1; + } +} diff --git a/composer.json b/composer.json index 542a37c41..4956db2fa 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,6 @@ "laravel/sanctum": "^4.0", "laravel/scout": "^10.13", "laravel/tinker": "^2.10.1", - "laravel/ui": "^4.6", "livewire/blaze": "^1.0", "livewire/livewire": "^3.6", "livewire/volt": "^1.6", @@ -163,8 +162,7 @@ }, "scripts": { "post-update-cmd": [ - "@php artisan ide-helper:generate --ansi", - "@php artisan vendor:publish --tag=laravel-assets --ansi --force" + "@php artisan ide-helper:generate --ansi" ], "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", diff --git a/composer.lock b/composer.lock index 24de93209..9ce16ad8a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c32c6bf6562bff67b37af654f09b476b", + "content-hash": "8de687f72473f4ec9902d492ff625378", "packages": [ { "name": "aharen/omdbapi", @@ -3315,69 +3315,6 @@ }, "time": "2026-02-06T14:12:35+00:00" }, - { - "name": "laravel/ui", - "version": "v4.6.1", - "source": { - "type": "git", - "url": "https://github.com/laravel/ui.git", - "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", - "reference": "7d6ffa38d79f19c9b3e70a751a9af845e8f41d88", - "shasum": "" - }, - "require": { - "illuminate/console": "^9.21|^10.0|^11.0|^12.0", - "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0", - "illuminate/support": "^9.21|^10.0|^11.0|^12.0", - "illuminate/validation": "^9.21|^10.0|^11.0|^12.0", - "php": "^8.0", - "symfony/console": "^6.0|^7.0" - }, - "require-dev": { - "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0", - "phpunit/phpunit": "^9.3|^10.4|^11.5" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Laravel\\Ui\\UiServiceProvider" - ] - }, - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\Ui\\": "src/", - "Illuminate\\Foundation\\Auth\\": "auth-backend/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel UI utilities and presets.", - "keywords": [ - "laravel", - "ui" - ], - "support": { - "source": "https://github.com/laravel/ui/tree/v4.6.1" - }, - "time": "2025-01-28T15:15:29+00:00" - }, { "name": "league/commonmark", "version": "2.8.0",