Output:
Wall time: 4.4244 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Passwords Routing Resetting the Password Handling the Form Submission\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n#### Handling the Form Submission\n\nOf course, we need to define a route to actually handle the password reset form submission. This route will be responsible for validating the incoming request and updating the user's password in the database:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Auth\\Events\\PasswordReset;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Support\\Facades\\Password;\nuse Illuminate\\Support\\Str;\n\nRoute::post('/reset-password', function (Request $request) {\n    $request->validate([\n        'token' => 'required',\n        'email' => 'required|email',\n        'password' => 'required|min:8|confirmed',\n    ]);\n\n    $status = Password::reset(\n        $request->only('email', 'password', 'password_confirmation', 'token'),\n        function (User $user, string $password) {\n            $user->forceFill([\n                'password' => Hash::make($password)\n            ])->setRememberToken(Str::random(60));\n\n            $user->save();\n\n            event(new PasswordReset($user));\n        }\n    );\n\n    return $status === Password::PasswordReset\n        ? redirect()->route('login')->with('status', __($status))\n        : back()->withErrors(['email' => [__($status)]]);\n})->middleware('guest')->name('password.update');\n```\n\nBefore moving on, let's examine this route in more detail. First, the request's `token`, `email`, and `password` attributes are validated. Next, we will use Laravel's built-in \"password broker\" (via the `Password` facade) to validate the password reset request credentials.\n\nIf the token, email address, and password given to the password broker are valid, the closure passed to the `reset` method will be invoked. Within this closure, which receives the user instance and the plain-text password provided to the password reset form, we may update the user's password in the database.\n\nThe `reset` method returns a \"status\" slug. This status may be translated using Laravel's [localization](/docs/{{version}}/localization) helpers in order to display a user-friendly message to the user regarding the status of their request. The translation of the password reset status is determined by your application's `lang/{lang}/passwords.php` language file. An entry for each possible value of the status slug is located within the `passwords` language file. If your application does not contain a `lang` directory, you may create it using the `lang:publish` Artisan command.\n\nBefore moving on, you may be wondering how Laravel knows how to retrieve the user record from your application's database when calling the `Password` facade's `reset` method. The Laravel password broker utilizes your authentication system's \"user providers\" to retrieve database records. The user provider used by the password broker is configured within the `passwords` configuration array of your `config/auth.php` configuration file. To learn more about writing custom user providers, consult the [authentication documentation](/docs/{{version}}/authentication#adding-custom-user-providers).\n\n---\n\n## Passwords Routing Requesting the Password Reset Link\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n### Requesting the Password Reset Link\n\n---\n\n## Fortify Password Reset Resetting the Password\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n### Resetting the Password\n\nTo finish implementing our application's password reset functionality, we need to instruct Fortify how to return our \"reset password\" view.\n\nAll of Fortify's view rendering logic may be customized using the appropriate methods available via the `Laravel\\Fortify\\Fortify` class. Typically, you should call this method from the `boot` method of your application's `App\\Providers\\FortifyServiceProvider` class:\n\n```php\nuse Laravel\\Fortify\\Fortify;\nuse Illuminate\\Http\\Request;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Fortify::resetPasswordView(function (Request $request) {\n        return view('auth.reset-password', ['request' => $request]);\n    });\n\n    // ...\n}\n```\n\nFortify will take care of defining the route to display this view. Your `reset-password` template should include a form that makes a POST request to `/reset-password`.\n\nThe `/reset-password` endpoint expects a string `email` field, a `password` field, a `password_confirmation` field, and a hidden field named `token` that contains the value of `request()->route('token')`. The name of the \"email\" field / database column should match the `email` configuration value defined within your application's `fortify` configuration file.\n\n---\n\n## Fortify Password Confirmation\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n## Password Confirmation\n\nWhile building your application, you may occasionally have actions that should require the user to confirm their password before the action is performed. Typically, these routes are protected by Laravel's built-in `password.confirm` middleware.\n\nTo begin implementing password confirmation functionality, we need to instruct Fortify how to return our application's \"password confirmation\" view. Remember, Fortify is a headless authentication library. If you would like a frontend implementation of Laravel's authentication features that are already completed for you, you should use an [application starter kit](/docs/{{version}}/starter-kits).\n\nAll of Fortify's view rendering logic may be customized using the appropriate methods available via the `Laravel\\Fortify\\Fortify` class. Typically, you should call this method from the `boot` method of your application's `App\\Providers\\FortifyServiceProvider` class:\n\n```php\nuse Laravel\\Fortify\\Fortify;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Fortify::confirmPasswordView(function () {\n        return view('auth.confirm-password');\n    });\n\n    // ...\n}\n```\n\nFortify will take care of defining the `/user/confirm-password` endpoint that returns this view. Your `confirm-password` template should include a form that makes a POST request to the `/user/confirm-password` endpoint. The `/user/confirm-password` endpoint expects a `password` field that contains the user's current password.\n\nIf the password matches the user's current password, Fortify will redirect the user to the route they were attempting to access. If the request was an XHR request, a 201 HTTP response will be returned.\n\nIf the request was not successful, the user will be redirected back to the confirm password screen and the validation errors will be available to you via the shared `$errors` Blade template variable. Or, in the case of an XHR request, the validation errors will be returned with a 422 HTTP response.\n\n---\n\n## Fortify Password Reset Customizing Password Resets\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n### Customizing Password Resets\n\nThe password reset process may be customized by modifying the `App\\Actions\\ResetUserPassword` action that was generated when you installed Laravel Fortify.\n\n---\n\n## Fortify Installation Disabling Views Disabling Views and Password Reset\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n#### Disabling Views and Password Reset\n\nIf you choose to disable Fortify's views and you will be implementing password reset features for your application, you should still define a route named `password.reset` that is responsible for displaying your application's \"reset password\" view. This is necessary because Laravel's `Illuminate\\Auth\\Notifications\\ResetPassword` notification will generate the password reset URL via the `password.reset` named route.\n\n---\n\n## Passwords Routing Resetting the Password\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n### Resetting the Password\n\n---\n\n## Passwords Routing Resetting the Password The Password Reset Form\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n#### The Password Reset Form\n\nNext, we will define the routes necessary to actually reset the password once the user clicks on the password reset link that has been emailed to them and provides a new password. First, let's define the route that will display the reset password form that is displayed when the user clicks the reset password link. This route will receive a `token` parameter that we will use later to verify the password reset request:\n\n```php\nRoute::get('/reset-password/{token}', function (string $token) {\n    return view('auth.reset-password', ['token' => $token]);\n})->middleware('guest')->name('password.reset');\n```\n\nThe view that is returned by this route should display a form containing an `email` field, a `password` field, a `password_confirmation` field, and a hidden `token` field, which should contain the value of the secret `$token` received by our route.\n\n---\n\n## Fortify Password Reset Resetting the Password\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n### Resetting the Password\n\nTo finish implementing our application's password reset functionality, we need to instruct Fortify how to return our \"reset password\" view.\n\nAll of Fortify's view rendering logic may be customized using the appropriate methods available via the `Laravel\\Fortify\\Fortify` class. Typically, you should call this method from the `boot` method of your application's `App\\Providers\\FortifyServiceProvider` class:\n\n```php\nuse Laravel\\Fortify\\Fortify;\nuse Illuminate\\Http\\Request;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Fortify::resetPasswordView(function (Request $request) {\n        return view('auth.reset-password', ['request' => $request]);\n    });\n\n    // ...\n}\n```\n\nFortify will take care of defining the route to display this view. Your `reset-password` template should include a form that makes a POST request to `/reset-password`.\n\nThe `/reset-password` endpoint expects a string `email` field, a `password` field, a `password_confirmation` field, and a hidden field named `token` that contains the value of `request()->route('token')`. The name of the \"email\" field / database column should match the `email` configuration value defined within your application's `fortify` configuration file.\n\n---\n\n## Passwords Routing Requesting the Password Reset Link Handling the Form Submission\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n#### Handling the Form Submission\n\nNext, we will define a route that handles the form submission request from the \"forgot password\" view. This route will be responsible for validating the email address and sending the password reset request to the corresponding user:\n\n```php\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Password;\n\nRoute::post('/forgot-password', function (Request $request) {\n    $request->validate(['email' => 'required|email']);\n\n    $status = Password::sendResetLink(\n        $request->only('email')\n    );\n\n    return $status === Password::ResetLinkSent\n        ? back()->with(['status' => __($status)])\n        : back()->withErrors(['email' => __($status)]);\n})->middleware('guest')->name('password.email');\n```\n\nBefore moving on, let's examine this route in more detail. First, the request's `email` attribute is validated. Next, we will use Laravel's built-in \"password broker\" (via the `Password` facade) to send a password reset link to the user. The password broker will take care of retrieving the user by the given field (in this case, the email address) and sending the user a password reset link via Laravel's built-in [notification system](/docs/{{version}}/notifications).\n\nThe `sendResetLink` method returns a \"status\" slug. This status may be translated using Laravel's [localization](/docs/{{version}}/localization) helpers in order to display a user-friendly message to the user regarding the status of their request. The translation of the password reset status is determined by your application's `lang/{lang}/passwords.php` language file. An entry for each possible value of the status slug is located within the `passwords` language file.\n\n> [!NOTE]\n> By default, the Laravel application skeleton does not include the `lang` directory. If you would like to customize Laravel's language files, you may publish them via the `lang:publish` Artisan command.\n\nYou may be wondering how Laravel knows how to retrieve the user record from your application's database when calling the `Password` facade's `sendResetLink` method. The Laravel password broker utilizes your authentication system's \"user providers\" to retrieve database records. The user provider used by the password broker is configured within the `passwords` configuration array of your `config/auth.php` configuration file. To learn more about writing custom user providers, consult the [authentication documentation](/docs/{{version}}/authentication#adding-custom-user-providers).\n\n> [!NOTE]\n> When manually implementing password resets, you are required to define the contents of the views and routes yourself. If you would like scaffolding that includes all necessary authentication and verification logic, check out the [Laravel application starter kits](/docs/{{version}}/starter-kits).\n\n---\n\n## Fortify Installation Disabling Views Disabling Views and Password Reset\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n#### Disabling Views and Password Reset\n\nIf you choose to disable Fortify's views and you will be implementing password reset features for your application, you should still define a route named `password.reset` that is responsible for displaying your application's \"reset password\" view. This is necessary because Laravel's `Illuminate\\Auth\\Notifications\\ResetPassword` notification will generate the password reset URL via the `password.reset` named route.\n\n---\n\n## Passwords\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n# Resetting Passwords\n\n- [Introduction](#introduction)\n    - [Configuration](#configuration)\n    - [Driver Prerequisites](#driver-prerequisites)\n    - [Model Preparation](#model-preparation)\n    - [Configuring Trusted Hosts](#configuring-trusted-hosts)\n- [Routing](#routing)\n    - [Requesting the Password Reset Link](#requesting-the-password-reset-link)\n    - [Resetting the Password](#resetting-the-password)\n- [Deleting Expired Tokens](#deleting-expired-tokens)\n- [Customization](#password-customization)\n\n---\n\n## Pest V4 Is Here Now With Browser Testing\nPest V4 Is Here Now With Browser Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/pest-v4-is-here-now-with-browser-testing.md\n\n---\ntitle: Pest v4 Is Here — Now with Browser Testing\ndescription: Today, we're thrilled to announce Pest v4 — our biggest release yet, featuring powerful new browser testing with parallel support and full Laravel integration.\n---\n\n> To get started with Pest v4's new features including browser testing, please refer to the upgrade guide: [Upgrade Guide →](/docs/upgrade-guide).\n\n- [Browser Testing](#content-pest-v4-is-here-now-with-browser-testing)\n- [Smoke Testing](#content-smoke-testing)\n- [Visual Regression Testing](#content-visual-regression-testing)\n- [Test Sharding](#content-test-sharding)\n- [Type Coverage Is Much Faster](#content-type-coverage-is-much-faster)\n- [Profanity Checking](#content-profanity-checking)\n- [On Top of PHPUnit 12](#content-on-top-of-phpunit-12)\n\n# Pest v4 Is Here — Now with Browser Testing\n\nToday, we're thrilled to announce the release of **Pest v4**, bringing the biggest testing upgrade yet: powerful **[Browser Testing](/docs/browser-testing)**. Pest's new browser testing features let you run elegant, maintainable browser tests — with first-class support for Laravel's testing API and the ability to run tests in parallel. For the first time, this is browser testing that feels as good as writing unit tests.\n\nHere is the creator of Pest, [Nuno Maduro](https://twitter.com/enunomaduro), demoing the new browser testing features in Pest v4 at Laracon US:\n\n[![Pest v4](/assets/pest4_play.png)](https://youtu.be/f5gAgwwwwOI?si=LtPpySZe3tf8qMjz&t=52)\n\nHere is an example of Browser Testing using [Laravel](https://laravel.com):\n\n```php\nit('may reset the password', function () {\n    // access any laravel testing helpers...\n    Notification::fake();\n\n    // access to the database — using the RefreshDatabase trait (even sqlite in memory...)\n    $this->actingAs(User::factory()->create());\n\n    $page = visit('/sign-in') // visit on a real browser...\n        ->on()->mobile() // or ->desktop(), ->tablet(), etc...\n        ->inDarkMode(); // or ->inLightMode()\n\n    $page->assertSee('Sign In')\n         ->click('Forgot Password?')\n         ->type('email', 'nuno@laravel.com')\n         ->press('Send Reset Link')\n         ->assertSee('We have emailed your password reset link!')\n         ->assertNoJavascriptErrors(); // or ->assertNoConsoleLogs()\n\n    Notification::assertSent(ResetPassword::class);\n});\n```\n\nWith Pest v4's browser testing, you can:\n- Seamlessly use **Laravel features** like `Event::fake()`, `assertAuthenticated()`, and model factories\n- Use `RefreshDatabase`, even with SQLite in-memory databases, to ensure a clean state for each test\n- Test on **multiple browsers** (Chrome, Firefox, Safari)\n- Test on **different devices** and viewports (like iPhone 14 Pro, tablets, or custom breakpoints)\n- Switch **color schemes** (light/dark mode)\n- Interact with the page (click, type, scroll, select, submit, drag-and-drop, touch gestures, etc.)\n- Run **parallel browser tests** for dramatically faster suites\n- Take **screenshots** or pause tests for debugging\n- …all with the elegance of Pest syntax\n- **Playwright-based** — modern, fast, and reliable\n\nTo get started with browser testing in Pest, you need to install the Pest Browser Plugin:\n\n```bash\ncomposer require pestphp/pest-plugin-browser --dev\n\nnpm install playwright@latest\nnpx playwright install\n```\n\nAfter, you may use the `visit()` function anywhere. Finally, running this test is as simple as executing `./vendor/bin/pest` in your terminal. Pest will handle the rest, launching a browser, navigating to the page, and performing the actions you specified.\n\n## Smoke Testing\n\nSmoke testing your application in real browsers has never been easier. With Pest v4, you can literally visit all your application pages, and ensure they don't throw any JavaScript errors, and they don't log any console errors.\n\n```php\n$routes = ['/', '/about', '/contact'];\n\nvisit($routes)->assertNoSmoke();\n\n// assertNoSmoke() is a shorthand for:\n// - assertNoJavascriptErrors()\n// - assertNoConsoleLogs()\n```\n\n## Visual Regression Testing\n\nWant to ensure your pages look exactly as expected over time? Pest v4 introduces visual regression testing with the `assertScreenshotMatches()` assertion. This allows you to take screenshots of your pages and compare them against baseline images, ensuring that your UI remains consistent across changes.\n\n```php\n$pages = visit(['/', '/about', '/contact']);\n\n$pages->assertScreenshotMatches();\n```\n\nThis is just a glimpse of what Browser Testing in Pest v4 can do. Find out more about the new features below, and check out the [Browser Testing documentation](/docs/browser-testing) for a complete guide on how to get started.\n\n## Test Sharding\n\nPest v4 introduces **Test Sharding**, allowing you to split your test suite into smaller, manageable chunks. This is particularly useful for large applications (or when running browser tests) where running all tests at once can be time-consuming.\n\nThis feature is particularly useful on CI platforms, where on things like GitHub actions you can no longer scale vertical, but rather horizontally. This means you can run your tests in parallel across multiple machines, significantly speeding up your test suite execution.\n\nTo get started with Test Sharding, you can use the `--shard` option when running Pest:\n\n```bash\n# GitHub Workflow One\n./vendor/bin/pest --shard=1/4\n\n# GitHub Workflow Two\n./vendor/bin/pest --shard=2/4\n\n# GitHub Workflow Three\n./vendor/bin/pest --shard=3/4\n\n# GitHub Workflow Four\n./vendor/bin/pest --shard=4/4\n```\n\nYou may combine this with the `--parallel` option to run your tests in parallel, and this way trully maximize your test suite execution speed:\n\n```bash\n./vendor/bin/pest --shard=1/4 --parallel\n```\n\nFinally, to easily set up sharding your CI configuration, you just have to ensure each job in your CI configuration runs a different shard of your test suite. For example, in GitHub Actions, you can use the `matrix` strategy to define multiple jobs that run different shards:\n\n```yaml\nstrategy:\n  matrix:\n    shard: [1, 2, 3, 4]\n\nname: Tests (Shard ${{ matrix.shard }}/4)\n\nsteps:\n  - name: Run tests\n    run: pest --parallel --shard ${{ matrix.shard }}/4\n```\n\n## Type Coverage Is Much Faster\n\nRemember the days when you had to wait for your type coverage to run? Not anymore! Pest v4 introduces a new type coverage engine that is significantly faster than previous versions.\n\nType coverage is 2x faster on the first run and instant on subsequent runs. This means you can quickly check your type coverage without waiting for long periods, making your development workflow much more efficient.\n\nIn addition, Type Coverage now supports **Sharding**. This means you can run type coverage with the `--shard` option, just like you do with your tests.\n\n## Profanity Checking\n\nPest v4 introduces a new feature that allows you to check for profanity in your test code. This is particularly useful for maintaining a clean and professional codebase, especially in collaborative environments.\n\nYou can enable profanity checking by adding the `--profanity` option when running Pest:\n\n```bash\n./vendor/bin/pest --profanity\n```\n\nTo start using Pest's Profanity plugin, you need to require the plugin via Composer.\n\n```bash\ncomposer require pestphp/pest-plugin-profanity --dev\n```\n\nAfter requiring the plugin, you may utilize the `--profanity` option to generate a report of your profanity.\n\n```bash\n./vendor/bin/pest --profanity\n```\n\n\n\nIf any of your files contain profanity, they will be highlighted in red and displayed using their respective line\nnumbers and the profane word(s) that have been found.\n\nAs an example, `pr31(f*ck)` means that the word \"fuck\" was found on line 31.\n\nTo learn more about the Profanity plugin and how to configure it, check out the [Profanity documentation](/docs/profanity).\n\n### Skip Locally or On CI\n\nPest v4 introduces the ability to conditionally skip tests based on the environment. You can use `skipLocally()` to skip tests when running locally, or `skipOnCi` to skip tests when running on a CI server.\n\n```php\nit('does not run locally', function () {\n    // This test will be skipped when running locally\n})->skipLocally();\n\nit('does not run on CI', function () {\n    // This test will be skipped when running on a CI server\n})->skipOnCi();\n```\n\n### Miscellaneous Improvements\n\n- You may now use `skipLocally()` or `skipOnCi` to conditionally skip tests based on the environment.\n- The `not->toHaveSuspiciousCharacters()` arch expectation has been added to help you identify potential suspicious characters in your code. This arch expectation is now enabled by default on the `php` arch preset. This expectation requires the `intl` PHP extension.\n- The expectation `toBeSlug` has been added to help you validate that a string is a valid slug.\n\n### On Top of PHPUnit 12\n\nPest v4 is built on top of PHPUnit 12, which means you get all the latest features and improvements from PHPUnit. As such, be sure to check out the [PHPUnit 12 release announcement](https://phpunit.de/announcements/phpunit-12.html).\n\n### Thanks To You, Pest v4 Is Here!\n\nThere's never been a better time to dive into testing and start using Pest. If you're ready to get started with Pest v4 right away, check out our [installation guide](/docs/installation) for step-by-step instructions. And if you're currently using an earlier version of Pest, we've got you covered with detailed upgrade instructions in our [upgrade guide](/docs/upgrade-guide).\n\nThank you for your continued support and feedback. We can't wait to see what you build with Pest v4!\n\n---\n\nThank you for reading about Pest v4's new features! Want to get started with Pest? You can find the installation guide in the next section of the documentation: [Installation →](/docs/installation)\n\n\n\n---\n\n## Fortify Password Reset Resetting the Password Handling the Password Reset Response\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n#### Handling the Password Reset Response\n\nIf the password reset request was successful, Fortify will redirect back to the `/login` route so that the user can log in with their new password. In addition, a `status` session variable will be set so that you may display the successful status of the reset on your login screen:\n\n```blade\n@if (session('status'))\n    <div class=\"mb-4 font-medium text-sm text-green-600\">\n        {{ session('status') }}\n    </div>\n@endif\n```\n\nIf the request was an XHR request, a 200 HTTP response will be returned.\n\nIf the request was not successful, the user will be redirected back to the reset password screen and the validation errors will be available to you via the shared `$errors` [Blade template variable](/docs/{{version}}/validation#quick-displaying-the-validation-errors). Or, in the case of an XHR request, the validation errors will be returned with a 422 HTTP response.\n\n---\n\n## Fortify Password Reset Requesting a Password Reset Link\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n### Requesting a Password Reset Link\n\nTo begin implementing our application's password reset functionality, we need to instruct Fortify how to return our \"forgot password\" view. Remember, Fortify is a headless authentication library. If you would like a frontend implementation of Laravel's authentication features that are already completed for you, you should use an [application starter kit](/docs/{{version}}/starter-kits).\n\nAll of Fortify's view rendering logic may be customized using the appropriate methods available via the `Laravel\\Fortify\\Fortify` class. Typically, you should call this method from the `boot` method of your application's `App\\Providers\\FortifyServiceProvider` class:\n\n```php\nuse Laravel\\Fortify\\Fortify;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Fortify::requestPasswordResetLinkView(function () {\n        return view('auth.forgot-password');\n    });\n\n    // ...\n}\n```\n\nFortify will take care of defining the `/forgot-password` endpoint that returns this view. Your `forgot-password` template should include a form that makes a POST request to the `/forgot-password` endpoint.\n\nThe `/forgot-password` endpoint expects a string `email` field. The name of this field / database column should match the `email` configuration value within your application's `fortify` configuration file.\n\n---\n\n## Fortify Password Reset Requesting a Password Reset Link Handling the Password Reset Link Request Response\nFortify | laravel/fortify@latest | Source: https://github.com/laravel/docs/blob/13.x/fortify.md\n\n\n#### Handling the Password Reset Link Request Response\n\nIf the password reset link request was successful, Fortify will redirect the user back to the `/forgot-password` endpoint and send an email to the user with a secure link they can use to reset their password. If the request was an XHR request, a 200 HTTP response will be returned.\n\nAfter being redirected back to the `/forgot-password` endpoint after a successful request, the `status` session variable may be used to display the status of the password reset link request attempt.\n\nThe value of the `$status` session variable will match one of the translation strings defined within your application's `passwords` [language file](/docs/{{version}}/localization). If you would like to customize this value and have not published Laravel's language files, you may do so via the `lang:publish` Artisan command:\n\n```html\n@if (session('status'))\n    <div class=\"mb-4 font-medium text-sm text-green-600\">\n        {{ session('status') }}\n    </div>\n@endif\n```\n\nIf the request was not successful, the user will be redirected back to the request password reset link screen and the validation errors will be available to you via the shared `$errors` [Blade template variable](/docs/{{version}}/validation#quick-displaying-the-validation-errors). Or, in the case of an XHR request, the validation errors will be returned with a 422 HTTP response.\n\n---\n\n## Passport Password Grant Customizing the User Provider\nPassport | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passport.md\n\n\n### Customizing the User Provider\n\nIf your application uses more than one [authentication user provider](/docs/{{version}}/authentication#introduction), you may specify which user provider the password grant client uses by providing a `--provider` option when creating the client via the `artisan passport:client --password` command. The given provider name should match a valid provider defined in your application's `config/auth.php` configuration file. You can then [protect your route using middleware](#multiple-authentication-guards) to ensure that only users from the guard's specified provider are authorized.\n\n---\n\n## Fortify Password Reset Customizing Password Resets\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n### Customizing Password Resets\n\nThe password reset process may be customized by modifying the `App\\Actions\\ResetUserPassword` action that was generated when you installed Laravel Fortify.\n\n---\n\n## Fortify Password Reset Requesting a Password Reset Link\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n### Requesting a Password Reset Link\n\nTo begin implementing our application's password reset functionality, we need to instruct Fortify how to return our \"forgot password\" view. Remember, Fortify is a headless authentication library. If you would like a frontend implementation of Laravel's authentication features that are already completed for you, you should use an [application starter kit](/docs/{{version}}/starter-kits).\n\nAll of Fortify's view rendering logic may be customized using the appropriate methods available via the `Laravel\\Fortify\\Fortify` class. Typically, you should call this method from the `boot` method of your application's `App\\Providers\\FortifyServiceProvider` class:\n\n```php\nuse Laravel\\Fortify\\Fortify;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Fortify::requestPasswordResetLinkView(function () {\n        return view('auth.forgot-password');\n    });\n\n    // ...\n}\n```\n\nFortify will take care of defining the `/forgot-password` endpoint that returns this view. Your `forgot-password` template should include a form that makes a POST request to the `/forgot-password` endpoint.\n\nThe `/forgot-password` endpoint expects a string `email` field. The name of this field / database column should match the `email` configuration value within your application's `fortify` configuration file.\n\n---\n\n## Attribute On Example: Real-time notifications\nAttribute On | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-on\n\n## Example: Real-time notifications\n\nHere's a practical example of a notification bell that listens for new notifications:\n\n```php\n<?php // resources/views/components/⚡notification-bell.blade.php\n\nuse Livewire\\Attributes\\On;\nuse Livewire\\Component;\n\nnew class extends Component {\n    public $unreadCount = 0;\n\n    public function mount()\n    {\n        $this->unreadCount = auth()->user()->unreadNotifications()->count();\n    }\n\n    #[On('notification-sent')] // [tl! highlight]\n    public function incrementCount()\n    {\n        $this->unreadCount++;\n    }\n\n    #[On('notifications-read')] // [tl! highlight]\n    public function resetCount()\n    {\n        $this->unreadCount = 0;\n    }\n};\n?>\n\n<button class=\"relative\">\n    <svg><!-- Bell icon --></svg>\n    @if($unreadCount > 0)\n        <span class=\"absolute -top-1 -right-1 bg-red-500 text-white rounded-full px-2 py-1 text-xs\">\n            {{ $unreadCount }}\n        </span>\n    @endif\n</button>\n```\n\nOther components can dispatch events to update the notification count:\n\n```php\n// From anywhere in your app\n$this->dispatch('notification-sent');\n```\n\n---\n\n## Fortify Password Reset Resetting the Password Handling the Password Reset Response\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n#### Handling the Password Reset Response\n\nIf the password reset request was successful, Fortify will redirect back to the `/login` route so that the user can log in with their new password. In addition, a `status` session variable will be set so that you may display the successful status of the reset on your login screen:\n\n```blade\n@if (session('status'))\n    <div class=\"mb-4 font-medium text-sm text-green-600\">\n        {{ session('status') }}\n    </div>\n@endif\n```\n\nIf the request was an XHR request, a 200 HTTP response will be returned.\n\nIf the request was not successful, the user will be redirected back to the reset password screen and the validation errors will be available to you via the shared `$errors` [Blade template variable](/docs/{{version}}/validation#quick-displaying-the-validation-errors). Or, in the case of an XHR request, the validation errors will be returned with a 422 HTTP response.\n\n---\n\n## Passwords Deleting Expired Tokens\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n## Deleting Expired Tokens\n\nIf you are using the `database` driver, password reset tokens that have expired will still be present within your database. However, you may easily delete these records using the `auth:clear-resets` Artisan command:\n\n```shell\nphp artisan auth:clear-resets\n```\n\nIf you would like to automate this process, consider adding the command to your application's [scheduler](/docs/{{version}}/scheduling):\n\n```php\nuse Illuminate\\Support\\Facades\\Schedule;\n\nSchedule::command('auth:clear-resets')->everyFifteenMinutes();\n```\n\n---\n\n## Telescope Available Watchers Notification Watcher\nTelescope | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/telescope.md\n\n\n### Notification Watcher\n\nThe notification watcher records all [notifications](/docs/{{version}}/notifications) sent by your application. If the notification triggers an email and you have the mail watcher enabled, the email will also be available for preview on the mail watcher screen.\n\n---\n\n## Passwords Routing Requesting the Password Reset Link The Password Reset Link Request Form\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n#### The Password Reset Link Request Form\n\nFirst, we will define the routes that are needed to request password reset links. To get started, we will define a route that returns a view with the password reset link request form:\n\n```php\nRoute::get('/forgot-password', function () {\n    return view('auth.forgot-password');\n})->middleware('guest')->name('password.request');\n```\n\nThe view that is returned by this route should have a form containing an `email` field, which will allow the user to request a password reset link for a given email address.\n\n---\n\n## Validation Validating Passwords Defining Default Password Rules\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Defining Default Password Rules\n\nYou may find it convenient to specify the default validation rules for passwords in a single location of your application. You can easily accomplish this using the `Password::defaults` method, which accepts a closure. The closure given to the `defaults` method should return the default configuration of the Password rule. Typically, the `defaults` rule should be called within the `boot` method of one of your application's service providers:\n\n```php\nuse Illuminate\\Validation\\Rules\\Password;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Password::defaults(function () {\n        $rule = Password::min(8);\n\n        return $this->app->isProduction()\n            ? $rule->mixedCase()->uncompromised()\n            : $rule;\n    });\n}\n```\n\nThen, when you would like to apply the default rules to a particular password undergoing validation, you may invoke the `defaults` method with no arguments:\n\n```php\n'password' => ['required', Password::defaults()],\n```\n\nOccasionally, you may want to attach additional validation rules to your default password validation rules. You may use the `rules` method to accomplish this:\n\n```php\nuse App\\Rules\\ZxcvbnRule;\n\nPassword::defaults(function () {\n    $rule = Password::min(8)->rules([new ZxcvbnRule]);\n\n    // ...\n});\n```\n\n---\n\n## Structure The App Directory The Notifications Directory\nStructure | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/structure.md\n\n\n### The Notifications Directory\n\nThis directory does not exist by default, but will be created for you if you execute the `make:notification` Artisan command. The `Notifications` directory contains all of the \"transactional\" [notifications](/docs/{{version}}/notifications) that are sent by your application, such as simple notifications about events that happen within your application. Laravel's notification feature abstracts sending notifications over a variety of drivers such as email, Slack, SMS, or stored in a database.\n\n---\n\n## Passwords Customization Reset Email Customization\nPasswords | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/passwords.md\n\n\n#### Reset Email Customization\n\nYou may easily modify the notification class used to send the password reset link to the user. To get started, override the `sendPasswordResetNotification` method on your `App\\Models\\User` model. Within this method, you may send the notification using any [notification class](/docs/{{version}}/notifications) of your own creation. The password reset `$token` is the first argument received by the method. You may use this `$token` to build the password reset URL of your choice and send your notification to the user:\n\n```php\nuse App\\Notifications\\ResetPasswordNotification;\n\n/**\n * Send a password reset notification to the user.\n *\n * @param  string  $token\n */\npublic function sendPasswordResetNotification($token): void\n{\n    $url = 'https://example.com/reset-password?token='.$token;\n\n    $this->notify(new ResetPasswordNotification($url));\n}\n```\n\n---\n\n## Fortify Password Reset Requesting a Password Reset Link Handling the Password Reset Link Request Response\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n#### Handling the Password Reset Link Request Response\n\nIf the password reset link request was successful, Fortify will redirect the user back to the `/forgot-password` endpoint and send an email to the user with a secure link they can use to reset their password. If the request was an XHR request, a 200 HTTP response will be returned.\n\nAfter being redirected back to the `/forgot-password` endpoint after a successful request, the `status` session variable may be used to display the status of the password reset link request attempt.\n\nThe value of the `$status` session variable will match one of the translation strings defined within your application's `passwords` [language file](/docs/{{version}}/localization). If you would like to customize this value and have not published Laravel's language files, you may do so via the `lang:publish` Artisan command:\n\n```html\n@if (session('status'))\n    <div class=\"mb-4 font-medium text-sm text-green-600\">\n        {{ session('status') }}\n    </div>\n@endif\n```\n\nIf the request was not successful, the user will be redirected back to the request password reset link screen and the validation errors will be available to you via the shared `$errors` [Blade template variable](/docs/{{version}}/validation#quick-displaying-the-validation-errors). Or, in the case of an XHR request, the validation errors will be returned with a 422 HTTP response.\n\n---\n\n## Prompts Notifications\nPrompts | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/prompts.md\n\n\n## Notifications\n\nThe `notify` function sends a native desktop notification from the terminal:\n\n```php\nuse function Laravel\\Prompts\\notify;\n\nnotify('Build Complete', 'Deployed to production');\n```\n\nNotifications are supported on macOS (via `osascript`) and Linux (via `notify-send` with `kdialog` fallback).\n\nOn macOS, you may also include a `subtitle` and a `sound`:\n\n```php\nnotify(\n    title: 'Build Complete',\n    body: 'Deployed to production',\n    subtitle: 'staging-server',\n    sound: 'Glass',\n);\n```\n\nOn Linux, you may provide a custom `icon`:\n\n```php\nnotify(\n    title: 'Build Complete',\n    body: 'Deployed to production',\n    icon: '/path/to/icon.png',\n);\n```\n\n---\n\n## Fortify Password Confirmation\nFortify | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/fortify.md\n\n\n## Password Confirmation\n\nWhile building your application, you may occasionally have actions that should require the user to confirm their password before the action is performed. Typically, these routes are protected by Laravel's built-in `password.confirm` middleware.\n\nTo begin implementing password confirmation functionality, we need to instruct Fortify how to return our application's \"password confirmation\" view. Remember, Fortify is a headless authentication library. If you would like a frontend implementation of Laravel's authentication features that are already completed for you, you should use an [application starter kit](/docs/{{version}}/starter-kits).\n\nAll of Fortify's view rendering logic may be customized using the appropriate methods available via the `Laravel\\Fortify\\Fortify` class. Typically, you should call this method from the `boot` method of your application's `App\\Providers\\FortifyServiceProvider` class:\n\n```php\nuse Laravel\\Fortify\\Fortify;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Fortify::confirmPasswordView(function () {\n        return view('auth.confirm-password');\n    });\n\n    // ...\n}\n```\n\nFortify will take care of defining the `/user/confirm-password` endpoint that returns this view. Your `confirm-password` template should include a form that makes a POST request to the `/user/confirm-password` endpoint. The `/user/confirm-password` endpoint expects a `password` field that contains the user's current password.\n\nIf the password matches the user's current password, Fortify will redirect the user to the route they were attempting to access. If the request was an XHR request, a 201 HTTP response will be returned.\n\nIf the request was not successful, the user will be redirected back to the confirm password screen and the validation errors will be available to you via the shared `$errors` Blade template variable. Or, in the case of an XHR request, the validation errors will be returned with a 422 HTTP response.\n\n---\n\n## Validation Validating Passwords\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Validating Passwords\n\nTo ensure that passwords have an adequate level of complexity, you may use Laravel's `Password` rule object:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rules\\Password;\n\n$validator = Validator::make($request->all(), [\n    'password' => ['required', 'confirmed', Password::min(8)],\n]);\n```\n\nThe `Password` rule object allows you to easily customize the password complexity requirements for your application, such as specifying that passwords require at least one letter, number, symbol, or characters with mixed casing:\n\n```php\n// Require at least 8 characters...\nPassword::min(8)\n\n// Require at least one letter...\nPassword::min(8)->letters()\n\n// Require at least one uppercase and one lowercase letter...\nPassword::min(8)->mixedCase()\n\n// Require at least one number...\nPassword::min(8)->numbers()\n\n// Require at least one symbol...\nPassword::min(8)->symbols()\n```\n\nIn addition, you may ensure that a password has not been compromised in a public password data breach leak using the `uncompromised` method:\n\n```php\nPassword::min(8)->uncompromised()\n```\n\nInternally, the `Password` rule object uses the [k-Anonymity](https://en.wikipedia.org/wiki/K-anonymity) model to determine if a password has been leaked via the [haveibeenpwned.com](https://haveibeenpwned.com) service without sacrificing the user's privacy or security.\n\nBy default, if a password appears at least once in a data leak, it will be considered compromised. You can customize this threshold using the first argument of the `uncompromised` method:\n\n```php\n// Ensure the password appears less than 3 times in the same data leak...\nPassword::min(8)->uncompromised(3);\n```\n\nOf course, you may chain all the methods in the examples above:\n\n```php\nPassword::min(8)\n    ->letters()\n    ->mixedCase()\n    ->numbers()\n    ->symbols()\n    ->uncompromised()\n```\n\n---\n\n## Notifications Testing On-Demand Notifications\nNotifications | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/notifications.md\n\n\n#### On-Demand Notifications\n\nIf the code you are testing sends [on-demand notifications](#on-demand-notifications), you can test that the on-demand notification was sent via the `assertSentOnDemand` method:\n\n```php\nNotification::assertSentOnDemand(OrderShipped::class);\n```\n\nBy passing a closure as the second argument to the `assertSentOnDemand` method, you may determine if an on-demand notification was sent to the correct \"route\" address:\n\n```php\nNotification::assertSentOnDemand(\n    OrderShipped::class,\n    function (OrderShipped $notification, array $channels, object $notifiable) use ($user) {\n        return $notifiable->routes['mail'] === $user->email;\n    }\n);\n```\n\n_[Truncated]_"}]


sed -n '1,220p' tests/Feature/Auth/PasswordResetTest.php

PasswordResetTest.php

<?php

use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Support\Facades\Notification;

uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

test('reset password link screen can be rendered', function () {
    $response = $this->get(route('password.request'));

    $response->assertOk();
});

test('reset password link can be requested', function () {
    Notification::fake();

    $user = User::factory()->create();

    $this->post(route('password.request'), ['email' => $user->email]);

    Notification::assertSentTo($user, ResetPassword::class);
});

test('reset password screen can be rendered', function () {
    Notification::fake();

    $user = User::factory()->create();

    $this->post(route('password.request'), ['email' => $user->email]);

    Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
        $response = $this->get(route('password.reset', $notification->token));
        $response->assertOk();

        return true;
    });
});

test('password can be reset with valid token', function () {
    Notification::fake();

    $user = User::factory()->create();

    $this->post(route('password.request'), ['email' => $user->email]);

    Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
        $response = $this->post(route('password.update'), [
            'token' => $notification->token,
            'email' => $user->email,
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);

        $response
            ->assertSessionHasNoErrors()
            ->assertRedirect(route('login', absolute: false));

        return true;
    });
});


sed -n '120,210p' specs/06-AUTH-AND-SECURITY.md

### 1.2 Customer Authentication

| Setting | Value |
|---------|-------|
| Guard name | `customer` (custom guard) |
| Provider name | `customers` (Eloquent driver) |
| Model | `App\Models\Customer` |
| Driver | Session (cookie-based) |
| Login route | `GET /account/login` |
| Registration route | `GET /account/register` |

#### Customer Login Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant LW as Livewire Component
    participant Auth as Auth Guard (customer)
    participant Store as ResolveStore Middleware
    participant DB as Database

    Note over Store: Store already resolved from hostname
    B->>LW: Submit email + password
    LW->>LW: Validate input
    LW->>LW: Check rate limiter (5/min per IP)
    LW->>Auth: attempt(credentials + store_id, remember)
    Auth->>DB: SELECT from customers WHERE email = ? AND store_id = ?
    Auth->>Auth: Verify bcrypt hash
    alt Valid
        Auth-->>LW: Success (regenerate session)
        LW-->>B: Redirect to /account (or intended URL)
    else Invalid
        Auth-->>LW: Failure
        LW-->>B: Flash "Invalid credentials"
    end
```

**Requirements:**

- Customer login is always scoped to the current store. The store is resolved from the request hostname by the `ResolveStore` middleware before the login attempt.
- The custom `CustomerUserProvider` must inject `store_id` into the credential check so the query filters by both `email` and `store_id`.
- The same email address can exist in different stores (multi-tenant isolation).
- Rate limiting: maximum 5 attempts per minute per IP, using the same `login` rate limiter.
- After login: redirect to `/account` or the intended URL (if the customer was redirected from a protected page).

#### Customer Registration Flow

```mermaid
flowchart TD
    A[Customer visits /account/register] --> B[Fill form: name, email, password, password_confirmation, marketing_opt_in]
    B --> C{Validate via RegisterCustomerRequest}
    C -- Invalid --> D[Show validation errors]
    C -- Valid --> E[Create Customer record scoped to current store]
    E --> F[Auto-login via customer guard]
    F --> G[Redirect to /account]
```

**Registration fields:**

| Field | Type | Required | Validation |
|-------|------|:--------:|------------|
| name | string | Y | max:255 |
| email | string | Y | valid email, unique per store (composite unique on email + store_id) |
| password | string | Y | min:8, confirmed |
| password_confirmation | string | Y | must match password |
| marketing_opt_in | boolean | N | defaults to false |

#### Customer Store Scoping

- The `customers` auth provider requires a custom `CustomerUserProvider` class.
- This provider overrides the default Eloquent provider to always include `store_id` in credential queries.
- The `store_id` is obtained from the container-bound current store (resolved by `ResolveStore` middleware).
- This ensures that `Auth::guard('customer')->attempt()` never crosses store boundaries.

#### Customer Password Reset

- Uses a separate password broker: `customers` broker.
- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
- Token records include `store_id` to scope resets to the correct store.
- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
- Token expiration: 60 minutes.
- Throttle: one reset email per 60 seconds.
- Generic response on the "forgot password" form (do not reveal whether email exists in the store).

---

### 1.3 API Authentication (Sanctum)

| Setting | Value |
|---------|-------|
| Guard name | `sanctum` |


rg -n "password reset|forgot password|reset-password|customer_password_reset|PasswordReset|password_reset|broker|ResetPassword|forgot" specs app config routes resources tests database

rg -n 'password reset|forgot password|reset-password|customer_password_reset|PasswordReset|password_reset|broker|ResetPassword|forgot' specs app config routes resources tests database

Question: password reset|forgot password|reset-password|customer_password_reset|PasswordReset|password_reset|broker|ResetPassword|forgot

config/auth.php:11:    | reset "broker" for your application. You may change these values
config/auth.php:98:    | generating more password reset tokens. This prevents the user from
config/auth.php:99:    | quickly generating a very large amount of password reset tokens.
config/auth.php:106:            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
config/auth.php:113:            'table' => 'customer_password_reset_tokens',
config/fortify.php:25:    | Here you may specify which password broker Fortify can use when a user
config/fortify.php:27:    | of your password brokers setup in your "auth" configuration file.
config/fortify.php:42:    | Out of the box, Fortify expects forgot password and reset password
config/fortify.php:71:    | authentication or password reset when the operations are successful
specs/01-DATABASE-SCHEMA.md:1179:### password_reset_tokens
specs/01-DATABASE-SCHEMA.md:1189:### customer_password_reset_tokens
specs/01-DATABASE-SCHEMA.md:1682:| Laravel framework tables | sessions, cache, cache_locks, jobs, job_batches, failed_jobs, personal_access_tokens, password_reset_tokens, customer_password_reset_tokens | 9 |
specs/06-AUTH-AND-SECURITY.md:75:    B->>App: GET /admin/forgot-password
specs/06-AUTH-AND-SECURITY.md:76:    App-->>B: Render forgot-password form
specs/06-AUTH-AND-SECURITY.md:77:    B->>App: POST /admin/forgot-password (email)
specs/06-AUTH-AND-SECURITY.md:80:        App->>DB: Store reset token in password_reset_tokens table
specs/06-AUTH-AND-SECURITY.md:84:    B->>App: GET /admin/reset-password/{token}
specs/06-AUTH-AND-SECURITY.md:85:    App-->>B: Render reset-password form
specs/06-AUTH-AND-SECURITY.md:86:    B->>App: POST /admin/reset-password (token, email, password, password_confirmation)
specs/06-AUTH-AND-SECURITY.md:99:- Uses Laravel's built-in password broker (`users` broker).
specs/06-AUTH-AND-SECURITY.md:100:- Reset tokens stored in the `password_reset_tokens` table.
specs/06-AUTH-AND-SECURITY.md:103:- The "forgot password" response must be generic (do not reveal whether email exists).
specs/06-AUTH-AND-SECURITY.md:196:- Uses a separate password broker: `customers` broker.
specs/06-AUTH-AND-SECURITY.md:197:- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
specs/06-AUTH-AND-SECURITY.md:199:- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
specs/06-AUTH-AND-SECURITY.md:202:- Generic response on the "forgot password" form (do not reveal whether email exists in the store).
specs/06-AUTH-AND-SECURITY.md:292:| `users` | `users` | `password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md:293:| `customers` | `customers` | `customer_password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md:295:**Defaults:** The default guard is `web` and the default password broker is `users`.
specs/06-AUTH-AND-SECURITY.md:611:| Admin web (auth pages) | `web` | Login, forgot-password, reset-password |
specs/02-API-ROUTES.md:20:| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
specs/02-API-ROUTES.md:21:| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
specs/02-API-ROUTES.md:22:| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
specs/02-API-ROUTES.md:23:| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |
specs/02-API-ROUTES.md:179:| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
specs/02-API-ROUTES.md:180:| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
specs/02-API-ROUTES.md:181:| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
specs/02-API-ROUTES.md:182:| POST | `/reset-password` | Reset password | Handled by Livewire action |
specs/09-IMPLEMENTATION-ROADMAP.md:96:| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
specs/09-IMPLEMENTATION-ROADMAP.md:188:| Password reset | Standard Laravel flow with `Password::broker('users')` |
specs/09-IMPLEMENTATION-ROADMAP.md:1933:| `config/auth.php` | Customer guard | Add customer guard, customer provider, customer password broker | Customer storefront auth |
specs/progress.md:34:| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout, customer guard/provider, customer login/register, login rate limiting, session hardening, resource policies, encrypted app client/webhook secrets, and one-time display developer token generation are implemented. Customer password reset and Sanctum token auth still missing. |
specs/progress.md:50:- 2026-05-03: Boost schema summary confirmed Phase 1 tables: organizations, stores, store_domains, store_users, store_settings, customers, customer_password_reset_tokens, users with password_hash.
specs/progress.md:284:- Phase 1 still lacks admin/customer password reset at the spec paths and a custom customer password reset token repository that scopes by store_id.
specs/05-BUSINESS-LOGIC.md:1315:- Password reset: standard Laravel password reset flow adapted for the customer guard and broker.
tests/Feature/Auth/PasswordResetTest.php:4:use Illuminate\Auth\Notifications\ResetPassword;
tests/Feature/Auth/PasswordResetTest.php:22:    Notification::assertSentTo($user, ResetPassword::class);
tests/Feature/Auth/PasswordResetTest.php:32:    Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
tests/Feature/Auth/PasswordResetTest.php:47:    Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
specs/04-STOREFRONT-UI.md:1921:| Password reset sent | "We have emailed your password reset link!" | success |
resources/views/livewire/auth/forgot-password.blade.php:3:        <x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />
resources/views/livewire/auth/forgot-password.blade.php:22:                {{ __('Email password reset link') }}
resources/views/livewire/auth/reset-password.blade.php:46:                <flux:button type="submit" variant="primary" class="w-full" data-test="reset-password-button">
app/Providers/FortifyServiceProvider.php:52:        Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
app/Providers/FortifyServiceProvider.php:53:        Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
app/Actions/Fortify/ResetUserPassword.php:15:     * Validate and reset the user's forgotten password.
database/migrations/2026_05_03_212158_create_customer_password_reset_tokens_table.php:14:        Schema::create('customer_password_reset_tokens', function (Blueprint $table) {
database/migrations/2026_05_03_212158_create_customer_password_reset_tokens_table.php:29:        Schema::dropIfExists('customer_password_reset_tokens');
database/migrations/0001_01_01_000000_create_users_table.php:29:        Schema::create('password_reset_tokens', function (Blueprint $table) {
database/migrations/0001_01_01_000000_create_users_table.php:51:        Schema::dropIfExists('password_reset_tokens');


Output:
Chunk ID: 548b95
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1830
Output:
config/auth.php:11:    | reset "broker" for your application. You may change these values
config/auth.php:98:    | generating more password reset tokens. This prevents the user from
config/auth.php:99:    | quickly generating a very large amount of password reset tokens.
config/auth.php:106:            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
config/auth.php:113:            'table' => 'customer_password_reset_tokens',
config/fortify.php:25:    | Here you may specify which password broker Fortify can use when a user
config/fortify.php:27:    | of your password brokers setup in your "auth" configuration file.
config/fortify.php:42:    | Out of the box, Fortify expects forgot password and reset password
config/fortify.php:71:    | authentication or password reset when the operations are successful
specs/01-DATABASE-SCHEMA.md:1179:### password_reset_tokens
specs/01-DATABASE-SCHEMA.md:1189:### customer_password_reset_tokens
specs/01-DATABASE-SCHEMA.md:1682:| Laravel framework tables | sessions, cache, cache_locks, jobs, job_batches, failed_jobs, personal_access_tokens, password_reset_tokens, customer_password_reset_tokens | 9 |
specs/06-AUTH-AND-SECURITY.md:75:    B->>App: GET /admin/forgot-password
specs/06-AUTH-AND-SECURITY.md:76:    App-->>B: Render forgot-password form
specs/06-AUTH-AND-SECURITY.md:77:    B->>App: POST /admin/forgot-password (email)
specs/06-AUTH-AND-SECURITY.md:80:        App->>DB: Store reset token in password_reset_tokens table
specs/06-AUTH-AND-SECURITY.md:84:    B->>App: GET /admin/reset-password/{token}
specs/06-AUTH-AND-SECURITY.md:85:    App-->>B: Render reset-password form
specs/06-AUTH-AND-SECURITY.md:86:    B->>App: POST /admin/reset-password (token, email, password, password_confirmation)
specs/06-AUTH-AND-SECURITY.md:99:- Uses Laravel's built-in password broker (`users` broker).
specs/06-AUTH-AND-SECURITY.md:100:- Reset tokens stored in the `password_reset_tokens` table.
specs/06-AUTH-AND-SECURITY.md:103:- The "forgot password" response must be generic (do not reveal whether email exists).
specs/06-AUTH-AND-SECURITY.md:196:- Uses a separate password broker: `customers` broker.
specs/06-AUTH-AND-SECURITY.md:197:- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
specs/06-AUTH-AND-SECURITY.md:199:- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
specs/06-AUTH-AND-SECURITY.md:202:- Generic response on the "forgot password" form (do not reveal whether email exists in the store).
specs/06-AUTH-AND-SECURITY.md:292:| `users` | `users` | `password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md:293:| `customers` | `customers` | `customer_password_reset_tokens` | 60 min | 60 sec |
specs/06-AUTH-AND-SECURITY.md:295:**Defaults:** The default guard is `web` and the default password broker is `users`.
specs/06-AUTH-AND-SECURITY.md:611:| Admin web (auth pages) | `web` | Login, forgot-password, reset-password |
specs/02-API-ROUTES.md:20:| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
specs/02-API-ROUTES.md:21:| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
specs/02-API-ROUTES.md:22:| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
specs/02-API-ROUTES.md:23:| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |
specs/02-API-ROUTES.md:179:| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
specs/02-API-ROUTES.md:180:| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
specs/02-API-ROUTES.md:181:| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
specs/02-API-ROUTES.md:182:| POST | `/reset-password` | Reset password | Handled by Livewire action |
specs/09-IMPLEMENTATION-ROADMAP.md:96:| `config/auth.php` | Passwords | Add `customers` password broker | Customer password reset |
specs/09-IMPLEMENTATION-ROADMAP.md:188:| Password reset | Standard Laravel flow with `Password::broker('users')` |
specs/09-IMPLEMENTATION-ROADMAP.md:1933:| `config/auth.php` | Customer guard | Add customer guard, customer provider, customer password broker | Customer storefront auth |
specs/progress.md:34:| Auth/security | `specs/06-AUTH-AND-SECURITY.md` | partial | Admin Livewire login/logout, customer guard/provider, customer login/register, login rate limiting, session hardening, resource policies, encrypted app client/webhook secrets, and one-time display developer token generation are implemented. Customer password reset and Sanctum token auth still missing. |
specs/progress.md:50:- 2026-05-03: Boost schema summary confirmed Phase 1 tables: organizations, stores, store_domains, store_users, store_settings, customers, customer_password_reset_tokens, users with password_hash.
specs/progress.md:284:- Phase 1 still lacks admin/customer password reset at the spec paths and a custom customer password reset token repository that scopes by store_id.
specs/05-BUSINESS-LOGIC.md:1315:- Password reset: standard Laravel password reset flow adapted for the customer guard and broker.
tests/Feature/Auth/PasswordResetTest.php:4:use Illuminate\Auth\Notifications\ResetPassword;
tests/Feature/Auth/PasswordResetTest.php:22:    Notification::assertSentTo($user, ResetPassword::class);
tests/Feature/Auth/PasswordResetTest.php:32:    Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
tests/Feature/Auth/PasswordResetTest.php:47:    Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
specs/04-STOREFRONT-UI.md:1921:| Password reset sent | "We have emailed your password reset link!" | success |
resources/views/livewire/auth/forgot-password.blade.php:3:        <x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />
resources/views/livewire/auth/forgot-password.blade.php:22:                {{ __('Email password reset link') }}
resources/views/livewire/auth/reset-password.blade.php:46:                <flux:button type="submit" variant="primary" class="w-full" data-test="reset-password-button">
app/Providers/FortifyServiceProvider.php:52:        Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
app/Providers/FortifyServiceProvider.php:53:        Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
app/Actions/Fortify/ResetUserPassword.php:15:     * Validate and reset the user's forgotten password.
database/migrations/2026_05_03_212158_create_customer_password_reset_tokens_table.php:14:        Schema::create('customer_password_reset_tokens', function (Blueprint $table) {
database/migrations/2026_05_03_212158_create_customer_password_reset_tokens_table.php:29:        Schema::dropIfExists('customer_password_reset_tokens');
database/migrations/0001_01_01_000000_create_users_table.php:29:        Schema::create('password_reset_tokens', function (Blueprint $table) {
database/migrations/0001_01_01_000000_create_users_table.php:51:        Schema::dropIfExists('password_reset_tokens');


Output:
Chunk ID: a80547
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 907
Output:
### 1.2 Customer Authentication

| Setting | Value |
|---------|-------|
| Guard name | `customer` (custom guard) |
| Provider name | `customers` (Eloquent driver) |
| Model | `App\Models\Customer` |
| Driver | Session (cookie-based) |
| Login route | `GET /account/login` |
| Registration route | `GET /account/register` |

#### Customer Login Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant LW as Livewire Component
    participant Auth as Auth Guard (customer)
    participant Store as ResolveStore Middleware
    participant DB as Database

    Note over Store: Store already resolved from hostname
    B->>LW: Submit email + password
    LW->>LW: Validate input
    LW->>LW: Check rate limiter (5/min per IP)
    LW->>Auth: attempt(credentials + store_id, remember)
    Auth->>DB: SELECT from customers WHERE email = ? AND store_id = ?
    Auth->>Auth: Verify bcrypt hash
    alt Valid
        Auth-->>LW: Success (regenerate session)
        LW-->>B: Redirect to /account (or intended URL)
    else Invalid
        Auth-->>LW: Failure
        LW-->>B: Flash "Invalid credentials"
    end
```

**Requirements:**

- Customer login is always scoped to the current store. The store is resolved from the request hostname by the `ResolveStore` middleware before the login attempt.
- The custom `CustomerUserProvider` must inject `store_id` into the credential check so the query filters by both `email` and `store_id`.
- The same email address can exist in different stores (multi-tenant isolation).
- Rate limiting: maximum 5 attempts per minute per IP, using the same `login` rate limiter.
- After login: redirect to `/account` or the intended URL (if the customer was redirected from a protected page).

#### Customer Registration Flow

```mermaid
flowchart TD
    A[Customer visits /account/register] --> B[Fill form: name, email, password, password_confirmation, marketing_opt_in]
    B --> C{Validate via RegisterCustomerRequest}
    C -- Invalid --> D[Show validation errors]
    C -- Valid --> E[Create Customer record scoped to current store]
    E --> F[Auto-login via customer guard]
    F --> G[Redirect to /account]
```

**Registration fields:**

| Field | Type | Required | Validation |
|-------|------|:--------:|------------|
| name | string | Y | max:255 |
| email | string | Y | valid email, unique per store (composite unique on email + store_id) |
| password | string | Y | min:8, confirmed |
| password_confirmation | string | Y | must match password |
| marketing_opt_in | boolean | N | defaults to false |

#### Customer Store Scoping

- The `customers` auth provider requires a custom `CustomerUserProvider` class.
- This provider overrides the default Eloquent provider to always include `store_id` in credential queries.
- The `store_id` is obtained from the container-bound current store (resolved by `ResolveStore` middleware).
- This ensures that `Auth::guard('customer')->attempt()` never crosses store boundaries.

#### Customer Password Reset

- Uses a separate password broker: `customers` broker.
- Tokens stored in the `customer_password_reset_tokens` table (separate from admin tokens).
- Token records include `store_id` to scope resets to the correct store.
- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
- Token expiration: 60 minutes.
- Throttle: one reset email per 60 seconds.
- Generic response on the "forgot password" form (do not reveal whether email exists in the store).

---

### 1.3 API Authentication (Sanctum)

| Setting | Value |
|---------|-------|
| Guard name | `sanctum` |


Output:
Chunk ID: c7d080
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 433
Output:
<?php

use App\Models\User;
use Illuminate\Auth\Notifications\ResetPassword;
use Illuminate\Support\Facades\Notification;

uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

test('reset password link screen can be rendered', function () {
    $response = $this->get(route('password.request'));

    $response->assertOk();
});

test('reset password link can be requested', function () {
    Notification::fake();

    $user = User::factory()->create();

    $this->post(route('password.request'), ['email' => $user->email]);

    Notification::assertSentTo($user, ResetPassword::class);
});

test('reset password screen can be rendered', function () {
    Notification::fake();

    $user = User::factory()->create();

    $this->post(route('password.request'), ['email' => $user->email]);

    Notification::assertSentTo($user, ResetPassword::class, function ($notification) {
        $response = $this->get(route('password.reset', $notification->token));
        $response->assertOk();

        return true;
    });
});

test('password can be reset with valid token', function () {
    Notification::fake();

    $user = User::factory()->create();

    $this->post(route('password.request'), ['email' => $user->email]);

    Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
        $response = $this->post(route('password.update'), [
            'token' => $notification->token,
            'email' => $user->email,
            'password' => 'password',
            'password_confirmation' => 'password',
        ]);

        $response
            ->assertSessionHasNoErrors()
            ->assertRedirect(route('login', absolute: false));

        return true;
    });
});


sed -n '1,130p' app/Providers/FortifyServiceProvider.php


sed -n '1,140p' resources/views/livewire/auth/forgot-password.blade.php


sed -n '1,160p' resources/views/livewire/auth/reset-password.blade.php


/bin/zsh
-lc
sed -n '1,160p' config/auth.php

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option defines the default authentication "guard" and password
    | reset "broker" for your application. You may change these values
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | which utilizes session storage plus the Eloquent user provider.
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | If you have multiple user tables or models you may configure multiple
    | providers to represent the model / table. These providers may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],

        'customers' => [
            'driver' => 'store_scoped_eloquent',
            'model' => App\Models\Customer::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | These configuration options specify the behavior of Laravel's password
    | reset functionality, including the table utilized for token storage
    | and the user provider that is invoked to actually retrieve users.
    |
    | The expiry time is the number of minutes that each reset token will be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    | The throttle setting is the number of seconds a user must wait before
    | generating more password reset tokens. This prevents the user from
    | quickly generating a very large amount of password reset tokens.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
            'expire' => 60,
            'throttle' => 60,
        ],

        'customers' => [
            'provider' => 'customers',
            'table' => 'customer_password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the number of seconds before a password confirmation
    | window expires and users are asked to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),

];


Output:
Chunk ID: b5e8ac
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1117
Output:
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Authentication Defaults
    |--------------------------------------------------------------------------
    |
    | This option defines the default authentication "guard" and password
    | reset "broker" for your application. You may change these values
    | as required, but they're a perfect start for most applications.
    |
    */

    'defaults' => [
        'guard' => env('AUTH_GUARD', 'web'),
        'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
    ],

    /*
    |--------------------------------------------------------------------------
    | Authentication Guards
    |--------------------------------------------------------------------------
    |
    | Next, you may define every authentication guard for your application.
    | Of course, a great default configuration has been defined for you
    | which utilizes session storage plus the Eloquent user provider.
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | Supported: "session"
    |
    */

    'guards' => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'customer' => [
            'driver' => 'session',
            'provider' => 'customers',
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | User Providers
    |--------------------------------------------------------------------------
    |
    | All authentication guards have a user provider, which defines how the
    | users are actually retrieved out of your database or other storage
    | system used by the application. Typically, Eloquent is utilized.
    |
    | If you have multiple user tables or models you may configure multiple
    | providers to represent the model / table. These providers may then
    | be assigned to any extra authentication guards you have defined.
    |
    | Supported: "database", "eloquent"
    |
    */

    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => env('AUTH_MODEL', App\Models\User::class),
        ],

        'customers' => [
            'driver' => 'store_scoped_eloquent',
            'model' => App\Models\Customer::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Resetting Passwords
    |--------------------------------------------------------------------------
    |
    | These configuration options specify the behavior of Laravel's password
    | reset functionality, including the table utilized for token storage
    | and the user provider that is invoked to actually retrieve users.
    |
    | The expiry time is the number of minutes that each reset token will be
    | considered valid. This security feature keeps tokens short-lived so
    | they have less time to be guessed. You may change this as needed.
    |
    | The throttle setting is the number of seconds a user must wait before
    | generating more password reset tokens. This prevents the user from
    | quickly generating a very large amount of password reset tokens.
    |
    */

    'passwords' => [
        'users' => [
            'provider' => 'users',
            'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'),
            'expire' => 60,
            'throttle' => 60,
        ],

        'customers' => [
            'provider' => 'customers',
            'table' => 'customer_password_reset_tokens',
            'expire' => 60,
            'throttle' => 60,
        ],
    ],

    /*
    |--------------------------------------------------------------------------
    | Password Confirmation Timeout
    |--------------------------------------------------------------------------
    |
    | Here you may define the number of seconds before a password confirmation
    | window expires and users are asked to re-enter their password via the
    | confirmation screen. By default, the timeout lasts for three hours.
    |
    */

    'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800),

];


/bin/zsh
-lc
sed -n '1,140p' app/Models/User.php

<?php

namespace App\Models;

use App\Enums\StoreUserRole;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable, TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'status',
        'password',
        'last_login_at',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password_hash',
        'two_factor_secret',
        'two_factor_recovery_codes',
        'remember_token',
    ];

    /**
     * @return BelongsToMany<Store, $this>
     */
    public function stores(): BelongsToMany
    {
        return $this->belongsToMany(Store::class, 'store_users')
            ->using(StoreUser::class)
            ->withPivot('role');
    }

    public function roleForStore(Store $store): ?StoreUserRole
    {
        return $this->roleForStoreId($store->getKey());
    }

    public function roleForStoreId(int $storeId): ?StoreUserRole
    {
        $role = $this->stores()
            ->whereKey($storeId)
            ->first()
            ?->pivot
            ?->role;

        if ($role instanceof StoreUserRole) {
            return $role;
        }

        return is_string($role) ? StoreUserRole::tryFrom($role) : null;
    }

    public function getAuthPassword(): ?string
    {
        return $this->password_hash;
    }

    public function setPasswordAttribute(string $value): void
    {
        $this->attributes['password_hash'] = Hash::needsRehash($value) ? Hash::make($value) : $value;
    }

    public function getPasswordAttribute(): ?string
    {
        return $this->password_hash;
    }

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'last_login_at' => 'datetime',
            'two_factor_confirmed_at' => 'datetime',
        ];
    }

    /**
     * Get the user's initials
     */
    public function initials(): string
    {
        return Str::of($this->name)
            ->explode(' ')
            ->take(2)
            ->map(fn ($word) => Str::substr($word, 0, 1))
            ->implode('');
    }
}


/bin/zsh
-lc
sed -n '1,160p' app/Models/Customer.php

<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;

class Customer extends Authenticatable
{
    /** @use HasFactory<\Database\Factories\CustomerFactory> */
    use BelongsToStore, HasFactory, Notifiable;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'email',
        'password',
        'name',
        'marketing_opt_in',
    ];

    /**
     * @var list<string>
     */
    protected $hidden = [
        'password_hash',
        'remember_token',
    ];

    public function getAuthPassword(): ?string
    {
        return $this->password_hash;
    }

    public function setPasswordAttribute(?string $value): void
    {
        $this->attributes['password_hash'] = $value && Hash::needsRehash($value) ? Hash::make($value) : $value;
    }

    public function getPasswordAttribute(): ?string
    {
        return $this->password_hash;
    }

    /**
     * @return HasMany<Cart, $this>
     */
    public function carts(): HasMany
    {
        return $this->hasMany(Cart::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return HasMany<CustomerAddress, $this>
     */
    public function addresses(): HasMany
    {
        return $this->hasMany(CustomerAddress::class);
    }

    /**
     * @return HasMany<Order, $this>
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'marketing_opt_in' => 'bool',
        ];
    }
}


Output:
Chunk ID: 77b6d7
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 490
Output:
<?php

namespace App\Models;

use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;

class Customer extends Authenticatable
{
    /** @use HasFactory<\Database\Factories\CustomerFactory> */
    use BelongsToStore, HasFactory, Notifiable;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'email',
        'password',
        'name',
        'marketing_opt_in',
    ];

    /**
     * @var list<string>
     */
    protected $hidden = [
        'password_hash',
        'remember_token',
    ];

    public function getAuthPassword(): ?string
    {
        return $this->password_hash;
    }

    public function setPasswordAttribute(?string $value): void
    {
        $this->attributes['password_hash'] = $value && Hash::needsRehash($value) ? Hash::make($value) : $value;
    }

    public function getPasswordAttribute(): ?string
    {
        return $this->password_hash;
    }

    /**
     * @return HasMany<Cart, $this>
     */
    public function carts(): HasMany
    {
        return $this->hasMany(Cart::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return HasMany<CustomerAddress, $this>
     */
    public function addresses(): HasMany
    {
        return $this->hasMany(CustomerAddress::class);
    }

    /**
     * @return HasMany<Order, $this>
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'marketing_opt_in' => 'bool',
        ];
    }
}


Output:
Chunk ID: 638555
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 706
Output:
<?php

namespace App\Models;

use App\Enums\StoreUserRole;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Laravel\Fortify\TwoFactorAuthenticatable;

class User extends Authenticatable implements MustVerifyEmail
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable, TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'status',
        'password',
        'last_login_at',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password_hash',
        'two_factor_secret',
        'two_factor_recovery_codes',
        'remember_token',
    ];

    /**
     * @return BelongsToMany<Store, $this>
     */
    public function stores(): BelongsToMany
    {
        return $this->belongsToMany(Store::class, 'store_users')
            ->using(StoreUser::class)
            ->withPivot('role');
    }

    public function roleForStore(Store $store): ?StoreUserRole
    {
        return $this->roleForStoreId($store->getKey());
    }

    public function roleForStoreId(int $storeId): ?StoreUserRole
    {
        $role = $this->stores()
            ->whereKey($storeId)
            ->first()
            ?->pivot
            ?->role;

        if ($role instanceof StoreUserRole) {
            return $role;
        }

        return is_string($role) ? StoreUserRole::tryFrom($role) : null;
    }

    public function getAuthPassword(): ?string
    {
        return $this->password_hash;
    }

    public function setPasswordAttribute(string $value): void
    {
        $this->attributes['password_hash'] = Hash::needsRehash($value) ? Hash::make($value) : $value;
    }

    public function getPasswordAttribute(): ?string
    {
        return $this->password_hash;
    }

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'last_login_at' => 'datetime',
            'two_factor_confirmed_at' => 'datetime',
        ];
    }

    /**
     * Get the user's initials
     */
    public function initials(): string
    {
        return Str::of($this->name)
            ->explode(' ')
            ->take(2)
            ->map(fn ($word) => Str::substr($word, 0, 1))
            ->implode('');
    }
}


sed -n '1,140p' resources/views/livewire/auth/forgot-password.blade.php

<x-layouts::auth>

        <x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />

        <x-auth-session-status class="text-center" :status="session('status')" />

        <form method="POST" action="{{ route('password.email') }}" class="flex flex-col gap-6">
            @csrf

            <flux:input
                name="email"
                :label="__('Email Address')"
                type="email"
                required
                autofocus
                placeholder="email@example.com"
            />

            <flux:button variant="primary" type="submit" class="w-full" data-test="email-password-reset-link-button">
                {{ __('Email password reset link') }}
            </flux:button>
        </form>

            {{ __('Or, return to') }}
            <flux:link :href="route('login')" wire:navigate>{{ __('log in') }}</flux:link>

</x-layouts::auth>


sed -n '1,130p' app/Providers/FortifyServiceProvider.php

<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureActions();
        $this->configureViews();
        $this->configureRateLimiting();
    }

    /**
     * Configure Fortify actions.
     */
    private function configureActions(): void
    {
        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
        Fortify::createUsersUsing(CreateNewUser::class);
    }

    /**
     * Configure Fortify views.
     */
    private function configureViews(): void
    {
        Fortify::loginView(fn () => view('livewire.auth.login'));
        Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
        Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
        Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
        Fortify::registerView(fn () => view('livewire.auth.register'));
        Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
        Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
    }

    /**
     * Configure rate limiting.
     */
    private function configureRateLimiting(): void
    {
        RateLimiter::for('two-factor', function (Request $request) {
            return Limit::perMinute(5)->by($request->session()->get('login.id'));
        });

        RateLimiter::for('login', function (Request $request) {
            return Limit::perMinute(5)->by($request->ip());
        });
    }
}


Output:
Chunk ID: ce8266
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 504
Output:
<?php

namespace App\Providers;

use App\Actions\Fortify\CreateNewUser;
use App\Actions\Fortify\ResetUserPassword;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Fortify;

class FortifyServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        $this->configureActions();
        $this->configureViews();
        $this->configureRateLimiting();
    }

    /**
     * Configure Fortify actions.
     */
    private function configureActions(): void
    {
        Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
        Fortify::createUsersUsing(CreateNewUser::class);
    }

    /**
     * Configure Fortify views.
     */
    private function configureViews(): void
    {
        Fortify::loginView(fn () => view('livewire.auth.login'));
        Fortify::verifyEmailView(fn () => view('livewire.auth.verify-email'));
        Fortify::twoFactorChallengeView(fn () => view('livewire.auth.two-factor-challenge'));
        Fortify::confirmPasswordView(fn () => view('livewire.auth.confirm-password'));
        Fortify::registerView(fn () => view('livewire.auth.register'));
        Fortify::resetPasswordView(fn () => view('livewire.auth.reset-password'));
        Fortify::requestPasswordResetLinkView(fn () => view('livewire.auth.forgot-password'));
    }

    /**
     * Configure rate limiting.
     */
    private function configureRateLimiting(): void
    {
        RateLimiter::for('two-factor', function (Request $request) {
            return Limit::perMinute(5)->by($request->session()->get('login.id'));
        });

        RateLimiter::for('login', function (Request $request) {
            return Limit::perMinute(5)->by($request->ip());
        });
    }
}


Output:
Chunk ID: 05c272
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 296
Output:
<x-layouts::auth>

        <x-auth-header :title="__('Forgot password')" :description="__('Enter your email to receive a password reset link')" />

        <x-auth-session-status class="text-center" :status="session('status')" />

        <form method="POST" action="{{ route('password.email') }}" class="flex flex-col gap-6">
            @csrf

            <flux:input
                name="email"
                :label="__('Email Address')"
                type="email"
                required
                autofocus
                placeholder="email@example.com"
            />

            <flux:button variant="primary" type="submit" class="w-full" data-test="email-password-reset-link-button">
                {{ __('Email password reset link') }}
            </flux:button>
        </form>

            {{ __('Or, return to') }}
            <flux:link :href="route('login')" wire:navigate>{{ __('log in') }}</flux:link>

</x-layouts::auth>