command: sed -n '1,260p' tests/Feature/Auth/PasswordResetTest.php && sed -n '1,260p' tests/Feature/Auth/CustomerPasswordResetTest.php

CustomerPasswordResetTest.php

aggregated_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('admin password reset routes render and submit through admin paths', function () {
    Notification::fake();

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

    $this->get(route('admin.password.request'))
        ->assertOk()
        ->assertSee(route('admin.password.email', absolute: false), false);

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

    Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
        $this->get(route('admin.password.reset', $notification->token).'?email='.$user->email)
            ->assertOk()
            ->assertSee(route('admin.password.update', absolute: false), false);

        $this->post(route('admin.password.update'), [
            'token' => $notification->token,
            'email' => $user->email,
            'password' => 'password',
            'password_confirmation' => 'password',
        ])->assertSessionHasNoErrors();

        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;
    });
});
<?php

use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Models\Customer;
use App\Models\Store;
use App\Models\StoreDomain;
use App\Notifications\CustomerResetPassword as CustomerResetPasswordNotification;
use App\Services\CustomerPasswordResetService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    Cache::flush();
});

test('customer password reset pages render for the resolved storefront store', function (): void {
    $store = Store::factory()->create();
    StoreDomain::factory()->create([
        'store_id' => $store->getKey(),
        'hostname' => 'customer-reset.test',
    ]);

    $this->get('http://customer-reset.test/account/forgot-password')
        ->assertSuccessful()
        ->assertSee('Reset password')
        ->assertSee('Send reset link');

    $this->get('http://customer-reset.test/account/reset-password/test-token?email=customer@example.test')
        ->assertSuccessful()
        ->assertSee('Set a new password')
        ->assertSee('Reset password');
});

test('customer reset links are sent generically and scoped to the current store', function (): void {
    Notification::fake();

    $store = Store::factory()->create();
    $otherStore = Store::factory()->create();

    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'customer@example.test',
    ]);

    $otherCustomer = Customer::factory()->create([
        'store_id' => $otherStore->getKey(),
        'email' => 'customer@example.test',
    ]);

    app()->instance('current_store', $store);

    Livewire::test(CustomerForgotPassword::class)
        ->set('email', 'CUSTOMER@example.test')
        ->call('send')
        ->assertHasNoErrors()
        ->assertSee('If an account matches that email, a reset link has been sent.');

    Notification::assertSentTo(
        $customer,
        CustomerResetPasswordNotification::class,
        fn (CustomerResetPasswordNotification $notification): bool => strlen($notification->token) === 64
            && $notification->store->is($store),
    );

    Notification::assertNotSentTo($otherCustomer, CustomerResetPasswordNotification::class);

    $this->assertDatabaseHas('customer_password_reset_tokens', [
        'store_id' => $store->getKey(),
        'email' => $customer->email,
    ]);

    $this->assertDatabaseMissing('customer_password_reset_tokens', [
        'store_id' => $otherStore->getKey(),
        'email' => $otherCustomer->email,
    ]);
});

test('unknown customer reset requests keep the generic response', function (): void {
    Notification::fake();

    $store = Store::factory()->create();

    app()->instance('current_store', $store);

    Livewire::test(CustomerForgotPassword::class)
        ->set('email', 'missing@example.test')
        ->call('send')
        ->assertHasNoErrors()
        ->assertSee('If an account matches that email, a reset link has been sent.');

    Notification::assertNothingSent();

    expect(DB::table('customer_password_reset_tokens')->count())->toBe(0);
});

test('customer password reset updates the password and deletes the token', function (): void {
    Notification::fake();

    $store = Store::factory()->create();
    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'customer@example.test',
        'password' => 'old-password',
    ]);

    app()->instance('current_store', $store);

    app(CustomerPasswordResetService::class)->sendResetLink($store, $customer->email);

    $token = null;

    Notification::assertSentTo(
        $customer,
        CustomerResetPasswordNotification::class,
        function (CustomerResetPasswordNotification $notification) use (&$token): bool {
            $token = $notification->token;

            return true;
        },
    );

    Livewire::test(CustomerResetPassword::class, ['token' => $token])
        ->set('email', $customer->email)
        ->set('password', 'new-password')
        ->set('password_confirmation', 'new-password')
        ->call('resetPassword')
        ->assertHasNoErrors()
        ->assertRedirect(route('account.login', absolute: false));

    expect(Hash::check('new-password', $customer->refresh()->password_hash))->toBeTrue();

    $this->assertDatabaseMissing('customer_password_reset_tokens', [
        'store_id' => $store->getKey(),
        'email' => $customer->email,
    ]);

    app()->instance('current_store', $store);

    expect(Auth::guard('customer')->attempt([
        'email' => $customer->email,
        'password' => 'new-password',
    ]))->toBeTrue();
});

test('customer reset tokens cannot be reused across stores or after expiry', function (): void {
    $store = Store::factory()->create();
    $otherStore = Store::factory()->create();

    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'shared@example.test',
        'password' => 'old-password',
    ]);

    $otherCustomer = Customer::factory()->create([
        'store_id' => $otherStore->getKey(),
        'email' => 'shared@example.test',
        'password' => 'other-password',
    ]);

    DB::table('customer_password_reset_tokens')->insert([
        'store_id' => $store->getKey(),
        'email' => $customer->email,
        'token' => Hash::make('valid-token'),
        'created_at' => now(),
    ]);

    app()->instance('current_store', $otherStore);

    Livewire::test(CustomerResetPassword::class, ['token' => 'valid-token'])
        ->set('email', $otherCustomer->email)
        ->set('password', 'new-password')
        ->set('password_confirmation', 'new-password')
        ->call('resetPassword')
        ->assertHasErrors(['email']);

    expect(Hash::check('other-password', $otherCustomer->refresh()->password_hash))->toBeTrue();

    DB::table('customer_password_reset_tokens')
        ->where('store_id', $store->getKey())
        ->where('email', $customer->email)
        ->update(['created_at' => now()->subMinutes(61)]);

    app()->instance('current_store', $store);

    Livewire::test(CustomerResetPassword::class, ['token' => 'valid-token'])
        ->set('email', $customer->email)
        ->set('password', 'new-password')
        ->set('password_confirmation', 'new-password')
        ->call('resetPassword')
        ->assertHasErrors(['email']);

    expect(Hash::check('old-password', $customer->refresh()->password_hash))->toBeTrue();
});


command: sed -n '1,160p' app/Providers/FortifyServiceProvider.php && sed -n '1,140p' config/fortify.php

FortifyServiceProvider.php

type: read

fortify.php

cmd: sed -n '1,140p' config/fortify.php

aggregated_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());
        });
    }
}
<?php

use Laravel\Fortify\Features;

return [

    /*
    |--------------------------------------------------------------------------
    | Fortify Guard
    |--------------------------------------------------------------------------
    |
    | Here you may specify which authentication guard Fortify will use while
    | authenticating users. This value should correspond with one of your
    | guards that is already present in your "auth" configuration file.
    |
    */

    'guard' => 'web',

    /*
    |--------------------------------------------------------------------------
    | Fortify Password Broker
    |--------------------------------------------------------------------------
    |
    | Here you may specify which password broker Fortify can use when a user
    | is resetting their password. This configured value should match one
    | of your password brokers setup in your "auth" configuration file.
    |
    */

    'passwords' => 'users',

    /*
    |--------------------------------------------------------------------------
    | Username / Email
    |--------------------------------------------------------------------------
    |
    | This value defines which model attribute should be considered as your
    | application's "username" field. Typically, this might be the email
    | address of the users but you are free to change this value here.
    |
    | Out of the box, Fortify expects forgot password and reset password
    | requests to have a field named 'email'. If the application uses
    | another name for the field you may define it below as needed.
    |
    */

    'username' => 'email',

    'email' => 'email',

    /*
    |--------------------------------------------------------------------------
    | Lowercase Usernames
    |--------------------------------------------------------------------------
    |
    | This value defines whether usernames should be lowercased before saving
    | them in the database, as some database system string fields are case
    | sensitive. You may disable this for your application if necessary.
    |
    */

    'lowercase_usernames' => true,

    /*
    |--------------------------------------------------------------------------
    | Home Path
    |--------------------------------------------------------------------------
    |
    | Here you may configure the path where users will get redirected during
    | authentication or password reset when the operations are successful
    | and the user is authenticated. You are free to change this value.
    |
    */

    'home' => '/admin',

    /*
    |--------------------------------------------------------------------------
    | Fortify Routes Prefix / Subdomain
    |--------------------------------------------------------------------------
    |
    | Here you may specify which prefix Fortify will assign to all the routes
    | that it registers with the application. If necessary, you may change
    | subdomain under which all of the Fortify routes will be available.
    |
    */

    'prefix' => '',

    'domain' => null,

    /*
    |--------------------------------------------------------------------------
    | Fortify Routes Middleware
    |--------------------------------------------------------------------------
    |
    | Here you may specify which middleware Fortify will assign to the routes
    | that it registers with the application. If necessary, you may change
    | these middleware but typically this provided default is preferred.
    |
    */

    'middleware' => ['web'],

    /*
    |--------------------------------------------------------------------------
    | Rate Limiting
    |--------------------------------------------------------------------------
    |
    | By default, Fortify will throttle logins to five requests per minute for
    | every email and IP address combination. However, if you would like to
    | specify a custom rate limiter to call then you may specify it here.
    |
    */

    'limiters' => [
        'login' => 'login',
        'two-factor' => 'two-factor',
    ],

    /*
    |--------------------------------------------------------------------------
    | Register View Routes
    |--------------------------------------------------------------------------
    |
    | Here you may specify if the routes returning views should be disabled as
    | you may not need them when building your own application. This may be
    | especially true if you're writing a custom single-page application.
    |
    */

    'views' => true,

    /*
    |--------------------------------------------------------------------------
    | Features
    |--------------------------------------------------------------------------
    |
    | Some of the Fortify features are optional. You may disable the features

status: completed


output:
Chunk ID: fa6ac1
Wall time: 0.1902 seconds
Process exited with code 0
Original token count: 21
Output:

   ERROR  Your application doesn't have any routes matching the given criteria.


output:
Chunk ID: 5f68e9
Wall time: 0.1812 seconds
Process exited with code 0
Original token count: 21
Output:

   ERROR  Your application doesn't have any routes matching the given criteria.


output:
Chunk ID: eb969e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2387
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('admin password reset routes render and submit through admin paths', function () {
    Notification::fake();

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

    $this->get(route('admin.password.request'))
        ->assertOk()
        ->assertSee(route('admin.password.email', absolute: false), false);

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

    Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) {
        $this->get(route('admin.password.reset', $notification->token).'?email='.$user->email)
            ->assertOk()
            ->assertSee(route('admin.password.update', absolute: false), false);

        $this->post(route('admin.password.update'), [
            'token' => $notification->token,
            'email' => $user->email,
            'password' => 'password',
            'password_confirmation' => 'password',
        ])->assertSessionHasNoErrors();

        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;
    });
});
<?php

use App\Livewire\Storefront\Account\Auth\ForgotPassword as CustomerForgotPassword;
use App\Livewire\Storefront\Account\Auth\ResetPassword as CustomerResetPassword;
use App\Models\Customer;
use App\Models\Store;
use App\Models\StoreDomain;
use App\Notifications\CustomerResetPassword as CustomerResetPasswordNotification;
use App\Services\CustomerPasswordResetService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->withoutVite();
    Cache::flush();
});

test('customer password reset pages render for the resolved storefront store', function (): void {
    $store = Store::factory()->create();
    StoreDomain::factory()->create([
        'store_id' => $store->getKey(),
        'hostname' => 'customer-reset.test',
    ]);

    $this->get('http://customer-reset.test/account/forgot-password')
        ->assertSuccessful()
        ->assertSee('Reset password')
        ->assertSee('Send reset link');

    $this->get('http://customer-reset.test/account/reset-password/test-token?email=customer@example.test')
        ->assertSuccessful()
        ->assertSee('Set a new password')
        ->assertSee('Reset password');
});

test('customer reset links are sent generically and scoped to the current store', function (): void {
    Notification::fake();

    $store = Store::factory()->create();
    $otherStore = Store::factory()->create();

    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'customer@example.test',
    ]);

    $otherCustomer = Customer::factory()->create([
        'store_id' => $otherStore->getKey(),
        'email' => 'customer@example.test',
    ]);

    app()->instance('current_store', $store);

    Livewire::test(CustomerForgotPassword::class)
        ->set('email', 'CUSTOMER@example.test')
        ->call('send')
        ->assertHasNoErrors()
        ->assertSee('If an account matches that email, a reset link has been sent.');

    Notification::assertSentTo(
        $customer,
        CustomerResetPasswordNotification::class,
        fn (CustomerResetPasswordNotification $notification): bool => strlen($notification->token) === 64
            && $notification->store->is($store),
    );

    Notification::assertNotSentTo($otherCustomer, CustomerResetPasswordNotification::class);

    $this->assertDatabaseHas('customer_password_reset_tokens', [
        'store_id' => $store->getKey(),
        'email' => $customer->email,
    ]);

    $this->assertDatabaseMissing('customer_password_reset_tokens', [
        'store_id' => $otherStore->getKey(),
        'email' => $otherCustomer->email,
    ]);
});

test('unknown customer reset requests keep the generic response', function (): void {
    Notification::fake();

    $store = Store::factory()->create();

    app()->instance('current_store', $store);

    Livewire::test(CustomerForgotPassword::class)
        ->set('email', 'missing@example.test')
        ->call('send')
        ->assertHasNoErrors()
        ->assertSee('If an account matches that email, a reset link has been sent.');

    Notification::assertNothingSent();

    expect(DB::table('customer_password_reset_tokens')->count())->toBe(0);
});

test('customer password reset updates the password and deletes the token', function (): void {
    Notification::fake();

    $store = Store::factory()->create();
    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'customer@example.test',
        'password' => 'old-password',
    ]);

    app()->instance('current_store', $store);

    app(CustomerPasswordResetService::class)->sendResetLink($store, $customer->email);

    $token = null;

    Notification::assertSentTo(
        $customer,
        CustomerResetPasswordNotification::class,
        function (CustomerResetPasswordNotification $notification) use (&$token): bool {
            $token = $notification->token;

            return true;
        },
    );

    Livewire::test(CustomerResetPassword::class, ['token' => $token])
        ->set('email', $customer->email)
        ->set('password', 'new-password')
        ->set('password_confirmation', 'new-password')
        ->call('resetPassword')
        ->assertHasNoErrors()
        ->assertRedirect(route('account.login', absolute: false));

    expect(Hash::check('new-password', $customer->refresh()->password_hash))->toBeTrue();

    $this->assertDatabaseMissing('customer_password_reset_tokens', [
        'store_id' => $store->getKey(),
        'email' => $customer->email,
    ]);

    app()->instance('current_store', $store);

    expect(Auth::guard('customer')->attempt([
        'email' => $customer->email,
        'password' => 'new-password',
    ]))->toBeTrue();
});

test('customer reset tokens cannot be reused across stores or after expiry', function (): void {
    $store = Store::factory()->create();
    $otherStore = Store::factory()->create();

    $customer = Customer::factory()->create([
        'store_id' => $store->getKey(),
        'email' => 'shared@example.test',
        'password' => 'old-password',
    ]);

    $otherCustomer = Customer::factory()->create([
        'store_id' => $otherStore->getKey(),
        'email' => 'shared@example.test',
        'password' => 'other-password',
    ]);

    DB::table('customer_password_reset_tokens')->insert([
        'store_id' => $store->getKey(),
        'email' => $customer->email,
        'token' => Hash::make('valid-token'),
        'created_at' => now(),
    ]);

    app()->instance('current_store', $otherStore);

    Livewire::test(CustomerResetPassword::class, ['token' => 'valid-token'])
        ->set('email', $otherCustomer->email)
        ->set('password', 'new-password')
        ->set('password_confirmation', 'new-password')
        ->call('resetPassword')
        ->assertHasErrors(['email']);

    expect(Hash::check('other-password', $otherCustomer->refresh()->password_hash))->toBeTrue();

    DB::table('customer_password_reset_tokens')
        ->where('store_id', $store->getKey())
        ->where('email', $customer->email)
        ->update(['created_at' => now()->subMinutes(61)]);

    app()->instance('current_store', $store);

    Livewire::test(CustomerResetPassword::class, ['token' => 'valid-token'])
        ->set('email', $customer->email)
        ->set('password', 'new-password')
        ->set('password_confirmation', 'new-password')
        ->call('resetPassword')
        ->assertHasErrors(['email']);

    expect(Hash::check('old-password', $customer->refresh()->password_hash))->toBeTrue();
});


output:
Chunk ID: a69a71
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1711
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());
        });
    }
}
<?php

use Laravel\Fortify\Features;

return [

    /*
    |--------------------------------------------------------------------------
    | Fortify Guard
    |--------------------------------------------------------------------------
    |
    | Here you may specify which authentication guard Fortify will use while
    | authenticating users. This value should correspond with one of your
    | guards that is already present in your "auth" configuration file.
    |
    */

    'guard' => 'web',

    /*
    |--------------------------------------------------------------------------
    | Fortify Password Broker
    |--------------------------------------------------------------------------
    |
    | Here you may specify which password broker Fortify can use when a user
    | is resetting their password. This configured value should match one
    | of your password brokers setup in your "auth" configuration file.
    |
    */

    'passwords' => 'users',

    /*
    |--------------------------------------------------------------------------
    | Username / Email
    |--------------------------------------------------------------------------
    |
    | This value defines which model attribute should be considered as your
    | application's "username" field. Typically, this might be the email
    | address of the users but you are free to change this value here.
    |
    | Out of the box, Fortify expects forgot password and reset password
    | requests to have a field named 'email'. If the application uses
    | another name for the field you may define it below as needed.
    |
    */

    'username' => 'email',

    'email' => 'email',

    /*
    |--------------------------------------------------------------------------
    | Lowercase Usernames
    |--------------------------------------------------------------------------
    |
    | This value defines whether usernames should be lowercased before saving
    | them in the database, as some database system string fields are case
    | sensitive. You may disable this for your application if necessary.
    |
    */

    'lowercase_usernames' => true,

    /*
    |--------------------------------------------------------------------------
    | Home Path
    |--------------------------------------------------------------------------
    |
    | Here you may configure the path where users will get redirected during
    | authentication or password reset when the operations are successful
    | and the user is authenticated. You are free to change this value.
    |
    */

    'home' => '/admin',

    /*
    |--------------------------------------------------------------------------
    | Fortify Routes Prefix / Subdomain
    |--------------------------------------------------------------------------
    |
    | Here you may specify which prefix Fortify will assign to all the routes
    | that it registers with the application. If necessary, you may change
    | subdomain under which all of the Fortify routes will be available.
    |
    */

    'prefix' => '',

    'domain' => null,

    /*
    |--------------------------------------------------------------------------
    | Fortify Routes Middleware
    |--------------------------------------------------------------------------
    |
    | Here you may specify which middleware Fortify will assign to the routes
    | that it registers with the application. If necessary, you may change
    | these middleware but typically this provided default is preferred.
    |
    */

    'middleware' => ['web'],

    /*
    |--------------------------------------------------------------------------
    | Rate Limiting
    |--------------------------------------------------------------------------
    |
    | By default, Fortify will throttle logins to five requests per minute for
    | every email and IP address combination. However, if you would like to
    | specify a custom rate limiter to call then you may specify it here.
    |
    */

    'limiters' => [
        'login' => 'login',
        'two-factor' => 'two-factor',
    ],

    /*
    |--------------------------------------------------------------------------
    | Register View Routes
    |--------------------------------------------------------------------------
    |
    | Here you may specify if the routes returning views should be disabled as
    | you may not need them when building your own application. This may be
    | especially true if you're writing a custom single-page application.
    |
    */

    'views' => true,

    /*
    |--------------------------------------------------------------------------
    | Features
    |--------------------------------------------------------------------------
    |
    | Some of the Fortify features are optional. You may disable the features


command: php artisan route:list --path=reset-password --except-vendor

type: unknown

aggregated_output: ERROR  Your application doesn't have any routes matching the given criteria.


command: php artisan route:list --path=forgot-password --except-vendor


cmd: sed -n '140,280p' config/fortify.php && php artisan route:list --name=password --except-vendor


output:
Chunk ID: bd1066
Wall time: 0.0571 seconds
Process exited with code 0
Original token count: 155
Output:
    | Some of the Fortify features are optional. You may disable the features
    | by removing them from this array. You're free to only remove some of
    | these features, or you can even remove all of these if you need to.
    |
    */

    'features' => [
        Features::registration(),
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::twoFactorAuthentication([
            'confirm' => true,
            'confirmPassword' => true,
            // 'window' => 0
        ]),
    ],

];

   ERROR  Your application doesn't have any routes matching the given criteria.


command: sed -n '140,280p' config/fortify.php && php artisan route:list --name=password --except-vendor


cmd: php artisan route:list --path=forgot-password && php artisan route:list --path=reset-password && php artisan route:list --name=password


output:
Chunk ID: 12ed32
Wall time: 0.3141 seconds
Process exited with code 0
Original token count: 567
Output:

  GET|HEAD   account/forgot-password account.password.request › Livewire\Mech…
  GET|HEAD   admin/forgot-password admin.password.request › Laravel\Fortify  …
  POST       admin/forgot-password admin.password.email › Laravel\Fortify › P…
  GET|HEAD   forgot-password password.request › Laravel\Fortify › PasswordRes…
  POST       forgot-password password.email › Laravel\Fortify › PasswordReset…

                                                            Showing [5] routes

  GET|HEAD   account/reset-password/{token} account.password.reset › Livewire…
  POST       admin/reset-password admin.password.update › Laravel\Fortify › N…
  GET|HEAD   admin/reset-password/{token} admin.password.reset › Laravel\Fort…
  POST       reset-password password.update › Laravel\Fortify › NewPasswordCo…
  GET|HEAD   reset-password/{token} password.reset › Laravel\Fortify › NewPas…

                                                            Showing [5] routes

  GET|HEAD   account/forgot-password account.password.request › Livewire\Mech…
  GET|HEAD   account/reset-password/{token} account.password.reset › Livewire…
  GET|HEAD   admin/forgot-password admin.password.request › Laravel\Fortify  …
  POST       admin/forgot-password admin.password.email › Laravel\Fortify › P…
  POST       admin/reset-password admin.password.update › Laravel\Fortify › N…
  GET|HEAD   admin/reset-password/{token} admin.password.reset › Laravel\Fort…
  GET|HEAD   forgot-password password.request › Laravel\Fortify › PasswordRes…
  POST       forgot-password password.email › Laravel\Fortify › PasswordReset…
  POST       reset-password password.update › Laravel\Fortify › NewPasswordCo…
  GET|HEAD   reset-password/{token} password.reset › Laravel\Fortify › NewPas…
  GET|HEAD   settings/password user-password.edit › Livewire\Mechanisms › Liv…
  GET|HEAD   user/confirm-password password.confirm › Laravel\Fortify › Confi…
  POST       user/confirm-password password.confirm.store › Laravel\Fortify  …
  GET|HEAD   user/confirmed-password-status password.confirmation › Laravel\F…

                                                           Showing [14] routes


command: php artisan route:list --path=forgot-password && php artisan route:list --path=reset-password && php artisan route:list --name=password

aggregated_output:
  GET|HEAD   account/forgot-password account.password.request › Livewire\Mech…
  GET|HEAD   admin/forgot-password admin.password.request › Laravel\Fortify  …
  POST       admin/forgot-password admin.password.email › Laravel\Fortify › P…
  GET|HEAD   forgot-password password.request › Laravel\Fortify › PasswordRes…
  POST       forgot-password password.email › Laravel\Fortify › PasswordReset…

                                                            Showing [5] routes

  GET|HEAD   account/reset-password/{token} account.password.reset › Livewire…
  POST       admin/reset-password admin.password.update › Laravel\Fortify › N…
  GET|HEAD   admin/reset-password/{token} admin.password.reset › Laravel\Fort…
  POST       reset-password password.update › Laravel\Fortify › NewPasswordCo…
  GET|HEAD   reset-password/{token} password.reset › Laravel\Fortify › NewPas…

                                                            Showing [5] routes

  GET|HEAD   account/forgot-password account.password.request › Livewire\Mech…
  GET|HEAD   account/reset-password/{token} account.password.reset › Livewire…
  GET|HEAD   admin/forgot-password admin.password.request › Laravel\Fortify  …
  POST       admin/forgot-password admin.password.email › Laravel\Fortify › P…
  POST       admin/reset-password admin.password.update › Laravel\Fortify › N…
  GET|HEAD   admin/reset-password/{token} admin.password.reset › Laravel\Fort…
  GET|HEAD   forgot-password password.request › Laravel\Fortify › PasswordRes…
  POST       forgot-password password.email › Laravel\Fortify › PasswordReset…
  POST       reset-password password.update › Laravel\Fortify › NewPasswordCo…
  GET|HEAD   reset-password/{token} password.reset › Laravel\Fortify › NewPas…
  GET|HEAD   settings/password user-password.edit › Livewire\Mechanisms › Liv…
  GET|HEAD   user/confirm-password password.confirm › Laravel\Fortify › Confi…
  POST       user/confirm-password password.confirm.store › Laravel\Fortify  …
  GET|HEAD   user/confirmed-password-status password.confirmation › Laravel\F…

                                                           Showing [14] routes


cmd: rg -n "forgot-password|reset-password|password reset|Password reset|password\.request|account/forgot|account/reset|/forgot|/reset" specs/02-API-ROUTES.md specs/04-STOREFRONT-UI.md specs/06-AUTH-AND-SECURITY.md specs/09-IMPLEMENTATION-ROADMAP.md


command: rg -n "forgot-password|reset-password|password reset|Password reset|password\.request|account/forgot|account/reset|/forgot|/reset" specs/02-API-ROUTES.md specs/04-STOREFRONT-UI.md specs/06-AUTH-AND-SECURITY.md specs/09-IMPLEMENTATION-ROADMAP.md

type: search

cmd: rg -n "forgot-password|reset-password|password reset|Password reset|password\\.request|account/forgot|account/reset|/forgot|/reset" specs/02-API-ROUTES.md specs/04-STOREFRONT-UI.md specs/06-AUTH-AND-SECURITY.md specs/09-IMPLEMENTATION-ROADMAP.md

query: forgot-password|reset-password|password reset|Password reset|password\.request|account/forgot|account/reset|/forgot|/reset

aggregated_output:
specs/04-STOREFRONT-UI.md:1921:| Password reset sent | "We have emailed your password reset link!" | success |
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: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:199:- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
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')` |


output:
Chunk ID: 9b1d56
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 519
Output:
specs/04-STOREFRONT-UI.md:1921:| Password reset sent | "We have emailed your password reset link!" | success |
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: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:199:- Routes: `GET /forgot-password`, `POST /forgot-password`, `GET /reset-password/{token}`, `POST /reset-password`.
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')` |


cmd: sed -n '60,220p' specs/06-AUTH-AND-SECURITY.md && sed -n '1,40p' specs/02-API-ROUTES.md && sed -n '160,190p' specs/02-API-ROUTES.md && sed -n '1800,1940p' specs/04-STOREFRONT-UI.md


command: sed -n '60,220p' specs/06-AUTH-AND-SECURITY.md && sed -n '1,40p' specs/02-API-ROUTES.md && sed -n '160,190p' specs/02-API-ROUTES.md && sed -n '1800,1940p' specs/04-STOREFRONT-UI.md

cmd: sed -n '1,40p' specs/02-API-ROUTES.md

aggregated_output:
- An optional "Remember me" checkbox on the login form.
- When enabled, Laravel sets a long-lived remember token cookie.
- The `remember_token` column on the `users` table stores the hashed token.
- Cookie name follows Laravel default convention: `remember_web_{hash}`.

#### Admin Password Reset Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant App as Application
    participant DB as Database
    participant Mail as Mail Service

    B->>App: GET /admin/forgot-password
    App-->>B: Render forgot-password form
    B->>App: POST /admin/forgot-password (email)
    App->>DB: Look up user by email
    alt User exists
        App->>DB: Store reset token in password_reset_tokens table
        App->>Mail: Send reset link email
    end
    App-->>B: "If that email exists, we sent a reset link." (generic)
    B->>App: GET /admin/reset-password/{token}
    App-->>B: Render reset-password form
    B->>App: POST /admin/reset-password (token, email, password, password_confirmation)
    App->>DB: Validate token (exists, not expired, matches email)
    alt Valid token
        App->>DB: Update user password (bcrypt hash)
        App->>DB: Delete used token
        App-->>B: Redirect to /admin/login with success flash
    else Invalid/expired token
        App-->>B: Error message
    end
```

**Requirements:**

- Uses Laravel's built-in password broker (`users` broker).
- Reset tokens stored in the `password_reset_tokens` table.
- Token expiration: 60 minutes (configurable in `config/auth.php` under `passwords.users.expire`).
- Throttle: one reset email per 60 seconds per email address.
- The "forgot password" response must be generic (do not reveal whether email exists).

#### Admin Logout

- Route: `POST /admin/logout` (via Livewire action or standard route).
- Steps: invalidate the entire session, regenerate the CSRF token, redirect to `/admin/login`.
- The response must not cache.

#### Session Lifetime

| Setting | Value |
|---------|-------|
| Lifetime | 120 minutes (configurable via `SESSION_LIFETIME` env var) |
| Expire on close | No (session persists until lifetime expires) |

---

### 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 `…2904 chars truncated…orm pattern defined in Spec 03.

#### Dashboard

| Method | Path | Description | Component |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
flowchart TD
    A[Home Page] --> B{Browse}
    B --> C[Collection Page]
    B --> D[Search Modal]
    D --> E[Search Results Page]
    C --> F[Product Page]
    E --> F
    F --> G{Select Variant}
    G --> H[Set Quantity]
    H --> I[Add to Cart]
    I --> J[Cart Drawer Opens]
    J --> K{Continue?}
    K -->|Continue Shopping| B
    K -->|Apply Discount| L[Enter Discount Code]
    L --> J
    K -->|Checkout| M[Checkout Page]
    M --> N[Step 1: Contact Info]
    N --> O[Step 2: Shipping Address]
    O --> P[Step 3: Shipping Method]
    P --> Q[Step 4: Payment Method & Pay]
    Q --> S[Order Confirmation]
    S --> T{Next?}
    T -->|Continue Shopping| A
    T -->|View Order| U[Account Order Detail]
```

### Account Management Flow

```mermaid
flowchart TD
    A[Login Page] --> B{Has Account?}
    B -->|No| C[Register Page]
    C --> D[Account Dashboard]
    B -->|Yes| D
    D --> E{Action}
    E --> F[Order History]
    F --> G[Order Detail]
    G --> H[Track Shipment]
    E --> I[Address Book]
    I --> J{Action}
    J --> K[Add Address]
    J --> L[Edit Address]
    J --> M[Delete Address]
    J --> N[Set Default Address]
    E --> O[Log Out]
```

### Cart Management Flow

```mermaid
flowchart TD
    A[Add to Cart] --> B[Cart Drawer Opens]
    B --> C{Action}
    C --> D[Update Quantity]
    D --> B
    C --> E[Remove Item]
    E --> F{Cart Empty?}
    F -->|Yes| G[Empty Cart State]
    G --> H[Continue Shopping]
    F -->|No| B
    C --> I[Apply Discount Code]
    I --> J{Valid?}
    J -->|Yes| K[Discount Applied]
    K --> B
    J -->|No| L[Error Message]
    L --> B
    C --> M[Proceed to Checkout]
    M --> N[Checkout Page]
    C --> O[View Full Cart Page]
    O --> N
```

### Component State Diagram

```mermaid
stateDiagram-v2
    [*] --> Empty: Cart initialized

    state "Cart Drawer" as CD {
        Empty --> HasItems: Add item
        HasItems --> HasItems: Update quantity
        HasItems --> HasItems: Apply discount
        HasItems --> Empty: Remove last item
        HasItems --> Checkout: Proceed to checkout
    }

    state "Product Page" as PP {
        NoVariant --> VariantSelected: Select options
        VariantSelected --> InStock: Stock > 0
        VariantSelected --> OutOfStock: Stock = 0
        InStock --> Adding: Click add to cart
        Adding --> InStock: Complete
        OutOfStock --> SoldOut: Deny policy
        OutOfStock --> Backorder: Continue policy
    }

    state "Checkout" as CO {
        Contact --> ShippingAddress: Complete step 1
        ShippingAddress --> ShippingMethod: Complete step 2
        ShippingMethod --> Payment: Complete step 3
        Payment --> Processing: Submit payment
        Processing --> Confirmation: Payment success
        Processing --> Payment: Payment failure
    }
```

---

## 21. Flash / Toast Messages

| Action | Message | Type |
|--------|---------|------|
| Item added to cart | "Added to cart" | success |
| Item removed from cart | "Item removed" | success |
| Cart is empty | "Your cart is empty" | info |
| Discount applied | "Discount applied" | success |
| Invalid discount code | "Invalid discount code" | error |
| Out of stock | "This product is currently out of stock" | warning |
| Registration success | "Account created successfully" | success |
| Login success | (redirect, no message) | - |
| Login failed | "Invalid credentials" | error |
| Password reset sent | "We have emailed your password reset link!" | success |
| Order placed | "Thank you for your order!" | success |


output:
Chunk ID: 7dab79
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3226
Output:

- An optional "Remember me" checkbox on the login form.
- When enabled, Laravel sets a long-lived remember token cookie.
- The `remember_token` column on the `users` table stores the hashed token.
- Cookie name follows Laravel default convention: `remember_web_{hash}`.

#### Admin Password Reset Flow

```mermaid
sequenceDiagram
    participant B as Browser
    participant App as Application
    participant DB as Database
    participant Mail as Mail Service

    B->>App: GET /admin/forgot-password
    App-->>B: Render forgot-password form
    B->>App: POST /admin/forgot-password (email)
    App->>DB: Look up user by email
    alt User exists
        App->>DB: Store reset token in password_reset_tokens table
        App->>Mail: Send reset link email
    end
    App-->>B: "If that email exists, we sent a reset link." (generic)
    B->>App: GET /admin/reset-password/{token}
    App-->>B: Render reset-password form
    B->>App: POST /admin/reset-password (token, email, password, password_confirmation)
    App->>DB: Validate token (exists, not expired, matches email)
    alt Valid token
        App->>DB: Update user password (bcrypt hash)
        App->>DB: Delete used token
        App-->>B: Redirect to /admin/login with success flash
    else Invalid/expired token
        App-->>B: Error message
    end
```

**Requirements:**

- Uses Laravel's built-in password broker (`users` broker).
- Reset tokens stored in the `password_reset_tokens` table.
- Token expiration: 60 minutes (configurable in `config/auth.php` under `passwords.users.expire`).
- Throttle: one reset email per 60 seconds per email address.
- The "forgot password" response must be generic (do not reveal whether email exists).

#### Admin Logout

- Route: `POST /admin/logout` (via Livewire action or standard route).
- Steps: invalidate the entire session, regenerate the CSRF token, redirect to `/admin/login`.
- The response must not cache.

#### Session Lifetime

| Setting | Value |
|---------|-------|
| Lifetime | 120 minutes (configurable via `SESSION_LIFETIME` env var) |
| Expire on close | No (session persists until lifetime expires) |

---

### 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` |
| Provider | `users` (admin users only) |
| Token type | Personal access tokens |
| Token prefix | `shop_` |
| Hashing | SHA-256 |
| Default expiration | 1 year |
| Storage | `personal_access_tokens` table |

#### Token Lifecycle

```mermaid
# API and Routing Specification

This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.

All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.

#### Dashboard

| Method | Path | Description | Component |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
flowchart TD
    A[Home Page] --> B{Browse}
    B --> C[Collection Page]
    B --> D[Search Modal]
    D --> E[Search Results Page]
    C --> F[Product Page]
    E --> F
    F --> G{Select Variant}
    G --> H[Set Quantity]
    H --> I[Add to Cart]
    I --> J[Cart Drawer Opens]
    J --> K{Continue?}
    K -->|Continue Shopping| B
    K -->|Apply Discount| L[Enter Discount Code]
    L --> J
    K -->|Checkout| M[Checkout Page]
    M --> N[Step 1: Contact Info]
    N --> O[Step 2: Shipping Address]
    O --> P[Step 3: Shipping Method]
    P --> Q[Step 4: Payment Method & Pay]
    Q --> S[Order Confirmation]
    S --> T{Next?}
    T -->|Continue Shopping| A
    T -->|View Order| U[Account Order Detail]
```

### Account Management Flow

```mermaid
flowchart TD
    A[Login Page] --> B{Has Account?}
    B -->|No| C[Register Page]
    C --> D[Account Dashboard]
    B -->|Yes| D
    D --> E{Action}
    E --> F[Order History]
    F --> G[Order Detail]
    G --> H[Track Shipment]
    E --> I[Address Book]
    I --> J{Action}
    J --> K[Add Address]
    J --> L[Edit Address]
    J --> M[Delete Address]
    J --> N[Set Default Address]
    E --> O[Log Out]
```

### Cart Management Flow

```mermaid
flowchart TD
    A[Add to Cart] --> B[Cart Drawer Opens]
    B --> C{Action}
    C --> D[Update Quantity]
    D --> B
    C --> E[Remove Item]
    E --> F{Cart Empty?}
    F -->|Yes| G[Empty Cart State]
    G --> H[Continue Shopping]
    F -->|No| B
    C --> I[Apply Discount Code]
    I --> J{Valid?}
    J -->|Yes| K[Discount Applied]
    K --> B
    J -->|No| L[Error Message]
    L --> B
    C --> M[Proceed to Checkout]
    M --> N[Checkout Page]
    C --> O[View Full Cart Page]
    O --> N
```

### Component State Diagram

```mermaid
stateDiagram-v2
    [*] --> Empty: Cart initialized

    state "Cart Drawer" as CD {
        Empty --> HasItems: Add item
        HasItems --> HasItems: Update quantity
        HasItems --> HasItems: Apply discount
        HasItems --> Empty: Remove last item
        HasItems --> Checkout: Proceed to checkout
    }

    state "Product Page" as PP {
        NoVariant --> VariantSelected: Select options
        VariantSelected --> InStock: Stock > 0
        VariantSelected --> OutOfStock: Stock = 0
        InStock --> Adding: Click add to cart
        Adding --> InStock: Complete
        OutOfStock --> SoldOut: Deny policy
        OutOfStock --> Backorder: Continue policy
    }

    state "Checkout" as CO {
        Contact --> ShippingAddress: Complete step 1
        ShippingAddress --> ShippingMethod: Complete step 2
        ShippingMethod --> Payment: Complete step 3
        Payment --> Processing: Submit payment
        Processing --> Confirmation: Payment success
        Processing --> Payment: Payment failure
    }
```

---

## 21. Flash / Toast Messages

| Action | Message | Type |
|--------|---------|------|
| Item added to cart | "Added to cart" | success |
| Item removed from cart | "Item removed" | success |
| Cart is empty | "Your cart is empty" | info |
| Discount applied | "Discount applied" | success |
| Invalid discount code | "Invalid discount code" | error |
| Out of stock | "This product is currently out of stock" | warning |
| Registration success | "Account created successfully" | success |
| Login success | (redirect, no message) | - |
| Login failed | "Invalid credentials" | error |
| Password reset sent | "We have emailed your password reset link!" | success |
| Order placed | "Thank you for your order!" | success |


cmd: php artisan route:list --path=login && php artisan route:list --path=register && php artisan route:list --name=login --except-vendor && php artisan route:list --name=login


output:
Chunk ID: eab6bf
Wall time: 0.5904 seconds
Process exited with code 0
Original token count: 359
Output:

  GET|HEAD   account/login account.login › Livewire\Mechanisms › LivewirePage…
  GET|HEAD   admin/login admin.login › Livewire\Mechanisms › LivewirePageCont…
  GET|HEAD   login login › Laravel\Fortify › AuthenticatedSessionController@c…
  POST       login login.store › Laravel\Fortify › AuthenticatedSessionContro…

                                                            Showing [4] routes

  GET|HEAD   account/register account.register › Livewire\Mechanisms › Livewi…
  GET|HEAD   register register › Laravel\Fortify › RegisteredUserController@c…
  POST       register register.store › Laravel\Fortify › RegisteredUserContro…

                                                            Showing [3] routes

   ERROR  Your application doesn't have any routes matching the given criteria.

  GET|HEAD   account/login account.login › Livewire\Mechanisms › LivewirePage…
  GET|HEAD   admin/login admin.login › Livewire\Mechanisms › LivewirePageCont…
  GET|HEAD   login login › Laravel\Fortify › AuthenticatedSessionController@c…
  POST       login login.store › Laravel\Fortify › AuthenticatedSessionContro…
  GET|HEAD   two-factor-challenge two-factor.login › Laravel\Fortify › TwoFac…
  POST       two-factor-challenge two-factor.login.store › Laravel\Fortify › …

                                                            Showing [6] routes


command: php artisan route:list --path=login && php artisan route:list --path=register && php artisan route:list --name=login --except-vendor && php artisan route:list --name=login

aggregated_output:
  GET|HEAD   account/login account.login › Livewire\Mechanisms › LivewirePage…
  GET|HEAD   admin/login admin.login › Livewire\Mechanisms › LivewirePageCont…
  GET|HEAD   login login › Laravel\Fortify › AuthenticatedSessionController@c…
  POST       login login.store › Laravel\Fortify › AuthenticatedSessionContro…

                                                            Showing [4] routes

  GET|HEAD   account/register account.register › Livewire\Mechanisms › Livewi…
  GET|HEAD   register register › Laravel\Fortify › RegisteredUserController@c…
  POST       register register.store › Laravel\Fortify › RegisteredUserContro…

                                                            Showing [3] routes

   ERROR  Your application doesn't have any routes matching the given criteria.

  GET|HEAD   account/login account.login › Livewire\Mechanisms › LivewirePage…
  GET|HEAD   admin/login admin.login › Livewire\Mechanisms › LivewirePageCont…
  GET|HEAD   login login › Laravel\Fortify › AuthenticatedSessionController@c…
  POST       login login.store › Laravel\Fortify › AuthenticatedSessionContro…
  GET|HEAD   two-factor-challenge two-factor.login › Laravel\Fortify › TwoFac…
  POST       two-factor-challenge two-factor.login.store › Laravel\Fortify › …

                                                            Showing [6] routes


cmd: rg -n "function boot|loadRoutes|booted|registerRoutes|reset-password|forgot-password" vendor/laravel/fortify/src/FortifyServiceProvider.php vendor/laravel/fortify/routes/routes.php


command: rg -n "function boot|loadRoutes|booted|registerRoutes|reset-password|forgot-password" vendor/laravel/fortify/src/FortifyServiceProvider.php vendor/laravel/fortify/routes/routes.php

type: search

cmd: rg -n 'function boot|loadRoutes|booted|registerRoutes|reset-password|forgot-password' vendor/laravel/fortify/src/FortifyServiceProvider.php vendor/laravel/fortify/routes/routes.php

query: function boot|loadRoutes|booted|registerRoutes|reset-password|forgot-password

aggregated_output:
vendor/laravel/fortify/routes/routes.php:51:            Route::get(RoutePath::for('password.request', '/forgot-password'), [PasswordResetLinkController::class, 'create'])
vendor/laravel/fortify/routes/routes.php:55:            Route::get(RoutePath::for('password.reset', '/reset-password/{token}'), [NewPasswordController::class, 'create'])
vendor/laravel/fortify/routes/routes.php:60:        Route::post(RoutePath::for('password.email', '/forgot-password'), [PasswordResetLinkController::class, 'store'])
vendor/laravel/fortify/routes/routes.php:64:        Route::post(RoutePath::for('password.update', '/reset-password'), [NewPasswordController::class, 'store'])
vendor/laravel/fortify/src/FortifyServiceProvider.php:118:    public function boot()
vendor/laravel/fortify/src/FortifyServiceProvider.php:167:                $this->loadRoutesFrom(__DIR__.'/../routes/routes.php');


output:
Chunk ID: b7f061
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 220
Output:
vendor/laravel/fortify/routes/routes.php:51:            Route::get(RoutePath::for('password.request', '/forgot-password'), [PasswordResetLinkController::class, 'create'])
vendor/laravel/fortify/routes/routes.php:55:            Route::get(RoutePath::for('password.reset', '/reset-password/{token}'), [NewPasswordController::class, 'create'])
vendor/laravel/fortify/routes/routes.php:60:        Route::post(RoutePath::for('password.email', '/forgot-password'), [PasswordResetLinkController::class, 'store'])
vendor/laravel/fortify/routes/routes.php:64:        Route::post(RoutePath::for('password.update', '/reset-password'), [NewPasswordController::class, 'store'])
vendor/laravel/fortify/src/FortifyServiceProvider.php:118:    public function boot()
vendor/laravel/fortify/src/FortifyServiceProvider.php:167:                $this->loadRoutesFrom(__DIR__.'/../routes/routes.php');


cmd: sed -n '100,190p' vendor/laravel/fortify/src/FortifyServiceProvider.php && sed -n '40,75p' vendor/laravel/fortify/routes/routes.php && sed -n '1,120p' vendor/laravel/fortify/src/RoutePath.php


command: sed -n '100,190p' vendor/laravel/fortify/src/FortifyServiceProvider.php && sed -n '40,75p' vendor/laravel/fortify/routes/routes.php && sed -n '1,120p' vendor/laravel/fortify/src/RoutePath.php

RoutePath.php

aggregated_output:
        $this->app->singleton(PasswordUpdateResponseContract::class, PasswordUpdateResponse::class);
        $this->app->singleton(ProfileInformationUpdatedResponseContract::class, ProfileInformationUpdatedResponse::class);
        $this->app->singleton(RecoveryCodesGeneratedResponseContract::class, RecoveryCodesGeneratedResponse::class);
        $this->app->singleton(RegisterResponseContract::class, RegisterResponse::class);
        $this->app->singleton(EmailVerificationNotificationSentResponseContract::class, EmailVerificationNotificationSentResponse::class);
        $this->app->singleton(SuccessfulPasswordResetLinkRequestResponseContract::class, SuccessfulPasswordResetLinkRequestResponse::class);
        $this->app->singleton(TwoFactorConfirmedResponseContract::class, TwoFactorConfirmedResponse::class);
        $this->app->singleton(TwoFactorDisabledResponseContract::class, TwoFactorDisabledResponse::class);
        $this->app->singleton(TwoFactorEnabledResponseContract::class, TwoFactorEnabledResponse::class);
        $this->app->singleton(TwoFactorLoginResponseContract::class, TwoFactorLoginResponse::class);
        $this->app->singleton(VerifyEmailResponseContract::class, VerifyEmailResponse::class);
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->configurePublishing();
        $this->configureRoutes();
        $this->registerCommands();
    }

    /**
     * Configure the publishable resources offered by the package.
     *
     * @return void
     */
    protected function configurePublishing()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../stubs/fortify.php' => config_path('fortify.php'),
            ], 'fortify-config');

            $this->publishes([
                __DIR__.'/../stubs/CreateNewUser.php' => app_path('Actions/Fortify/CreateNewUser.php'),
                __DIR__.'/../stubs/FortifyServiceProvider.php' => app_path('Providers/FortifyServiceProvider.php'),
                __DIR__.'/../stubs/PasswordValidationRules.php' => app_path('Actions/Fortify/PasswordValidationRules.php'),
                __DIR__.'/../stubs/ResetUserPassword.php' => app_path('Actions/Fortify/ResetUserPassword.php'),
                __DIR__.'/../stubs/UpdateUserProfileInformation.php' => app_path('Actions/Fortify/UpdateUserProfileInformation.php'),
                __DIR__.'/../stubs/UpdateUserPassword.php' => app_path('Actions/Fortify/UpdateUserPassword.php'),
            ], 'fortify-support');

            $method = method_exists($this, 'publishesMigrations') ? 'publishesMigrations' : 'publishes';

            $this->{$method}([
                __DIR__.'/../database/migrations' => database_path('migrations'),
            ], 'fortify-migrations');
        }
    }

    /**
     * Configure the routes offered by the application.
     *
     * @return void
     */
    protected function configureRoutes()
    {
        if (Fortify::$registersRoutes) {
            Route::group([
                'namespace' => 'Laravel\Fortify\Http\Controllers',
                'domain' => config('fortify.domain', null),
                'prefix' => config('fortify.prefix'),
            ], function () {
                $this->loadRoutesFrom(__DIR__.'/../routes/routes.php');
            });
        }
    }

    /**
     * Register the package's commands.
     */
    protected function registerCommands(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                Console\InstallCommand::class,
            ]);
        }
    }
}
            'guest:'.config('fortify.guard'),
            $limiter ? 'throttle:'.$limiter : null,
        ]))->name('login.store');

    Route::post(RoutePath::for('logout', '/logout'), [AuthenticatedSessionController::class, 'destroy'])
        ->middleware([config('fortify.auth_middleware', 'auth').':'.config('fortify.guard')])
        ->name('logout');

    // Password Reset...
    if (Features::enabled(Features::resetPasswords())) {
        if ($enableViews) {
            Route::get(RoutePath::for('password.request', '/forgot-password'), [PasswordResetLinkController::class, 'create'])
                ->middleware(['guest:'.config('fortify.guard')])
                ->name('password.request');

            Route::get(RoutePath::for('password.reset', '/reset-password/{token}'), [NewPasswordController::class, 'create'])
                ->middleware(['guest:'.config('fortify.guard')])
                ->name('password.reset');
        }

        Route::post(RoutePath::for('password.email', '/forgot-password'), [PasswordResetLinkController::class, 'store'])
            ->middleware(['guest:'.config('fortify.guard')])
            ->name('password.email');

        Route::post(RoutePath::for('password.update', '/reset-password'), [NewPasswordController::class, 'store'])
            ->middleware(['guest:'.config('fortify.guard')])
            ->name('password.update');
    }

    // Registration...
    if (Features::enabled(Features::registration())) {
        if ($enableViews) {
            Route::get(RoutePath::for('register', '/register'), [RegisteredUserController::class, 'create'])
                ->middleware(['guest:'.config('fortify.guard')])
                ->name('register');
        }
<?php

namespace Laravel\Fortify;

class RoutePath
{
    /**
     * Get the route path for the given route name.
     *
     * @param  string  $routeName
     * @param  string  $default
     * @return string
     */
    public static function for(string $routeName, string $default)
    {
        return config('fortify.paths.'.$routeName) ?? $default;
    }
}


output:
Chunk ID: 28968d
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1435
Output:
        $this->app->singleton(PasswordUpdateResponseContract::class, PasswordUpdateResponse::class);
        $this->app->singleton(ProfileInformationUpdatedResponseContract::class, ProfileInformationUpdatedResponse::class);
        $this->app->singleton(RecoveryCodesGeneratedResponseContract::class, RecoveryCodesGeneratedResponse::class);
        $this->app->singleton(RegisterResponseContract::class, RegisterResponse::class);
        $this->app->singleton(EmailVerificationNotificationSentResponseContract::class, EmailVerificationNotificationSentResponse::class);
        $this->app->singleton(SuccessfulPasswordResetLinkRequestResponseContract::class, SuccessfulPasswordResetLinkRequestResponse::class);
        $this->app->singleton(TwoFactorConfirmedResponseContract::class, TwoFactorConfirmedResponse::class);
        $this->app->singleton(TwoFactorDisabledResponseContract::class, TwoFactorDisabledResponse::class);
        $this->app->singleton(TwoFactorEnabledResponseContract::class, TwoFactorEnabledResponse::class);
        $this->app->singleton(TwoFactorLoginResponseContract::class, TwoFactorLoginResponse::class);
        $this->app->singleton(VerifyEmailResponseContract::class, VerifyEmailResponse::class);
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        $this->configurePublishing();
        $this->configureRoutes();
        $this->registerCommands();
    }

    /**
     * Configure the publishable resources offered by the package.
     *
     * @return void
     */
    protected function configurePublishing()
    {
        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__.'/../stubs/fortify.php' => config_path('fortify.php'),
            ], 'fortify-config');

            $this->publishes([
                __DIR__.'/../stubs/CreateNewUser.php' => app_path('Actions/Fortify/CreateNewUser.php'),
                __DIR__.'/../stubs/FortifyServiceProvider.php' => app_path('Providers/FortifyServiceProvider.php'),
                __DIR__.'/../stubs/PasswordValidationRules.php' => app_path('Actions/Fortify/PasswordValidationRules.php'),
                __DIR__.'/../stubs/ResetUserPassword.php' => app_path('Actions/Fortify/ResetUserPassword.php'),
                __DIR__.'/../stubs/UpdateUserProfileInformation.php' => app_path('Actions/Fortify/UpdateUserProfileInformation.php'),
                __DIR__.'/../stubs/UpdateUserPassword.php' => app_path('Actions/Fortify/UpdateUserPassword.php'),
            ], 'fortify-support');

            $method = method_exists($this, 'publishesMigrations') ? 'publishesMigrations' : 'publishes';

            $this->{$method}([
                __DIR__.'/../database/migrations' => database_path('migrations'),
            ], 'fortify-migrations');
        }
    }

    /**
     * Configure the routes offered by the application.
     *
     * @return void
     */
    protected function configureRoutes()
    {
        if (Fortify::$registersRoutes) {
            Route::group([
                'namespace' => 'Laravel\Fortify\Http\Controllers',
                'domain' => config('fortify.domain', null),
                'prefix' => config('fortify.prefix'),
            ], function () {
                $this->loadRoutesFrom(__DIR__.'/../routes/routes.php');
            });
        }
    }

    /**
     * Register the package's commands.
     */
    protected function registerCommands(): void
    {
        if ($this->app->runningInConsole()) {
            $this->commands([
                Console\InstallCommand::class,
            ]);
        }
    }
}
            'guest:'.config('fortify.guard'),
            $limiter ? 'throttle:'.$limiter : null,
        ]))->name('login.store');

    Route::post(RoutePath::for('logout', '/logout'), [AuthenticatedSessionController::class, 'destroy'])
        ->middleware([config('fortify.auth_middleware', 'auth').':'.config('fortify.guard')])
        ->name('logout');

    // Password Reset...
    if (Features::enabled(Features::resetPasswords())) {
        if ($enableViews) {
            Route::get(RoutePath::for('password.request', '/forgot-password'), [PasswordResetLinkController::class, 'create'])
                ->middleware(['guest:'.config('fortify.guard')])
                ->name('password.request');

            Route::get(RoutePath::for('password.reset', '/reset-password/{token}'), [NewPasswordController::class, 'create'])
                ->middleware(['guest:'.config('fortify.guard')])
                ->name('password.reset');
        }

        Route::post(RoutePath::for('password.email', '/forgot-password'), [PasswordResetLinkController::class, 'store'])
            ->middleware(['guest:'.config('fortify.guard')])
            ->name('password.email');

        Route::post(RoutePath::for('password.update', '/reset-password'), [NewPasswordController::class, 'store'])
            ->middleware(['guest:'.config('fortify.guard')])
            ->name('password.update');
    }

    // Registration...
    if (Features::enabled(Features::registration())) {
        if ($enableViews) {
            Route::get(RoutePath::for('register', '/register'), [RegisteredUserController::class, 'create'])
                ->middleware(['guest:'.config('fortify.guard')])
                ->name('register');
        }
<?php

namespace Laravel\Fortify;

class RoutePath
{
    /**
     * Get the route path for the given route name.
     *
     * @param  string  $routeName
     * @param  string  $default
     * @return string
     */
    public static function for(string $routeName, string $default)
    {
        return config('fortify.paths.'.$routeName) ?? $default;
    }
}


message: I’ve confirmed the spec names the customer reset pages at root paths, while the current implementation only exposes them under `/account/*` to avoid Fortify’s starter routes. The clean way to close this is to move Fortify’s generic reset URLs away from those root paths, then make the customer Livewire reset pages available at both the spec paths and the existing account aliases.

phase: commentary


type: message

role: assistant

text: I’ve confirmed the spec names the customer reset pages at root paths, while the current implementation only exposes them under `/account/*` to avoid Fortify’s starter routes. The clean way to close this is to move Fortify’s generic reset URLs away from those root paths, then make the customer Livewire reset pages available at both the spec paths and the existing account aliases.


cmd: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/laravel-best-practices/SKILL.md


cmd: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/pest-testing/SKILL.md


cmd: sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/livewire-development/SKILL.md


cmd: sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/developing-with-fortify/SKILL.md


command: sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/developing-with-fortify/SKILL.md

aggregated_output:
---
name: developing-with-fortify
description: Laravel Fortify headless authentication backend development. Activate when implementing authentication features including login, registration, password reset, email verification, two-factor authentication (2FA/TOTP), profile updates, headless auth, authentication scaffolding, or auth guards in Laravel applications.
---

# Laravel Fortify Development

Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications.

## Documentation

Use `search-docs` for detailed Laravel Fortify patterns and documentation.

## Usage

- **Routes**: Use `list-routes` with `only_vendor: true` and `action: "Fortify"` to see all registered endpoints
- **Actions**: Check `app/Actions/Fortify/` for customizable business logic (user creation, password validation, etc.)
- **Config**: See `config/fortify.php` for all options including features, guards, rate limiters, and username field
- **Contracts**: Look in `Laravel\Fortify\Contracts\` for overridable response classes (`LoginResponse`, `LogoutResponse`, etc.)
- **Views**: All view callbacks are set in `FortifyServiceProvider::boot()` using `Fortify::loginView()`, `Fortify::registerView()`, etc.

## Available Features

Enable in `config/fortify.php` features array:

- `Features::registration()` - User registration
- `Features::resetPasswords()` - Password reset via email
- `Features::emailVerification()` - Requires User to implement `MustVerifyEmail`
- `Features::updateProfileInformation()` - Profile updates
- `Features::updatePasswords()` - Password changes
- `Features::twoFactorAuthentication()` - 2FA with QR codes and recovery codes

> Use `search-docs` for feature configuration options and customization patterns.

## Setup Workflows

### Two-Factor Authentication Setup

```
- [ ] Add TwoFactorAuthenticatable trait to User model
- [ ] Enable feature in config/fortify.php
- [ ] Run migrations for 2FA columns
- [ ] Set up view callbacks in FortifyServiceProvider
- [ ] Create 2FA management UI
- [ ] Test QR code and recovery codes
```

> Use `search-docs` for TOTP implementation and recovery code handling patterns.

### Email Verification Setup

```
- [ ] Enable emailVerification feature in config
- [ ] Implement MustVerifyEmail interface on User model
- [ ] Set up verifyEmailView callback
- [ ] Add verified middleware to protected routes
- [ ] Test verification email flow
```

> Use `search-docs` for MustVerifyEmail implementation patterns.

### Password Reset Setup

```
- [ ] Enable resetPasswords feature in config
- [ ] Set up requestPasswordResetLinkView callback
- [ ] Set up resetPasswordView callback
- [ ] Define password.reset named route (if views disabled)
- [ ] Test reset email and link flow
```

> Use `search-docs` for custom password reset flow patterns.

### SPA Authentication Setup

```
- [ ] Set 'views' => false in config/fortify.php
- [ ] Install and configure Laravel Sanctum
- [ ] Use 'web' guard in fortify config
- [ ] Set up CSRF token handling
- [ ] Test XHR authentication flows
```

> Use `search-docs` for integration and SPA authentication patterns.

## Best Practices

### Custom Authentication Logic

Override authentication behavior using `Fortify::authenticateUsing()` for custom user retrieval or `Fortify::authenticateThrough()` to customize the authentication pipeline. Override response contracts in `AppServiceProvider` for custom redirects.

### Registration Customization

Modify `app/Actions/Fortify/CreateNewUser.php` to customize user creation logic, validation rules, and additional fields.

### Rate Limiting

Configure via `fortify.limiters.login` in config. Default configuration throttles by username + IP combination.

## Key Endpoints

| Feature                | Method   | Endpoint                                    |
|------------------------|----------|---------------------------------------------|
| Login                  | POST     | `/login`                                    |
| Logout                 | POST     | `/logout`                                   |
| Register               | POST     | `/register`                                 |
| Password Reset Request | POST     | `/forgot-password`                          |
| Password Reset         | POST     | `/reset-password`                           |
| Email Verify Notice    | GET      | `/email/verify`                             |
| Resend Verification    | POST     | `/email/verification-notification`          |
| Password Confirm       | POST     | `/user/confirm-password`                    |
| Enable 2FA             | POST     | `/user/two-factor-authentication`           |
| Confirm 2FA            | POST     | `/user/confirmed-two-factor-authentication` |
| 2FA Challenge          | POST     | `/two-factor-challenge`                     |
| Get QR Code            | GET      | `/user/two-factor-qr-code`                  |
| Recovery Codes         | GET/POST | `/user/two-factor-recovery-codes`           |