├── 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 |
4 |
5 |
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 |
73 | @endsection
74 |
--------------------------------------------------------------------------------
/src/Auth/bootstrap-stubs/auth/passwords/confirm.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ __('Please confirm your password before continuing.') }}
12 |
13 |
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 |
9 |
10 |
11 | @if (session('status'))
12 |
13 | {{ session('status') }}
14 |
15 | @endif
16 |
17 |
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 |
65 | @endsection
66 |
--------------------------------------------------------------------------------
/src/Auth/bootstrap-stubs/auth/register.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
77 | @endsection
78 |
--------------------------------------------------------------------------------
/src/Auth/bootstrap-stubs/auth/verify.stub:
--------------------------------------------------------------------------------
1 | @extends('layouts.app')
2 |
3 | @section('content')
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | @if (session('resent'))
12 |
13 | {{ __('A fresh verification link has been sent to your email address.') }}
14 |
15 | @endif
16 |
17 | {{ __('Before proceeding, please check your email for a verification link.') }}
18 | {{ __('If you did not receive the email') }},
19 |
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 |
9 |
10 |
11 | @if (session('status'))
12 |
13 | {{ session('status') }}
14 |
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 |
22 |
73 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | I'm an example component.
10 |
11 |
12 |
13 |
14 |
15 |
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 |
--------------------------------------------------------------------------------