├── LICENSE.md ├── README.md ├── auth-backend ├── AuthenticatesUsers.php ├── ConfirmsPasswords.php ├── RedirectsUsers.php ├── RegistersUsers.php ├── ResetsPasswords.php ├── SendsPasswordResetEmails.php ├── ThrottlesLogins.php └── VerifiesEmails.php ├── composer.json ├── src ├── Auth │ ├── bootstrap-stubs │ │ ├── auth │ │ │ ├── login.stub │ │ │ ├── passwords │ │ │ │ ├── confirm.stub │ │ │ │ ├── email.stub │ │ │ │ └── reset.stub │ │ │ ├── register.stub │ │ │ └── verify.stub │ │ ├── home.stub │ │ └── layouts │ │ │ └── app.stub │ └── stubs │ │ ├── controllers │ │ ├── Controller.stub │ │ └── HomeController.stub │ │ └── routes.stub ├── AuthCommand.php ├── AuthRouteMethods.php ├── ControllersCommand.php ├── Presets │ ├── Bootstrap.php │ ├── Preset.php │ ├── React.php │ ├── Vue.php │ ├── bootstrap-stubs │ │ ├── _variables.scss │ │ ├── app.scss │ │ ├── bootstrap.js │ │ └── vite.config.js │ ├── react-stubs │ │ ├── Example.jsx │ │ ├── app.js │ │ └── vite.config.js │ └── vue-stubs │ │ ├── ExampleComponent.vue │ │ ├── app.js │ │ └── vite.config.js ├── UiCommand.php └── UiServiceProvider.php ├── stubs ├── Auth │ ├── ConfirmPasswordController.stub │ ├── ForgotPasswordController.stub │ ├── LoginController.stub │ ├── RegisterController.stub │ ├── ResetPasswordController.stub │ └── VerificationController.stub └── migrations │ └── 2014_10_12_100000_create_password_resets_table.php └── tests └── AuthBackend ├── AuthenticatesUsersTest.php ├── RegistersUsersTest.php └── ThrottleLoginsTest.php /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Taylor Otwell 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel UI 2 | 3 | Total Downloads 4 | Latest Stable Version 5 | License 6 | 7 | ## Introduction 8 | 9 | While Laravel does not dictate which JavaScript or CSS pre-processors you use, it does provide a basic starting point using [Bootstrap](https://getbootstrap.com/), [React](https://reactjs.org/), and / or [Vue](https://vuejs.org/) that will be helpful for many applications. By default, Laravel uses [NPM](https://www.npmjs.org/) to install both of these frontend packages. 10 | 11 | > This legacy package is a very simple authentication scaffolding built on the Bootstrap CSS framework. While it continues to work with the latest version of Laravel, you should consider using [Laravel Breeze](https://github.com/laravel/breeze) for new projects. Or, for something more robust, consider [Laravel Jetstream](https://github.com/laravel/jetstream). 12 | 13 | ## Official Documentation 14 | 15 | ### Supported Versions 16 | 17 | Only the latest major version of Laravel UI receives bug fixes. The table below lists compatible Laravel versions: 18 | 19 | | Version | Laravel Version | 20 | |---- |----| 21 | | [1.x](https://github.com/laravel/ui/tree/1.x) | 5.8, 6.x | 22 | | [2.x](https://github.com/laravel/ui/tree/2.x) | 7.x | 23 | | [3.x](https://github.com/laravel/ui/tree/3.x) | 8.x, 9.x | 24 | | [4.x](https://github.com/laravel/ui/tree/4.x) | 9.x, 10.x, 11.x, 12.x | 25 | 26 | ### Installation 27 | 28 | The Bootstrap and Vue scaffolding provided by Laravel is located in the `laravel/ui` Composer package, which may be installed using Composer: 29 | 30 | ```bash 31 | composer require laravel/ui 32 | ``` 33 | 34 | Once the `laravel/ui` package has been installed, you may install the frontend scaffolding using the `ui` Artisan command: 35 | 36 | ```bash 37 | // Generate basic scaffolding... 38 | php artisan ui bootstrap 39 | php artisan ui vue 40 | php artisan ui react 41 | 42 | // Generate login / registration scaffolding... 43 | php artisan ui bootstrap --auth 44 | php artisan ui vue --auth 45 | php artisan ui react --auth 46 | ``` 47 | 48 | #### CSS 49 | 50 | Laravel officially supports [Vite](https://laravel.com/docs/vite), a modern frontend build tool that provides an extremely fast development environment and bundles your code for production. Vite supports a variety of CSS preprocessor languages, including SASS and Less, which are extensions of plain CSS that add variables, mixins, and other powerful features that make working with CSS much more enjoyable. In this document, we will briefly discuss CSS compilation in general; however, you should consult the full [Vite documentation](https://laravel.com/docs/vite#working-with-stylesheets) for more information on compiling SASS or Less. 51 | 52 | #### JavaScript 53 | 54 | Laravel does not require you to use a specific JavaScript framework or library to build your applications. In fact, you don't have to use JavaScript at all. However, Laravel does include some basic scaffolding to make it easier to get started writing modern JavaScript using the [Vue](https://vuejs.org) library. Vue provides an expressive API for building robust JavaScript applications using components. As with CSS, we may use Vite to easily compile JavaScript components into a single, browser-ready JavaScript file. 55 | 56 | ### Writing CSS 57 | 58 | After installing the `laravel/ui` Composer package and [generating the frontend scaffolding](#introduction), Laravel's `package.json` file will include the `bootstrap` package to help you get started prototyping your application's frontend using Bootstrap. However, feel free to add or remove packages from the `package.json` file as needed for your own application. You are not required to use the Bootstrap framework to build your Laravel application - it is provided as a good starting point for those who choose to use it. 59 | 60 | Before compiling your CSS, install your project's frontend dependencies using the [Node package manager (NPM)](https://www.npmjs.org): 61 | 62 | ```bash 63 | npm install 64 | ``` 65 | 66 | Once the dependencies have been installed using `npm install`, you can compile your SASS files to plain CSS using [Vite](https://laravel.com/docs/vite#working-with-stylesheets). The `npm run dev` command will process the instructions in your `vite.config.js` file. Typically, your compiled CSS will be placed in the `public/build/assets` directory: 67 | 68 | ```bash 69 | npm run dev 70 | ``` 71 | 72 | The `vite.config.js` file included with Laravel's frontend scaffolding will compile the `resources/sass/app.scss` SASS file. This `app.scss` file imports a file of SASS variables and loads Bootstrap, which provides a good starting point for most applications. Feel free to customize the `app.scss` file however you wish or even use an entirely different pre-processor by [configuring Vite](https://laravel.com/docs/vite#working-with-stylesheets). 73 | 74 | ### Writing JavaScript 75 | 76 | All of the JavaScript dependencies required by your application can be found in the `package.json` file in the project's root directory. This file is similar to a `composer.json` file except it specifies JavaScript dependencies instead of PHP dependencies. You can install these dependencies using the [Node package manager (NPM)](https://www.npmjs.org): 77 | 78 | ```bash 79 | npm install 80 | ``` 81 | 82 | > By default, the Laravel `package.json` file includes a few packages such as `lodash` and `axios` to help you get started building your JavaScript application. Feel free to add or remove from the `package.json` file as needed for your own application. 83 | 84 | Once the packages are installed, you can use the `npm run dev` command to [compile your assets](https://laravel.com/docs/vite). Vite is a module bundler for modern JavaScript applications. When you run the `npm run dev` command, Vite will execute the instructions in your `vite.config.js` file: 85 | 86 | ```bash 87 | npm run dev 88 | ``` 89 | 90 | By default, the Laravel `vite.config.js` file compiles your SASS and the `resources/js/app.js` file. Within the `app.js` file you may register your Vue components or, if you prefer a different framework, configure your own JavaScript application. Your compiled JavaScript will typically be placed in the `public/build/assets` directory. 91 | 92 | > The `app.js` file will load the `resources/js/bootstrap.js` file which bootstraps and configures Vue, Axios, jQuery, and all other JavaScript dependencies. If you have additional JavaScript dependencies to configure, you may do so in this file. 93 | 94 | #### Writing Vue Components 95 | 96 | When using the `laravel/ui` package to scaffold your frontend, an `ExampleComponent.vue` Vue component will be placed in the `resources/js/components` directory. The `ExampleComponent.vue` file is an example of a [single file Vue component](https://vuejs.org/guide/scaling-up/sfc.html) which defines its JavaScript and HTML template in the same file. Single file components provide a very convenient approach to building JavaScript driven applications. The example component is registered in your `app.js` file: 97 | 98 | ```javascript 99 | import ExampleComponent from './components/ExampleComponent.vue'; 100 | Vue.component('example-component', ExampleComponent); 101 | ``` 102 | 103 | To use the component in your application, you may drop it into one of your HTML templates. For example, after running the `php artisan ui vue --auth` Artisan command to scaffold your application's authentication and registration screens, you could drop the component into the `home.blade.php` Blade template: 104 | 105 | ```blade 106 | @extends('layouts.app') 107 | 108 | @section('content') 109 | 110 | @endsection 111 | ``` 112 | 113 | > Remember, you should run the `npm run dev` command each time you change a Vue component. Or, you may run the `npm run watch` command to monitor and automatically recompile your components each time they are modified. 114 | 115 | If you are interested in learning more about writing Vue components, you should read the [Vue documentation](https://vuejs.org/guide/), which provides a thorough, easy-to-read overview of the entire Vue framework. 116 | 117 | #### Using React 118 | 119 | If you prefer to use React to build your JavaScript application, Laravel makes it a cinch to swap the Vue scaffolding with React scaffolding: 120 | 121 | ```bash 122 | composer require laravel/ui 123 | 124 | // Generate basic scaffolding... 125 | php artisan ui react 126 | 127 | // Generate login / registration scaffolding... 128 | php artisan ui react --auth 129 | ```` 130 | 131 | ### Adding Presets 132 | 133 | Presets are "macroable", which allows you to add additional methods to the `UiCommand` class at runtime. For example, the following code adds a `nextjs` method to the `UiCommand` class. Typically, you should declare preset macros in a [service provider](https://laravel.com/docs/providers): 134 | 135 | ```php 136 | use Laravel\Ui\UiCommand; 137 | 138 | UiCommand::macro('nextjs', function (UiCommand $command) { 139 | // Scaffold your frontend... 140 | }); 141 | ``` 142 | Then, you may call the new preset via the `ui` command: 143 | 144 | ```bash 145 | php artisan ui nextjs 146 | ``` 147 | 148 | ## Contributing 149 | 150 | Thank you for considering contributing to UI! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). 151 | 152 | ## Code of Conduct 153 | 154 | In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). 155 | 156 | ## Security Vulnerabilities 157 | 158 | Please review [our security policy](https://github.com/laravel/ui/security/policy) on how to report security vulnerabilities. 159 | 160 | ## License 161 | 162 | Laravel UI is open-sourced software licensed under the [MIT license](LICENSE.md). 163 | -------------------------------------------------------------------------------- /auth-backend/AuthenticatesUsers.php: -------------------------------------------------------------------------------- 1 | validateLogin($request); 35 | 36 | // If the class is using the ThrottlesLogins trait, we can automatically throttle 37 | // the login attempts for this application. We'll key this by the username and 38 | // the IP address of the client making these requests into this application. 39 | if (method_exists($this, 'hasTooManyLoginAttempts') && 40 | $this->hasTooManyLoginAttempts($request)) { 41 | $this->fireLockoutEvent($request); 42 | 43 | return $this->sendLockoutResponse($request); 44 | } 45 | 46 | if ($this->attemptLogin($request)) { 47 | if ($request->hasSession()) { 48 | $request->session()->put('auth.password_confirmed_at', time()); 49 | } 50 | 51 | return $this->sendLoginResponse($request); 52 | } 53 | 54 | // If the login attempt was unsuccessful we will increment the number of attempts 55 | // to login and redirect the user back to the login form. Of course, when this 56 | // user surpasses their maximum number of attempts they will get locked out. 57 | $this->incrementLoginAttempts($request); 58 | 59 | return $this->sendFailedLoginResponse($request); 60 | } 61 | 62 | /** 63 | * Validate the user login request. 64 | * 65 | * @param \Illuminate\Http\Request $request 66 | * @return void 67 | * 68 | * @throws \Illuminate\Validation\ValidationException 69 | */ 70 | protected function validateLogin(Request $request) 71 | { 72 | $request->validate([ 73 | $this->username() => 'required|string', 74 | 'password' => 'required|string', 75 | ]); 76 | } 77 | 78 | /** 79 | * Attempt to log the user into the application. 80 | * 81 | * @param \Illuminate\Http\Request $request 82 | * @return bool 83 | */ 84 | protected function attemptLogin(Request $request) 85 | { 86 | return $this->guard()->attempt( 87 | $this->credentials($request), $request->boolean('remember') 88 | ); 89 | } 90 | 91 | /** 92 | * Get the needed authorization credentials from the request. 93 | * 94 | * @param \Illuminate\Http\Request $request 95 | * @return array 96 | */ 97 | protected function credentials(Request $request) 98 | { 99 | return $request->only($this->username(), 'password'); 100 | } 101 | 102 | /** 103 | * Send the response after the user was authenticated. 104 | * 105 | * @param \Illuminate\Http\Request $request 106 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 107 | */ 108 | protected function sendLoginResponse(Request $request) 109 | { 110 | $request->session()->regenerate(); 111 | 112 | $this->clearLoginAttempts($request); 113 | 114 | if ($response = $this->authenticated($request, $this->guard()->user())) { 115 | return $response; 116 | } 117 | 118 | return $request->wantsJson() 119 | ? new JsonResponse([], 204) 120 | : redirect()->intended($this->redirectPath()); 121 | } 122 | 123 | /** 124 | * The user has been authenticated. 125 | * 126 | * @param \Illuminate\Http\Request $request 127 | * @param mixed $user 128 | * @return mixed 129 | */ 130 | protected function authenticated(Request $request, $user) 131 | { 132 | // 133 | } 134 | 135 | /** 136 | * Get the failed login response instance. 137 | * 138 | * @param \Illuminate\Http\Request $request 139 | * @return \Symfony\Component\HttpFoundation\Response 140 | * 141 | * @throws \Illuminate\Validation\ValidationException 142 | */ 143 | protected function sendFailedLoginResponse(Request $request) 144 | { 145 | throw ValidationException::withMessages([ 146 | $this->username() => [trans('auth.failed')], 147 | ]); 148 | } 149 | 150 | /** 151 | * Get the login username to be used by the controller. 152 | * 153 | * @return string 154 | */ 155 | public function username() 156 | { 157 | return 'email'; 158 | } 159 | 160 | /** 161 | * Log the user out of the application. 162 | * 163 | * @param \Illuminate\Http\Request $request 164 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 165 | */ 166 | public function logout(Request $request) 167 | { 168 | $this->guard()->logout(); 169 | 170 | $request->session()->invalidate(); 171 | 172 | $request->session()->regenerateToken(); 173 | 174 | if ($response = $this->loggedOut($request)) { 175 | return $response; 176 | } 177 | 178 | return $request->wantsJson() 179 | ? new JsonResponse([], 204) 180 | : redirect('/'); 181 | } 182 | 183 | /** 184 | * The user has logged out of the application. 185 | * 186 | * @param \Illuminate\Http\Request $request 187 | * @return mixed 188 | */ 189 | protected function loggedOut(Request $request) 190 | { 191 | // 192 | } 193 | 194 | /** 195 | * Get the guard to be used during authentication. 196 | * 197 | * @return \Illuminate\Contracts\Auth\StatefulGuard 198 | */ 199 | protected function guard() 200 | { 201 | return Auth::guard(); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /auth-backend/ConfirmsPasswords.php: -------------------------------------------------------------------------------- 1 | validate($this->rules(), $this->validationErrorMessages()); 31 | 32 | $this->resetPasswordConfirmationTimeout($request); 33 | 34 | return $request->wantsJson() 35 | ? new JsonResponse([], 204) 36 | : redirect()->intended($this->redirectPath()); 37 | } 38 | 39 | /** 40 | * Reset the password confirmation timeout. 41 | * 42 | * @param \Illuminate\Http\Request $request 43 | * @return void 44 | */ 45 | protected function resetPasswordConfirmationTimeout(Request $request) 46 | { 47 | $request->session()->put('auth.password_confirmed_at', time()); 48 | } 49 | 50 | /** 51 | * Get the password confirmation validation rules. 52 | * 53 | * @return array 54 | */ 55 | protected function rules() 56 | { 57 | return [ 58 | 'password' => 'required|current_password:web', 59 | ]; 60 | } 61 | 62 | /** 63 | * Get the password confirmation validation error messages. 64 | * 65 | * @return array 66 | */ 67 | protected function validationErrorMessages() 68 | { 69 | return []; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /auth-backend/RedirectsUsers.php: -------------------------------------------------------------------------------- 1 | redirectTo(); 16 | } 17 | 18 | return property_exists($this, 'redirectTo') ? $this->redirectTo : '/home'; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /auth-backend/RegistersUsers.php: -------------------------------------------------------------------------------- 1 | validator($request->all())->validate(); 33 | 34 | event(new Registered($user = $this->create($request->all()))); 35 | 36 | $this->guard()->login($user); 37 | 38 | if ($response = $this->registered($request, $user)) { 39 | return $response; 40 | } 41 | 42 | return $request->wantsJson() 43 | ? new JsonResponse([], 201) 44 | : redirect($this->redirectPath()); 45 | } 46 | 47 | /** 48 | * Get the guard to be used during registration. 49 | * 50 | * @return \Illuminate\Contracts\Auth\StatefulGuard 51 | */ 52 | protected function guard() 53 | { 54 | return Auth::guard(); 55 | } 56 | 57 | /** 58 | * The user has been registered. 59 | * 60 | * @param \Illuminate\Http\Request $request 61 | * @param mixed $user 62 | * @return mixed 63 | */ 64 | protected function registered(Request $request, $user) 65 | { 66 | // 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /auth-backend/ResetsPasswords.php: -------------------------------------------------------------------------------- 1 | route()->parameter('token'); 30 | 31 | return view('auth.passwords.reset')->with( 32 | ['token' => $token, 'email' => $request->email] 33 | ); 34 | } 35 | 36 | /** 37 | * Reset the given user's password. 38 | * 39 | * @param \Illuminate\Http\Request $request 40 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 41 | */ 42 | public function reset(Request $request) 43 | { 44 | $request->validate($this->rules(), $this->validationErrorMessages()); 45 | 46 | // Here we will attempt to reset the user's password. If it is successful we 47 | // will update the password on an actual user model and persist it to the 48 | // database. Otherwise we will parse the error and return the response. 49 | $response = $this->broker()->reset( 50 | $this->credentials($request), function ($user, $password) { 51 | $this->resetPassword($user, $password); 52 | } 53 | ); 54 | 55 | // If the password was successfully reset, we will redirect the user back to 56 | // the application's home authenticated view. If there is an error we can 57 | // redirect them back to where they came from with their error message. 58 | return $response == Password::PASSWORD_RESET 59 | ? $this->sendResetResponse($request, $response) 60 | : $this->sendResetFailedResponse($request, $response); 61 | } 62 | 63 | /** 64 | * Get the password reset validation rules. 65 | * 66 | * @return array 67 | */ 68 | protected function rules() 69 | { 70 | return [ 71 | 'token' => 'required', 72 | 'email' => 'required|email', 73 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 74 | ]; 75 | } 76 | 77 | /** 78 | * Get the password reset validation error messages. 79 | * 80 | * @return array 81 | */ 82 | protected function validationErrorMessages() 83 | { 84 | return []; 85 | } 86 | 87 | /** 88 | * Get the password reset credentials from the request. 89 | * 90 | * @param \Illuminate\Http\Request $request 91 | * @return array 92 | */ 93 | protected function credentials(Request $request) 94 | { 95 | return $request->only( 96 | 'email', 'password', 'password_confirmation', 'token' 97 | ); 98 | } 99 | 100 | /** 101 | * Reset the given user's password. 102 | * 103 | * @param \Illuminate\Contracts\Auth\CanResetPassword $user 104 | * @param string $password 105 | * @return void 106 | */ 107 | protected function resetPassword($user, $password) 108 | { 109 | $this->setUserPassword($user, $password); 110 | 111 | $user->setRememberToken(Str::random(60)); 112 | 113 | $user->save(); 114 | 115 | event(new PasswordReset($user)); 116 | 117 | $this->guard()->login($user); 118 | } 119 | 120 | /** 121 | * Set the user's password. 122 | * 123 | * @param \Illuminate\Contracts\Auth\CanResetPassword $user 124 | * @param string $password 125 | * @return void 126 | */ 127 | protected function setUserPassword($user, $password) 128 | { 129 | $user->password = Hash::make($password); 130 | } 131 | 132 | /** 133 | * Get the response for a successful password reset. 134 | * 135 | * @param \Illuminate\Http\Request $request 136 | * @param string $response 137 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 138 | */ 139 | protected function sendResetResponse(Request $request, $response) 140 | { 141 | if ($request->wantsJson()) { 142 | return new JsonResponse(['message' => trans($response)], 200); 143 | } 144 | 145 | return redirect($this->redirectPath()) 146 | ->with('status', trans($response)); 147 | } 148 | 149 | /** 150 | * Get the response for a failed password reset. 151 | * 152 | * @param \Illuminate\Http\Request $request 153 | * @param string $response 154 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 155 | */ 156 | protected function sendResetFailedResponse(Request $request, $response) 157 | { 158 | if ($request->wantsJson()) { 159 | throw ValidationException::withMessages([ 160 | 'email' => [trans($response)], 161 | ]); 162 | } 163 | 164 | return redirect()->back() 165 | ->withInput($request->only('email')) 166 | ->withErrors(['email' => trans($response)]); 167 | } 168 | 169 | /** 170 | * Get the broker to be used during password reset. 171 | * 172 | * @return \Illuminate\Contracts\Auth\PasswordBroker 173 | */ 174 | public function broker() 175 | { 176 | return Password::broker(); 177 | } 178 | 179 | /** 180 | * Get the guard to be used during password reset. 181 | * 182 | * @return \Illuminate\Contracts\Auth\StatefulGuard 183 | */ 184 | protected function guard() 185 | { 186 | return Auth::guard(); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /auth-backend/SendsPasswordResetEmails.php: -------------------------------------------------------------------------------- 1 | validateEmail($request); 31 | 32 | // We will send the password reset link to this user. Once we have attempted 33 | // to send the link, we will examine the response then see the message we 34 | // need to show to the user. Finally, we'll send out a proper response. 35 | $response = $this->broker()->sendResetLink( 36 | $this->credentials($request) 37 | ); 38 | 39 | return $response == Password::RESET_LINK_SENT 40 | ? $this->sendResetLinkResponse($request, $response) 41 | : $this->sendResetLinkFailedResponse($request, $response); 42 | } 43 | 44 | /** 45 | * Validate the email for the given request. 46 | * 47 | * @param \Illuminate\Http\Request $request 48 | * @return void 49 | */ 50 | protected function validateEmail(Request $request) 51 | { 52 | $request->validate(['email' => 'required|email']); 53 | } 54 | 55 | /** 56 | * Get the needed authentication credentials from the request. 57 | * 58 | * @param \Illuminate\Http\Request $request 59 | * @return array 60 | */ 61 | protected function credentials(Request $request) 62 | { 63 | return $request->only('email'); 64 | } 65 | 66 | /** 67 | * Get the response for a successful password reset link. 68 | * 69 | * @param \Illuminate\Http\Request $request 70 | * @param string $response 71 | * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse 72 | */ 73 | protected function sendResetLinkResponse(Request $request, $response) 74 | { 75 | return $request->wantsJson() 76 | ? new JsonResponse(['message' => trans($response)], 200) 77 | : back()->with('status', trans($response)); 78 | } 79 | 80 | /** 81 | * Get the response for a failed password reset link. 82 | * 83 | * @param \Illuminate\Http\Request $request 84 | * @param string $response 85 | * @return \Illuminate\Http\RedirectResponse 86 | * 87 | * @throws \Illuminate\Validation\ValidationException 88 | */ 89 | protected function sendResetLinkFailedResponse(Request $request, $response) 90 | { 91 | if ($request->wantsJson()) { 92 | throw ValidationException::withMessages([ 93 | 'email' => [trans($response)], 94 | ]); 95 | } 96 | 97 | return back() 98 | ->withInput($request->only('email')) 99 | ->withErrors(['email' => trans($response)]); 100 | } 101 | 102 | /** 103 | * Get the broker to be used during password reset. 104 | * 105 | * @return \Illuminate\Contracts\Auth\PasswordBroker 106 | */ 107 | public function broker() 108 | { 109 | return Password::broker(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /auth-backend/ThrottlesLogins.php: -------------------------------------------------------------------------------- 1 | limiter()->tooManyAttempts( 23 | $this->throttleKey($request), $this->maxAttempts() 24 | ); 25 | } 26 | 27 | /** 28 | * Increment the login attempts for the user. 29 | * 30 | * @param \Illuminate\Http\Request $request 31 | * @return void 32 | */ 33 | protected function incrementLoginAttempts(Request $request) 34 | { 35 | $this->limiter()->hit( 36 | $this->throttleKey($request), $this->decayMinutes() * 60 37 | ); 38 | } 39 | 40 | /** 41 | * Redirect the user after determining they are locked out. 42 | * 43 | * @param \Illuminate\Http\Request $request 44 | * @return \Symfony\Component\HttpFoundation\Response 45 | * 46 | * @throws \Illuminate\Validation\ValidationException 47 | */ 48 | protected function sendLockoutResponse(Request $request) 49 | { 50 | $seconds = $this->limiter()->availableIn( 51 | $this->throttleKey($request) 52 | ); 53 | 54 | throw ValidationException::withMessages([ 55 | $this->username() => [trans('auth.throttle', [ 56 | 'seconds' => $seconds, 57 | 'minutes' => ceil($seconds / 60), 58 | ])], 59 | ])->status(Response::HTTP_TOO_MANY_REQUESTS); 60 | } 61 | 62 | /** 63 | * Clear the login locks for the given user credentials. 64 | * 65 | * @param \Illuminate\Http\Request $request 66 | * @return void 67 | */ 68 | protected function clearLoginAttempts(Request $request) 69 | { 70 | $this->limiter()->clear($this->throttleKey($request)); 71 | } 72 | 73 | /** 74 | * Fire an event when a lockout occurs. 75 | * 76 | * @param \Illuminate\Http\Request $request 77 | * @return void 78 | */ 79 | protected function fireLockoutEvent(Request $request) 80 | { 81 | event(new Lockout($request)); 82 | } 83 | 84 | /** 85 | * Get the throttle key for the given request. 86 | * 87 | * @param \Illuminate\Http\Request $request 88 | * @return string 89 | */ 90 | protected function throttleKey(Request $request) 91 | { 92 | return Str::transliterate(Str::lower($request->input($this->username())).'|'.$request->ip()); 93 | } 94 | 95 | /** 96 | * Get the rate limiter instance. 97 | * 98 | * @return \Illuminate\Cache\RateLimiter 99 | */ 100 | protected function limiter() 101 | { 102 | return app(RateLimiter::class); 103 | } 104 | 105 | /** 106 | * Get the maximum number of attempts to allow. 107 | * 108 | * @return int 109 | */ 110 | public function maxAttempts() 111 | { 112 | return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5; 113 | } 114 | 115 | /** 116 | * Get the number of minutes to throttle for. 117 | * 118 | * @return int 119 | */ 120 | public function decayMinutes() 121 | { 122 | return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /auth-backend/VerifiesEmails.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail() 23 | ? redirect($this->redirectPath()) 24 | : view('auth.verify'); 25 | } 26 | 27 | /** 28 | * Mark the authenticated user's email address as verified. 29 | * 30 | * @param \Illuminate\Http\Request $request 31 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse 32 | * 33 | * @throws \Illuminate\Auth\Access\AuthorizationException 34 | */ 35 | public function verify(Request $request) 36 | { 37 | if (! hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) { 38 | throw new AuthorizationException; 39 | } 40 | 41 | if (! hash_equals((string) $request->route('hash'), sha1($request->user()->getEmailForVerification()))) { 42 | throw new AuthorizationException; 43 | } 44 | 45 | if ($request->user()->hasVerifiedEmail()) { 46 | return $request->wantsJson() 47 | ? new JsonResponse([], 204) 48 | : redirect($this->redirectPath()); 49 | } 50 | 51 | if ($request->user()->markEmailAsVerified()) { 52 | event(new Verified($request->user())); 53 | } 54 | 55 | if ($response = $this->verified($request)) { 56 | return $response; 57 | } 58 | 59 | return $request->wantsJson() 60 | ? new JsonResponse([], 204) 61 | : redirect($this->redirectPath())->with('verified', true); 62 | } 63 | 64 | /** 65 | * The user has been verified. 66 | * 67 | * @param \Illuminate\Http\Request $request 68 | * @return mixed 69 | */ 70 | protected function verified(Request $request) 71 | { 72 | // 73 | } 74 | 75 | /** 76 | * Resend the email verification notification. 77 | * 78 | * @param \Illuminate\Http\Request $request 79 | * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse 80 | */ 81 | public function resend(Request $request) 82 | { 83 | if ($request->user()->hasVerifiedEmail()) { 84 | return $request->wantsJson() 85 | ? new JsonResponse([], 204) 86 | : redirect($this->redirectPath()); 87 | } 88 | 89 | $request->user()->sendEmailVerificationNotification(); 90 | 91 | return $request->wantsJson() 92 | ? new JsonResponse([], 202) 93 | : back()->with('resent', true); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/ui", 3 | "description": "Laravel UI utilities and presets.", 4 | "keywords": ["laravel", "ui"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Taylor Otwell", 9 | "email": "taylor@laravel.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.0", 14 | "illuminate/console": "^9.21|^10.0|^11.0|^12.0", 15 | "illuminate/filesystem": "^9.21|^10.0|^11.0|^12.0", 16 | "illuminate/support": "^9.21|^10.0|^11.0|^12.0", 17 | "illuminate/validation": "^9.21|^10.0|^11.0|^12.0", 18 | "symfony/console": "^6.0|^7.0" 19 | }, 20 | "require-dev": { 21 | "orchestra/testbench": "^7.35|^8.15|^9.0|^10.0", 22 | "phpunit/phpunit": "^9.3|^10.4|^11.5" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Laravel\\Ui\\": "src/", 27 | "Illuminate\\Foundation\\Auth\\": "auth-backend/" 28 | } 29 | }, 30 | "config": { 31 | "sort-packages": true 32 | }, 33 | "extra": { 34 | "branch-alias": { 35 | "dev-master": "4.x-dev" 36 | }, 37 | "laravel": { 38 | "providers": [ 39 | "Laravel\\Ui\\UiServiceProvider" 40 | ] 41 | } 42 | }, 43 | "minimum-stability": "dev", 44 | "prefer-stable": true 45 | } 46 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/auth/login.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Login') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @error('email') 21 | 22 | {{ $message }} 23 | 24 | @enderror 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | @error('password') 35 | 36 | {{ $message }} 37 | 38 | @enderror 39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 | 50 |
51 |
52 |
53 | 54 |
55 |
56 | 59 | 60 | @if (Route::has('password.request')) 61 | 62 | {{ __('Forgot Your Password?') }} 63 | 64 | @endif 65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | @endsection 74 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/auth/passwords/confirm.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Confirm Password') }}
9 | 10 |
11 | {{ __('Please confirm your password before continuing.') }} 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @error('password') 23 | 24 | {{ $message }} 25 | 26 | @enderror 27 |
28 |
29 | 30 |
31 |
32 | 35 | 36 | @if (Route::has('password.request')) 37 | 38 | {{ __('Forgot Your Password?') }} 39 | 40 | @endif 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | @endsection 50 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/auth/passwords/email.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Reset Password') }}
9 | 10 |
11 | @if (session('status')) 12 | 15 | @endif 16 | 17 |
18 | @csrf 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @error('email') 27 | 28 | {{ $message }} 29 | 30 | @enderror 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/auth/passwords/reset.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Reset Password') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @error('email') 23 | 24 | {{ $message }} 25 | 26 | @enderror 27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 | 35 | 36 | @error('password') 37 | 38 | {{ $message }} 39 | 40 | @enderror 41 |
42 |
43 | 44 |
45 | 46 | 47 |
48 | 49 |
50 |
51 | 52 |
53 |
54 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | @endsection 66 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/auth/register.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Register') }}
9 | 10 |
11 |
12 | @csrf 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 | @error('name') 21 | 22 | {{ $message }} 23 | 24 | @enderror 25 |
26 |
27 | 28 |
29 | 30 | 31 |
32 | 33 | 34 | @error('email') 35 | 36 | {{ $message }} 37 | 38 | @enderror 39 |
40 |
41 | 42 |
43 | 44 | 45 |
46 | 47 | 48 | @error('password') 49 | 50 | {{ $message }} 51 | 52 | @enderror 53 |
54 |
55 | 56 |
57 | 58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 |
66 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | @endsection 78 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/auth/verify.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Verify Your Email Address') }}
9 | 10 |
11 | @if (session('resent')) 12 | 15 | @endif 16 | 17 | {{ __('Before proceeding, please check your email for a verification link.') }} 18 | {{ __('If you did not receive the email') }}, 19 |
20 | @csrf 21 | . 22 |
23 |
24 |
25 |
26 |
27 |
28 | @endsection 29 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/home.stub: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Dashboard') }}
9 | 10 |
11 | @if (session('status')) 12 | 15 | @endif 16 | 17 | {{ __('You are logged in!') }} 18 |
19 |
20 |
21 |
22 |
23 | @endsection 24 | -------------------------------------------------------------------------------- /src/Auth/bootstrap-stubs/layouts/app.stub: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ config('app.name', 'Laravel') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | @vite(['resources/sass/app.scss', 'resources/js/app.js']) 18 | 19 | 20 |
21 | 74 | 75 |
76 | @yield('content') 77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /src/Auth/stubs/controllers/Controller.stub: -------------------------------------------------------------------------------- 1 | middleware('auth'); 17 | } 18 | 19 | /** 20 | * Show the application dashboard. 21 | * 22 | * @return \Illuminate\Contracts\Support\Renderable 23 | */ 24 | public function index() 25 | { 26 | return view('home'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Auth/stubs/routes.stub: -------------------------------------------------------------------------------- 1 | 2 | Auth::routes(); 3 | 4 | Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); 5 | -------------------------------------------------------------------------------- /src/AuthCommand.php: -------------------------------------------------------------------------------- 1 | 'auth/login.blade.php', 36 | 'auth/passwords/confirm.stub' => 'auth/passwords/confirm.blade.php', 37 | 'auth/passwords/email.stub' => 'auth/passwords/email.blade.php', 38 | 'auth/passwords/reset.stub' => 'auth/passwords/reset.blade.php', 39 | 'auth/register.stub' => 'auth/register.blade.php', 40 | 'auth/verify.stub' => 'auth/verify.blade.php', 41 | 'home.stub' => 'home.blade.php', 42 | 'layouts/app.stub' => 'layouts/app.blade.php', 43 | ]; 44 | 45 | /** 46 | * Execute the console command. 47 | * 48 | * @return void 49 | * 50 | * @throws \InvalidArgumentException 51 | */ 52 | public function handle() 53 | { 54 | if (static::hasMacro($this->argument('type'))) { 55 | return call_user_func(static::$macros[$this->argument('type')], $this); 56 | } 57 | 58 | if (! in_array($this->argument('type'), ['bootstrap'])) { 59 | throw new InvalidArgumentException('Invalid preset.'); 60 | } 61 | 62 | $this->ensureDirectoriesExist(); 63 | $this->exportViews(); 64 | 65 | if (! $this->option('views')) { 66 | $this->exportBackend(); 67 | } 68 | 69 | $this->components->info('Authentication scaffolding generated successfully.'); 70 | } 71 | 72 | /** 73 | * Create the directories for the files. 74 | * 75 | * @return void 76 | */ 77 | protected function ensureDirectoriesExist() 78 | { 79 | if (! is_dir($directory = $this->getViewPath('layouts'))) { 80 | mkdir($directory, 0755, true); 81 | } 82 | 83 | if (! is_dir($directory = $this->getViewPath('auth/passwords'))) { 84 | mkdir($directory, 0755, true); 85 | } 86 | } 87 | 88 | /** 89 | * Export the authentication views. 90 | * 91 | * @return void 92 | */ 93 | protected function exportViews() 94 | { 95 | foreach ($this->views as $key => $value) { 96 | if (file_exists($view = $this->getViewPath($value)) && ! $this->option('force')) { 97 | if (! $this->components->confirm("The [$value] view already exists. Do you want to replace it?")) { 98 | continue; 99 | } 100 | } 101 | 102 | copy( 103 | __DIR__.'/Auth/'.$this->argument('type').'-stubs/'.$key, 104 | $view 105 | ); 106 | } 107 | } 108 | 109 | /** 110 | * Export the authentication backend. 111 | * 112 | * @return void 113 | */ 114 | protected function exportBackend() 115 | { 116 | $this->callSilent('ui:controllers'); 117 | 118 | $controller = app_path('Http/Controllers/HomeController.php'); 119 | 120 | if (file_exists($controller) && ! $this->option('force')) { 121 | if ($this->components->confirm("The [HomeController.php] file already exists. Do you want to replace it?", true)) { 122 | file_put_contents($controller, $this->compileStub('controllers/HomeController')); 123 | } 124 | } else { 125 | file_put_contents($controller, $this->compileStub('controllers/HomeController')); 126 | } 127 | 128 | $baseController = app_path('Http/Controllers/Controller.php'); 129 | 130 | if (file_exists($baseController) && ! $this->option('force')) { 131 | if ($this->components->confirm("The [Controller.php] file already exists. Do you want to replace it?", true)) { 132 | file_put_contents($baseController, $this->compileStub('controllers/Controller')); 133 | } 134 | } else { 135 | file_put_contents($baseController, $this->compileStub('controllers/Controller')); 136 | } 137 | 138 | if (! file_exists(database_path('migrations/0001_01_01_000000_create_users_table.php'))) { 139 | copy( 140 | __DIR__.'/../stubs/migrations/2014_10_12_100000_create_password_resets_table.php', 141 | base_path('database/migrations/2014_10_12_100000_create_password_resets_table.php') 142 | ); 143 | } 144 | 145 | file_put_contents( 146 | base_path('routes/web.php'), 147 | file_get_contents(__DIR__.'/Auth/stubs/routes.stub'), 148 | FILE_APPEND 149 | ); 150 | } 151 | 152 | /** 153 | * Compiles the given stub. 154 | * 155 | * @param string $stub 156 | * @return string 157 | */ 158 | protected function compileStub($stub) 159 | { 160 | return str_replace( 161 | '{{namespace}}', 162 | $this->laravel->getNamespace(), 163 | file_get_contents(__DIR__.'/Auth/stubs/'.$stub.'.stub') 164 | ); 165 | } 166 | 167 | /** 168 | * Get full view path relative to the application's configured view path. 169 | * 170 | * @param string $path 171 | * @return string 172 | */ 173 | protected function getViewPath($path) 174 | { 175 | return implode(DIRECTORY_SEPARATOR, [ 176 | config('view.paths')[0] ?? resource_path('views'), $path, 177 | ]); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/AuthRouteMethods.php: -------------------------------------------------------------------------------- 1 | prependGroupNamespace('Auth\LoginController')) ? null : 'App\Http\Controllers'; 17 | 18 | $this->group(['namespace' => $namespace], function() use($options) { 19 | // Login Routes... 20 | if ($options['login'] ?? true) { 21 | $this->get('login', 'Auth\LoginController@showLoginForm')->name('login'); 22 | $this->post('login', 'Auth\LoginController@login'); 23 | } 24 | 25 | // Logout Routes... 26 | if ($options['logout'] ?? true) { 27 | $this->post('logout', 'Auth\LoginController@logout')->name('logout'); 28 | } 29 | 30 | // Registration Routes... 31 | if ($options['register'] ?? true) { 32 | $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register'); 33 | $this->post('register', 'Auth\RegisterController@register'); 34 | } 35 | 36 | // Password Reset Routes... 37 | if ($options['reset'] ?? true) { 38 | $this->resetPassword(); 39 | } 40 | 41 | // Password Confirmation Routes... 42 | if ($options['confirm'] ?? 43 | class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) { 44 | $this->confirmPassword(); 45 | } 46 | 47 | // Email Verification Routes... 48 | if ($options['verify'] ?? false) { 49 | $this->emailVerification(); 50 | } 51 | }); 52 | }; 53 | } 54 | 55 | /** 56 | * Register the typical reset password routes for an application. 57 | * 58 | * @return callable 59 | */ 60 | public function resetPassword() 61 | { 62 | return function () { 63 | $this->get('password/reset', 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request'); 64 | $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email'); 65 | $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm')->name('password.reset'); 66 | $this->post('password/reset', 'Auth\ResetPasswordController@reset')->name('password.update'); 67 | }; 68 | } 69 | 70 | /** 71 | * Register the typical confirm password routes for an application. 72 | * 73 | * @return callable 74 | */ 75 | public function confirmPassword() 76 | { 77 | return function () { 78 | $this->get('password/confirm', 'Auth\ConfirmPasswordController@showConfirmForm')->name('password.confirm'); 79 | $this->post('password/confirm', 'Auth\ConfirmPasswordController@confirm'); 80 | }; 81 | } 82 | 83 | /** 84 | * Register the typical email verification routes for an application. 85 | * 86 | * @return callable 87 | */ 88 | public function emailVerification() 89 | { 90 | return function () { 91 | $this->get('email/verify', 'Auth\VerificationController@show')->name('verification.notice'); 92 | $this->get('email/verify/{id}/{hash}', 'Auth\VerificationController@verify')->name('verification.verify'); 93 | $this->post('email/resend', 'Auth\VerificationController@resend')->name('verification.resend'); 94 | }; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/ControllersCommand.php: -------------------------------------------------------------------------------- 1 | allFiles(__DIR__.'/../stubs/Auth')) 42 | ->each(function (SplFileInfo $file) use ($filesystem) { 43 | $filesystem->copy( 44 | $file->getPathname(), 45 | app_path('Http/Controllers/Auth/'.Str::replaceLast('.stub', '.php', $file->getFilename())) 46 | ); 47 | }); 48 | 49 | $this->components->info('Authentication scaffolding generated successfully.'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Presets/Bootstrap.php: -------------------------------------------------------------------------------- 1 | '^5.2.3', 33 | '@popperjs/core' => '^2.11.6', 34 | 'sass' => '^1.56.1', 35 | ] + $packages; 36 | } 37 | 38 | /** 39 | * Update the Vite configuration. 40 | * 41 | * @return void 42 | */ 43 | protected static function updateViteConfiguration() 44 | { 45 | copy(__DIR__.'/bootstrap-stubs/vite.config.js', base_path('vite.config.js')); 46 | } 47 | 48 | /** 49 | * Update the Sass files for the application. 50 | * 51 | * @return void 52 | */ 53 | protected static function updateSass() 54 | { 55 | (new Filesystem)->ensureDirectoryExists(resource_path('sass')); 56 | 57 | copy(__DIR__.'/bootstrap-stubs/_variables.scss', resource_path('sass/_variables.scss')); 58 | copy(__DIR__.'/bootstrap-stubs/app.scss', resource_path('sass/app.scss')); 59 | } 60 | 61 | /** 62 | * Update the bootstrapping files. 63 | * 64 | * @return void 65 | */ 66 | protected static function updateBootstrapping() 67 | { 68 | copy(__DIR__.'/bootstrap-stubs/bootstrap.js', resource_path('js/bootstrap.js')); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Presets/Preset.php: -------------------------------------------------------------------------------- 1 | isDirectory($directory = resource_path('js/components'))) { 19 | $filesystem->makeDirectory($directory, 0755, true); 20 | } 21 | } 22 | 23 | /** 24 | * Update the "package.json" file. 25 | * 26 | * @param bool $dev 27 | * @return void 28 | */ 29 | protected static function updatePackages($dev = true) 30 | { 31 | if (! file_exists(base_path('package.json'))) { 32 | return; 33 | } 34 | 35 | $configurationKey = $dev ? 'devDependencies' : 'dependencies'; 36 | 37 | $packages = json_decode(file_get_contents(base_path('package.json')), true); 38 | 39 | $packages[$configurationKey] = static::updatePackageArray( 40 | array_key_exists($configurationKey, $packages) ? $packages[$configurationKey] : [], 41 | $configurationKey 42 | ); 43 | 44 | ksort($packages[$configurationKey]); 45 | 46 | file_put_contents( 47 | base_path('package.json'), 48 | json_encode($packages, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL 49 | ); 50 | } 51 | 52 | /** 53 | * Remove the installed Node modules. 54 | * 55 | * @return void 56 | */ 57 | protected static function removeNodeModules() 58 | { 59 | tap(new Filesystem, function ($files) { 60 | $files->deleteDirectory(base_path('node_modules')); 61 | 62 | $files->delete(base_path('yarn.lock')); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Presets/React.php: -------------------------------------------------------------------------------- 1 | '^4.2.0', 36 | 'react' => '^18.2.0', 37 | 'react-dom' => '^18.2.0', 38 | ] + Arr::except($packages, [ 39 | '@vitejs/plugin-vue', 40 | 'vue' 41 | ]); 42 | } 43 | 44 | /** 45 | * Update the Vite configuration. 46 | * 47 | * @return void 48 | */ 49 | protected static function updateViteConfiguration() 50 | { 51 | copy(__DIR__.'/react-stubs/vite.config.js', base_path('vite.config.js')); 52 | } 53 | 54 | /** 55 | * Update the example component. 56 | * 57 | * @return void 58 | */ 59 | protected static function updateComponent() 60 | { 61 | (new Filesystem)->delete( 62 | resource_path('js/components/ExampleComponent.vue') 63 | ); 64 | 65 | copy( 66 | __DIR__.'/react-stubs/Example.jsx', 67 | resource_path('js/components/Example.jsx') 68 | ); 69 | } 70 | 71 | /** 72 | * Update the bootstrapping files. 73 | * 74 | * @return void 75 | */ 76 | protected static function updateBootstrapping() 77 | { 78 | copy(__DIR__.'/react-stubs/app.js', resource_path('js/app.js')); 79 | } 80 | 81 | /** 82 | * Add Vite's React Refresh Runtime 83 | * 84 | * @return void 85 | */ 86 | protected static function addViteReactRefreshDirective() 87 | { 88 | $view = static::getViewPath('layouts/app.blade.php'); 89 | 90 | if (! file_exists($view)) { 91 | return; 92 | } 93 | 94 | file_put_contents( 95 | $view, 96 | str_replace('@vite(', '@viteReactRefresh'.PHP_EOL.' @vite(', file_get_contents($view)) 97 | ); 98 | } 99 | 100 | /** 101 | * Get full view path relative to the application's configured view path. 102 | * 103 | * @param string $path 104 | * @return string 105 | */ 106 | protected static function getViewPath($path) 107 | { 108 | return implode(DIRECTORY_SEPARATOR, [ 109 | config('view.paths')[0] ?? resource_path('views'), $path, 110 | ]); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Presets/Vue.php: -------------------------------------------------------------------------------- 1 | '^5.2.1', 35 | 'vue' => '^3.5.13', 36 | ] + Arr::except($packages, [ 37 | '@vitejs/plugin-react', 38 | 'react', 39 | 'react-dom', 40 | ]); 41 | } 42 | 43 | /** 44 | * Update the Vite configuration. 45 | * 46 | * @return void 47 | */ 48 | protected static function updateViteConfiguration() 49 | { 50 | copy(__DIR__.'/vue-stubs/vite.config.js', base_path('vite.config.js')); 51 | } 52 | 53 | /** 54 | * Update the example component. 55 | * 56 | * @return void 57 | */ 58 | protected static function updateComponent() 59 | { 60 | (new Filesystem)->delete( 61 | resource_path('js/components/Example.js') 62 | ); 63 | 64 | copy( 65 | __DIR__.'/vue-stubs/ExampleComponent.vue', 66 | resource_path('js/components/ExampleComponent.vue') 67 | ); 68 | } 69 | 70 | /** 71 | * Update the bootstrapping files. 72 | * 73 | * @return void 74 | */ 75 | protected static function updateBootstrapping() 76 | { 77 | copy(__DIR__.'/vue-stubs/app.js', resource_path('js/app.js')); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Presets/bootstrap-stubs/_variables.scss: -------------------------------------------------------------------------------- 1 | // Body 2 | $body-bg: #f8fafc; 3 | 4 | // Typography 5 | $font-family-sans-serif: 'Nunito', sans-serif; 6 | $font-size-base: 0.9rem; 7 | $line-height-base: 1.6; 8 | -------------------------------------------------------------------------------- /src/Presets/bootstrap-stubs/app.scss: -------------------------------------------------------------------------------- 1 | // Fonts 2 | @import url('https://fonts.bunny.net/css?family=Nunito'); 3 | 4 | // Variables 5 | @import 'variables'; 6 | 7 | // Bootstrap 8 | @import 'bootstrap/scss/bootstrap'; 9 | -------------------------------------------------------------------------------- /src/Presets/bootstrap-stubs/bootstrap.js: -------------------------------------------------------------------------------- 1 | import 'bootstrap'; 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | import axios from 'axios'; 10 | window.axios = axios; 11 | 12 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 13 | 14 | /** 15 | * Echo exposes an expressive API for subscribing to channels and listening 16 | * for events that are broadcast by Laravel. Echo and event broadcasting 17 | * allows your team to easily build robust real-time web applications. 18 | */ 19 | 20 | // import Echo from 'laravel-echo'; 21 | 22 | // import Pusher from 'pusher-js'; 23 | // window.Pusher = Pusher; 24 | 25 | // window.Echo = new Echo({ 26 | // broadcaster: 'pusher', 27 | // key: import.meta.env.VITE_PUSHER_APP_KEY, 28 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', 29 | // wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, 30 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, 31 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, 32 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', 33 | // enabledTransports: ['ws', 'wss'], 34 | // }); 35 | -------------------------------------------------------------------------------- /src/Presets/bootstrap-stubs/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | laravel({ 7 | input: [ 8 | 'resources/sass/app.scss', 9 | 'resources/js/app.js', 10 | ], 11 | refresh: true, 12 | }), 13 | ], 14 | }); 15 | -------------------------------------------------------------------------------- /src/Presets/react-stubs/Example.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | 4 | function Example() { 5 | return ( 6 |
7 |
8 |
9 |
10 |
Example Component
11 | 12 |
I'm an example component!
13 |
14 |
15 |
16 |
17 | ); 18 | } 19 | 20 | export default Example; 21 | 22 | if (document.getElementById('example')) { 23 | const Index = ReactDOM.createRoot(document.getElementById("example")); 24 | 25 | Index.render( 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/Presets/react-stubs/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * First we will load all of this project's JavaScript dependencies which 3 | * includes React and other helpers. It's a great starting point while 4 | * building robust, powerful web applications using React + Laravel. 5 | */ 6 | 7 | import './bootstrap'; 8 | 9 | /** 10 | * Next, we will create a fresh React component instance and attach it to 11 | * the page. Then, you may begin adding components to this application 12 | * or customize the JavaScript scaffolding to fit your unique needs. 13 | */ 14 | 15 | import './components/Example'; 16 | -------------------------------------------------------------------------------- /src/Presets/react-stubs/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | import react from '@vitejs/plugin-react'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: [ 9 | 'resources/sass/app.scss', 10 | 'resources/js/app.js', 11 | ], 12 | refresh: true, 13 | }), 14 | react(), 15 | ], 16 | }); 17 | -------------------------------------------------------------------------------- /src/Presets/vue-stubs/ExampleComponent.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /src/Presets/vue-stubs/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * First we will load all of this project's JavaScript dependencies which 3 | * includes Vue and other libraries. It is a great starting point when 4 | * building robust, powerful web applications using Vue and Laravel. 5 | */ 6 | 7 | import './bootstrap'; 8 | import { createApp } from 'vue'; 9 | 10 | /** 11 | * Next, we will create a fresh Vue application instance. You may then begin 12 | * registering components with the application instance so they are ready 13 | * to use in your application's views. An example is included for you. 14 | */ 15 | 16 | const app = createApp({}); 17 | 18 | import ExampleComponent from './components/ExampleComponent.vue'; 19 | app.component('example-component', ExampleComponent); 20 | 21 | /** 22 | * The following block of code may be used to automatically register your 23 | * Vue components. It will recursively scan this directory for the Vue 24 | * components and automatically register them with their "basename". 25 | * 26 | * Eg. ./components/ExampleComponent.vue -> 27 | */ 28 | 29 | // Object.entries(import.meta.glob('./**/*.vue', { eager: true })).forEach(([path, definition]) => { 30 | // app.component(path.split('/').pop().replace(/\.\w+$/, ''), definition.default); 31 | // }); 32 | 33 | /** 34 | * Finally, we will attach the application instance to a HTML element with 35 | * an "id" attribute of "app". This element is included with the "auth" 36 | * scaffolding. Otherwise, you will need to add an element yourself. 37 | */ 38 | 39 | app.mount('#app'); 40 | -------------------------------------------------------------------------------- /src/Presets/vue-stubs/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | import vue from '@vitejs/plugin-vue'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: [ 9 | 'resources/sass/app.scss', 10 | 'resources/js/app.js', 11 | ], 12 | refresh: true, 13 | }), 14 | vue({ 15 | template: { 16 | transformAssetUrls: { 17 | base: null, 18 | includeAbsolute: false, 19 | }, 20 | }, 21 | }), 22 | ], 23 | resolve: { 24 | alias: { 25 | vue: 'vue/dist/vue.esm-bundler.js', 26 | }, 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /src/UiCommand.php: -------------------------------------------------------------------------------- 1 | argument('type'))) { 39 | return call_user_func(static::$macros[$this->argument('type')], $this); 40 | } 41 | 42 | if (! in_array($this->argument('type'), ['bootstrap', 'vue', 'react'])) { 43 | throw new InvalidArgumentException('Invalid preset.'); 44 | } 45 | 46 | if ($this->option('auth')) { 47 | $this->call('ui:auth'); 48 | } 49 | 50 | $this->{$this->argument('type')}(); 51 | } 52 | 53 | /** 54 | * Install the "bootstrap" preset. 55 | * 56 | * @return void 57 | */ 58 | protected function bootstrap() 59 | { 60 | Presets\Bootstrap::install(); 61 | 62 | $this->components->info('Bootstrap scaffolding installed successfully.'); 63 | $this->components->warn('Please run [npm install && npm run dev] to compile your fresh scaffolding.'); 64 | } 65 | 66 | /** 67 | * Install the "vue" preset. 68 | * 69 | * @return void 70 | */ 71 | protected function vue() 72 | { 73 | Presets\Bootstrap::install(); 74 | Presets\Vue::install(); 75 | 76 | $this->components->info('Vue scaffolding installed successfully.'); 77 | $this->components->warn('Please run [npm install && npm run dev] to compile your fresh scaffolding.'); 78 | } 79 | 80 | /** 81 | * Install the "react" preset. 82 | * 83 | * @return void 84 | */ 85 | protected function react() 86 | { 87 | Presets\Bootstrap::install(); 88 | Presets\React::install(); 89 | 90 | $this->components->info('React scaffolding installed successfully.'); 91 | $this->components->warn('Please run [npm install && npm run dev] to compile your fresh scaffolding.'); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/UiServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 18 | $this->commands([ 19 | AuthCommand::class, 20 | ControllersCommand::class, 21 | UiCommand::class, 22 | ]); 23 | } 24 | } 25 | 26 | /** 27 | * Bootstrap any application services. 28 | * 29 | * @return void 30 | */ 31 | public function boot() 32 | { 33 | Route::mixin(new AuthRouteMethods); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stubs/Auth/ConfirmPasswordController.stub: -------------------------------------------------------------------------------- 1 | middleware('auth'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /stubs/Auth/ForgotPasswordController.stub: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 38 | $this->middleware('auth')->only('logout'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /stubs/Auth/RegisterController.stub: -------------------------------------------------------------------------------- 1 | middleware('guest'); 41 | } 42 | 43 | /** 44 | * Get a validator for an incoming registration request. 45 | * 46 | * @param array $data 47 | * @return \Illuminate\Contracts\Validation\Validator 48 | */ 49 | protected function validator(array $data) 50 | { 51 | return Validator::make($data, [ 52 | 'name' => ['required', 'string', 'max:255'], 53 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 54 | 'password' => ['required', 'string', 'min:8', 'confirmed'], 55 | ]); 56 | } 57 | 58 | /** 59 | * Create a new user instance after a valid registration. 60 | * 61 | * @param array $data 62 | * @return \App\Models\User 63 | */ 64 | protected function create(array $data) 65 | { 66 | return User::create([ 67 | 'name' => $data['name'], 68 | 'email' => $data['email'], 69 | 'password' => Hash::make($data['password']), 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /stubs/Auth/ResetPasswordController.stub: -------------------------------------------------------------------------------- 1 | middleware('auth'); 38 | $this->middleware('signed')->only('verify'); 39 | $this->middleware('throttle:6,1')->only('verify', 'resend'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /stubs/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /tests/AuthBackend/AuthenticatesUsersTest.php: -------------------------------------------------------------------------------- 1 | create(); 38 | 39 | $request = Request::create('/login', 'POST', [ 40 | 'email' => $user->email, 41 | 'password' => 'password', 42 | ], [], [], [ 43 | 'HTTP_ACCEPT' => 'application/json', 44 | ]); 45 | 46 | $response = $this->handleRequestUsing($request, function ($request) { 47 | return $this->login($request); 48 | })->assertStatus(204); 49 | 50 | Event::assertDispatched(function (Attempting $event) { 51 | return $event->remember === false; 52 | }); 53 | } 54 | 55 | #[Test] 56 | public function it_can_deauthenticate_a_user() 57 | { 58 | Event::fake(); 59 | 60 | $user = UserFactory::new()->create(); 61 | 62 | $this->actingAs($user); 63 | 64 | $request = Request::create('/logout', 'POST', [], [], [], [ 65 | 'HTTP_ACCEPT' => 'application/json', 66 | ]); 67 | 68 | $response = $this->handleRequestUsing( 69 | $request, fn ($request) => $this->logout($request) 70 | )->assertStatus(204); 71 | 72 | Event::assertDispatched(fn (Logout $event) => $user->is($event->user)); 73 | } 74 | 75 | #[Test] 76 | public function it_can_authenticate_a_user_with_remember_as_false() 77 | { 78 | Event::fake(); 79 | 80 | $user = UserFactory::new()->create(); 81 | 82 | $request = Request::create('/login', 'POST', [ 83 | 'email' => $user->email, 84 | 'password' => 'password', 85 | 'remember' => false, 86 | ], [], [], [ 87 | 'HTTP_ACCEPT' => 'application/json', 88 | ]); 89 | 90 | $response = $this->handleRequestUsing($request, function ($request) { 91 | return $this->login($request); 92 | })->assertStatus(204); 93 | 94 | Event::assertDispatched(function (Attempting $event) { 95 | return $event->remember === false; 96 | }); 97 | } 98 | 99 | #[Test] 100 | public function it_can_authenticate_a_user_with_remember_as_true() 101 | { 102 | Event::fake(); 103 | 104 | $user = UserFactory::new()->create(); 105 | 106 | $request = Request::create('/login', 'POST', [ 107 | 'email' => $user->email, 108 | 'password' => 'password', 109 | 'remember' => true, 110 | ], [], [], [ 111 | 'HTTP_ACCEPT' => 'application/json', 112 | ]); 113 | 114 | $response = $this->handleRequestUsing($request, function ($request) { 115 | return $this->login($request); 116 | })->assertStatus(204); 117 | 118 | Event::assertDispatched(function (Attempting $event) { 119 | return $event->remember === true; 120 | }); 121 | } 122 | 123 | #[Test] 124 | public function it_cant_authenticate_a_user_with_invalid_password() 125 | { 126 | $user = UserFactory::new()->create(); 127 | 128 | $request = Request::create('/login', 'POST', [ 129 | 'email' => $user->email, 130 | 'password' => 'invalid-password', 131 | ], [], [], [ 132 | 'HTTP_ACCEPT' => 'application/json', 133 | ]); 134 | 135 | $response = $this->handleRequestUsing($request, function ($request) { 136 | return $this->login($request); 137 | })->assertUnprocessable(); 138 | 139 | $this->assertInstanceOf(ValidationException::class, $response->exception); 140 | $this->assertSame([ 141 | 'email' => [ 142 | 'These credentials do not match our records.', 143 | ], 144 | ], $response->exception->errors()); 145 | } 146 | 147 | #[Test] 148 | public function it_cant_authenticate_unknown_credential() 149 | { 150 | $request = Request::create('/login', 'POST', [ 151 | 'email' => 'taylor@laravel.com', 152 | 'password' => 'password', 153 | ], [], [], [ 154 | 'HTTP_ACCEPT' => 'application/json', 155 | ]); 156 | 157 | $response = $this->handleRequestUsing($request, function ($request) { 158 | return $this->login($request); 159 | })->assertUnprocessable(); 160 | 161 | $this->assertInstanceOf(ValidationException::class, $response->exception); 162 | $this->assertSame([ 163 | 'email' => [ 164 | 'These credentials do not match our records.', 165 | ], 166 | ], $response->exception->errors()); 167 | } 168 | 169 | /** 170 | * Handle Request using the following pipeline. 171 | * 172 | * @param \Illuminate\Http\Request $request 173 | * @param callable $callback 174 | * @return \Illuminate\Testing\TestResponse 175 | */ 176 | protected function handleRequestUsing(Request $request, callable $callback) 177 | { 178 | return new TestResponse( 179 | (new Pipeline($this->app)) 180 | ->send($request) 181 | ->through([ 182 | \Illuminate\Session\Middleware\StartSession::class, 183 | ]) 184 | ->then($callback) 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /tests/AuthBackend/RegistersUsersTest.php: -------------------------------------------------------------------------------- 1 | 'Taylor Otwell', 30 | 'email' => 'taylor@laravel.com', 31 | 'password' => 'secret-password', 32 | 'password_confirmation' => 'secret-password', 33 | ], [], [], [ 34 | 'HTTP_ACCEPT' => 'application/json', 35 | ]); 36 | 37 | $response = $this->handleRequestUsing($request, function ($request) { 38 | return $this->register($request); 39 | })->assertCreated(); 40 | 41 | $this->assertDatabaseHas('users', [ 42 | 'name' => 'Taylor Otwell', 43 | 'email' => 'taylor@laravel.com', 44 | ]); 45 | } 46 | 47 | /** 48 | * Get a validator for an incoming registration request. 49 | * 50 | * @param array $data 51 | * @return \Illuminate\Contracts\Validation\Validator 52 | */ 53 | protected function validator(array $data) 54 | { 55 | return Validator::make($data, [ 56 | 'name' => ['required', 'string', 'max:255'], 57 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 58 | 'password' => ['required', 'string', 'min:8', 'confirmed'], 59 | ]); 60 | } 61 | 62 | /** 63 | * Create a new user instance after a valid registration. 64 | * 65 | * @param array $data 66 | * @return \App\Models\User 67 | */ 68 | protected function create(array $data) 69 | { 70 | $user = (new User())->forceFill([ 71 | 'name' => $data['name'], 72 | 'email' => $data['email'], 73 | 'password' => Hash::make($data['password']), 74 | ]); 75 | 76 | $user->save(); 77 | 78 | return $user; 79 | } 80 | 81 | /** 82 | * Handle Request using the following pipeline. 83 | * 84 | * @param \Illuminate\Http\Request $request 85 | * @param callable $callback 86 | * @return \Illuminate\Testing\TestResponse 87 | */ 88 | protected function handleRequestUsing(Request $request, callable $callback) 89 | { 90 | return new TestResponse( 91 | (new Pipeline($this->app)) 92 | ->send($request) 93 | ->through([ 94 | \Illuminate\Session\Middleware\StartSession::class, 95 | ]) 96 | ->then($callback) 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/AuthBackend/ThrottleLoginsTest.php: -------------------------------------------------------------------------------- 1 | createMock(ThrottlesLogins::class); 18 | $throttle->method('username')->willReturn('email'); 19 | $reflection = new \ReflectionClass($throttle); 20 | $method = $reflection->getMethod('throttleKey'); 21 | $method->setAccessible(true); 22 | 23 | $request = $this->mock(Request::class); 24 | $request->expects('input')->with('email')->andReturn($email); 25 | $request->expects('ip')->andReturn('192.168.0.1'); 26 | 27 | $this->assertSame($expectedEmail . '|192.168.0.1', $method->invoke($throttle, $request)); 28 | } 29 | 30 | public static function emailProvider(): array 31 | { 32 | return [ 33 | 'lowercase special characters' => ['ⓣⓔⓢⓣ@ⓛⓐⓡⓐⓥⓔⓛ.ⓒⓞⓜ', 'test@laravel.com'], 34 | 'uppercase special characters' => ['ⓉⒺⓈⓉ@ⓁⒶⓇⒶⓋⒺⓁ.ⒸⓄⓂ', 'test@laravel.com'], 35 | 'special character numbers' => ['test⑩⓸③@laravel.com', 'test1043@laravel.com'], 36 | 'default email' => ['test@laravel.com', 'test@laravel.com'], 37 | ]; 38 | } 39 | } 40 | 41 | class ThrottlesLogins 42 | { 43 | use ThrottlesLoginsTrait; 44 | 45 | public function username() 46 | { 47 | return 'email'; 48 | } 49 | } 50 | --------------------------------------------------------------------------------