├── .github └── workflows │ ├── fix-cs.yml │ ├── gen-coverage.yml │ ├── gen-docs.yml │ └── run-tests.yml ├── .gitignore ├── .php-cs-fixer.php ├── .scrutinizer.yml ├── composer.json ├── composer.lock ├── doctum.php ├── license.txt ├── readme.md ├── settings.json ├── src ├── Console │ ├── InstallCommand.php │ └── Traits │ │ ├── InstallsApiStack.php │ │ ├── InstallsBladeStack.php │ │ ├── InstallsInertiaReactStack.php │ │ └── InstallsInertiaVueStack.php └── MultiAuthServiceProvider.php └── stubs ├── api ├── pest-tests │ ├── Feature │ │ └── Auth │ │ │ ├── AuthenticationTest.php │ │ │ ├── EmailVerificationTest.php │ │ │ ├── PasswordResetTest.php │ │ │ └── RegistrationTest.php │ └── Pest.php ├── src │ ├── Database │ │ └── Factories │ │ │ └── {{singularClass}}Factory.php │ ├── Http │ │ ├── Controllers │ │ │ ├── Auth │ │ │ │ ├── AuthenticatedSessionController.php │ │ │ │ ├── EmailVerificationNotificationController.php │ │ │ │ ├── NewPasswordController.php │ │ │ │ ├── PasswordResetLinkController.php │ │ │ │ ├── Registered{{singularClass}}Controller.php │ │ │ │ └── VerifyEmailController.php │ │ │ └── Controller.php │ │ ├── Middleware │ │ │ ├── Ensure{{singularClass}}EmailIsVerified.php │ │ │ ├── RedirectIfNot{{singularClass}}.php │ │ │ ├── RedirectIf{{singularClass}}.php │ │ │ └── Require{{singularClass}}Password.php │ │ └── Requests │ │ │ └── Auth │ │ │ ├── EmailVerificationRequest.php │ │ │ └── LoginRequest.php │ ├── Models │ │ └── {{singularClass}}.php │ ├── Notifications │ │ └── Auth │ │ │ ├── ResetPassword.php │ │ │ └── VerifyEmail.php │ ├── config │ │ ├── cors.php │ │ └── sanctum.php │ ├── database │ │ ├── .gitignore │ │ └── migrations │ │ │ ├── 2023_02_21_000001_create_{{pluralSnake}}_table.php │ │ │ ├── 2023_02_21_000002_create_{{pluralSnake}}_password_resets_table.php │ │ │ └── 2023_02_21_000003_create_{{pluralSnake}}_password_reset_tokens_table.php │ ├── routes │ │ ├── auth.php │ │ └── {{singularSnake}}.php │ └── {{singularClass}}ServiceProvider.php └── tests │ └── Feature │ └── Auth │ ├── AuthenticationTest.php │ ├── EmailVerificationTest.php │ ├── PasswordResetTest.php │ └── RegistrationTest.php ├── blade ├── pest-tests │ └── Feature │ │ ├── Auth │ │ ├── AuthenticationTest.php │ │ ├── EmailVerificationTest.php │ │ ├── PasswordConfirmationTest.php │ │ ├── PasswordResetTest.php │ │ ├── PasswordUpdateTest.php │ │ └── RegistrationTest.php │ │ └── ProfileTest.php ├── resources │ └── views │ │ ├── auth │ │ ├── confirm-password.blade.php │ │ ├── forgot-password.blade.php │ │ ├── login.blade.php │ │ ├── passwords │ │ │ ├── confirm.blade.php │ │ │ ├── email.blade.php │ │ │ └── reset.blade.php │ │ ├── register.blade.php │ │ ├── reset-password.blade.php │ │ └── verify-email.blade.php │ │ ├── dashboard.blade.php │ │ ├── layouts │ │ ├── app.blade.php │ │ ├── guest.blade.php │ │ └── navigation.blade.php │ │ └── profile │ │ ├── edit.blade.php │ │ └── partials │ │ ├── delete-user-form.blade.php │ │ ├── update-password-form.blade.php │ │ └── update-profile-information-form.blade.php ├── src │ ├── Database │ │ └── Factories │ │ │ └── {{singularClass}}Factory.php │ ├── Http │ │ ├── Controllers │ │ │ ├── Auth │ │ │ │ ├── AuthenticatedSessionController.php │ │ │ │ ├── ConfirmablePasswordController.php │ │ │ │ ├── EmailVerificationNotificationController.php │ │ │ │ ├── EmailVerificationPromptController.php │ │ │ │ ├── NewPasswordController.php │ │ │ │ ├── PasswordController.php │ │ │ │ ├── PasswordResetLinkController.php │ │ │ │ ├── Registered{{singularClass}}Controller.php │ │ │ │ └── VerifyEmailController.php │ │ │ ├── Controller.php │ │ │ └── ProfileController.php │ │ ├── Middleware │ │ │ ├── Ensure{{singularClass}}EmailIsVerified.php │ │ │ ├── RedirectIfNot{{singularClass}}.php │ │ │ ├── RedirectIf{{singularClass}}.php │ │ │ └── Require{{singularClass}}Password.php │ │ └── Requests │ │ │ ├── Auth │ │ │ ├── EmailVerificationRequest.php │ │ │ └── LoginRequest.php │ │ │ └── ProfileUpdateRequest.php │ ├── Models │ │ └── {{singularClass}}.php │ ├── Notifications │ │ └── Auth │ │ │ ├── ResetPassword.php │ │ │ └── VerifyEmail.php │ ├── View │ │ └── Components │ │ │ ├── {{singularClass}}AppLayout.php │ │ │ └── {{singularClass}}GuestLayout.php │ ├── database │ │ ├── .gitignore │ │ └── migrations │ │ │ ├── 2023_02_21_000001_create_{{pluralSnake}}_table.php │ │ │ ├── 2023_02_21_000002_create_{{pluralSnake}}_password_resets_table.php │ │ │ └── 2023_02_21_000003_create_{{pluralSnake}}_password_reset_tokens_table.php │ ├── routes │ │ ├── auth.php │ │ └── {{singularSnake}}.php │ └── {{singularClass}}ServiceProvider.php └── tests │ └── Feature │ ├── Auth │ ├── AuthenticationTest.php │ ├── EmailVerificationTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── PasswordUpdateTest.php │ └── RegistrationTest.php │ └── ProfileTest.php ├── inertia ├── pest-tests │ └── Feature │ │ ├── Auth │ │ └── PasswordUpdateTest.php │ │ └── ProfileTest.php ├── src │ ├── Database │ │ └── Factories │ │ │ └── {{singularClass}}Factory.php │ ├── Http │ │ ├── Controllers │ │ │ ├── Auth │ │ │ │ ├── AuthenticatedSessionController.php │ │ │ │ ├── ConfirmablePasswordController.php │ │ │ │ ├── EmailVerificationNotificationController.php │ │ │ │ ├── EmailVerificationPromptController.php │ │ │ │ ├── NewPasswordController.php │ │ │ │ ├── PasswordController.php │ │ │ │ ├── PasswordResetLinkController.php │ │ │ │ ├── Registered{{singularClass}}Controller.php │ │ │ │ └── VerifyEmailController.php │ │ │ ├── Controller.php │ │ │ └── ProfileController.php │ │ ├── Middleware │ │ │ ├── Ensure{{singularClass}}EmailIsVerified.php │ │ │ ├── HandleInertiaRequests.php │ │ │ ├── RedirectIfNot{{singularClass}}.php │ │ │ ├── RedirectIf{{singularClass}}.php │ │ │ └── Require{{singularClass}}Password.php │ │ └── Requests │ │ │ ├── Auth │ │ │ ├── EmailVerificationRequest.php │ │ │ └── LoginRequest.php │ │ │ └── ProfileUpdateRequest.php │ ├── Models │ │ └── {{singularClass}}.php │ ├── Notifications │ │ └── Auth │ │ │ ├── ResetPassword.php │ │ │ └── VerifyEmail.php │ ├── database │ │ ├── .gitignore │ │ └── migrations │ │ │ ├── 2023_02_21_000001_create_{{pluralSnake}}_table.php │ │ │ ├── 2023_02_21_000002_create_{{pluralSnake}}_password_resets_table.php │ │ │ └── 2023_02_21_000003_create_{{pluralSnake}}_password_reset_tokens_table.php │ ├── routes │ │ ├── auth.php │ │ └── {{singularSnake}}.php │ └── {{singularClass}}ServiceProvider.php └── tests │ └── Feature │ ├── Auth │ └── PasswordUpdateTest.php │ └── ProfileTest.php ├── react └── resources │ ├── js │ ├── Layouts │ │ ├── AuthenticatedLayout.jsx │ │ └── GuestLayout.jsx │ ├── Pages │ │ ├── Auth │ │ │ ├── ConfirmPassword.jsx │ │ │ ├── ForgotPassword.jsx │ │ │ ├── Login.jsx │ │ │ ├── Register.jsx │ │ │ ├── ResetPassword.jsx │ │ │ └── VerifyEmail.jsx │ │ ├── Dashboard.jsx │ │ ├── Profile │ │ │ ├── Edit.jsx │ │ │ └── Partials │ │ │ │ ├── DeleteUserForm.jsx │ │ │ │ ├── UpdatePasswordForm.jsx │ │ │ │ └── UpdateProfileInformationForm.jsx │ │ └── Welcome.jsx │ ├── app.jsx │ ├── bootstrap.js │ └── ssr.jsx │ └── views │ └── {{singularSlug}}.blade.php └── vue └── resources ├── js ├── Layouts │ ├── AuthenticatedLayout.vue │ └── GuestLayout.vue ├── Pages │ ├── Auth │ │ ├── ConfirmPassword.vue │ │ ├── ForgotPassword.vue │ │ ├── Login.vue │ │ ├── Register.vue │ │ ├── ResetPassword.vue │ │ └── VerifyEmail.vue │ ├── Dashboard.vue │ ├── Profile │ │ ├── Edit.vue │ │ └── Partials │ │ │ ├── DeleteUserForm.vue │ │ │ ├── UpdatePasswordForm.vue │ │ │ └── UpdateProfileInformationForm.vue │ └── Welcome.vue ├── app.js ├── bootstrap.js └── ssr.js └── views └── {{singularSlug}}.blade.php /.github/workflows/fix-cs.yml: -------------------------------------------------------------------------------- 1 | name: fix-cs 2 | 3 | on: 4 | workflow_dispatch: 5 | # push: 6 | # branches: [ master ] 7 | 8 | jobs: 9 | code-style: 10 | runs-on: ubuntu-latest 11 | name: CS PHP ^8.0 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | persist-credentials: false 19 | 20 | - name: Fix style 21 | uses: docker://oskarstark/php-cs-fixer-ga 22 | with: 23 | args: --config=.php-cs-fixer.php --allow-risky=yes 24 | 25 | - name: Extract branch name 26 | shell: bash 27 | run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" 28 | id: extract_branch 29 | 30 | - name: Commit changes 31 | uses: stefanzweifel/git-auto-commit-action@v2.3.0 32 | with: 33 | commit_message: Fix styling 34 | branch: ${{ steps.extract_branch.outputs.branch }} 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/gen-coverage.yml: -------------------------------------------------------------------------------- 1 | name: gen-coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | coverage: 13 | runs-on: ubuntu-latest 14 | 15 | name: Coverage 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | persist-credentials: false 23 | 24 | - name: Setup PHP 25 | uses: shivammathur/setup-php@v2 26 | with: 27 | php-version: 8.2 28 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, pcov 29 | tools: composer:v2 30 | coverage: pcov 31 | 32 | - name: Get composer cache directory 33 | id: composer-cache 34 | run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT 35 | 36 | - name: Cache composer dependencies 37 | uses: actions/cache@v4 38 | with: 39 | path: ${{ steps.composer-cache.outputs.dir }} 40 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 41 | restore-keys: ${{ runner.os }}-composer- 42 | 43 | - name: Install dependencies 44 | run: | 45 | composer install --no-interaction --no-progress --prefer-dist --optimize-autoloader 46 | 47 | - name: Create tests database 48 | run: | 49 | mkdir -p database 50 | touch database/database.sqlite 51 | 52 | - name: Execute tests, generate code coverage report 53 | env: 54 | DB_CONNECTION: sqlite 55 | DB_DATABASE: database/database.sqlite 56 | run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 57 | 58 | - name: Upload code coverage report 59 | run: | 60 | composer global require scrutinizer/ocular 61 | composer global config --list | grep "vendor-dir" 62 | ~/.composer/vendor/bin/ocular code-coverage:upload --format=php-clover coverage.clover 63 | -------------------------------------------------------------------------------- /.github/workflows/gen-docs.yml: -------------------------------------------------------------------------------- 1 | name: gen-docs 2 | 3 | on: 4 | workflow_dispatch: 5 | # push: 6 | # tags: 7 | # - '*' 8 | 9 | jobs: 10 | docs: 11 | runs-on: ubuntu-latest 12 | strategy: 13 | fail-fast: true 14 | matrix: 15 | php: [8.0] 16 | name: Docs P${{ matrix.php }} 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | with: 22 | fetch-depth: 0 23 | persist-credentials: false 24 | 25 | - name: Cache 26 | uses: actions/cache@v3 27 | with: 28 | path: ~/.composer/cache/files 29 | key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 30 | 31 | - name: Composer 32 | run: composer update --prefer-dist --no-interaction --no-progress 33 | 34 | - name: Generate 35 | run: vendor/bin/doctum.php update doctum.php --only-version=master --force -v 36 | 37 | - name: Deploy 🚀 38 | uses: JamesIves/github-pages-deploy-action@3.7.1 39 | with: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | BRANCH: gh-pages # The branch the action should deploy to. 42 | FOLDER: docs # The folder the action should deploy. 43 | CLEAN: true # Automatically remove deleted files from the deploy branch 44 | -------------------------------------------------------------------------------- /.github/workflows/run-tests.yml: -------------------------------------------------------------------------------- 1 | name: run-tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | tests: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | fail-fast: true 18 | matrix: 19 | php: [8.1, 8.2, 8.3] 20 | laravel: [^10.0, ^11.0] 21 | include: 22 | - laravel: ^11.0 23 | testbench: 9.0.2 24 | - laravel: ^10.0 25 | testbench: ^8.0 26 | exclude: 27 | - laravel: ^11.0 28 | php: 8.1 29 | 30 | name: P ${{ matrix.php }} L ${{ matrix.laravel }} T ${{ matrix.testbench }} 31 | 32 | steps: 33 | - name: Checkout 34 | uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 0 37 | persist-credentials: false 38 | 39 | - name: Setup PHP 40 | uses: shivammathur/setup-php@v2 41 | with: 42 | php-version: ${{ matrix.php }} 43 | extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, pcov 44 | tools: composer:v2 45 | coverage: pcov 46 | 47 | - name: Cache dependencies 48 | uses: actions/cache@v4 49 | with: 50 | path: ~/.composer/cache/files 51 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} 52 | 53 | - name: Install dependencies 54 | run: | 55 | composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --dev --no-interaction --no-progress --no-update 56 | composer update --prefer-dist --no-interaction --no-progress 57 | 58 | - name: Create tests database 59 | run: | 60 | mkdir -p database 61 | touch database/database.sqlite 62 | 63 | - name: Execute tests, generate code coverage report 64 | env: 65 | DB_CONNECTION: sqlite 66 | DB_DATABASE: database/database.sqlite 67 | run: vendor/bin/phpunit 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.cache 2 | .stubs 3 | .vscode 4 | coverage 5 | docs 6 | vendor 7 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | 3 | environment: 4 | php: 8.1.2 5 | 6 | nodes: 7 | analysis: 8 | tests: 9 | override: 10 | - php-scrutinizer-run 11 | 12 | filter: 13 | excluded_paths: 14 | - "stubs/" 15 | - "tests/" 16 | 17 | tools: 18 | external_code_coverage: true 19 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bmatovu/multi-auth", 3 | "description": "Laravel Multi Authentication", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "laravel", 8 | "multi", 9 | "auth" 10 | ], 11 | "support": { 12 | "issues": "https://github.com/mtvbrianking/multi-auth/issues", 13 | "source": "https://github.com/mtvbrianking/multi-auth" 14 | }, 15 | "authors": [ 16 | { 17 | "name": "Brian Matovu", 18 | "email": "mtvbrianking@gmail.com", 19 | "homepage": "https://bmatovu.com", 20 | "role": "Maintainer" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.1", 25 | "illuminate/auth": "^10.0|^11.0", 26 | "illuminate/console": "^10.0|^11.0", 27 | "illuminate/notifications": "^10.0|^11.0", 28 | "illuminate/routing": "^10.0|^11.0", 29 | "illuminate/support": "^10.0|^11.0" 30 | }, 31 | "require-dev": { 32 | "friendsofphp/php-cs-fixer": "^3.14", 33 | "orchestra/testbench": "^8.0|^9.0", 34 | "phpunit/phpunit": "^10.0|^11.0" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Bmatovu\\MultiAuth\\": "src/" 39 | } 40 | }, 41 | "autoload-dev": { 42 | "psr-4": { 43 | "Bmatovu\\MultiAuth\\Test\\": "tests/" 44 | } 45 | }, 46 | "suggest": { 47 | "inertiajs/inertia-laravel": "The Laravel adapter for Inertia.js.", 48 | "laravel/breeze": "Min. Laravel auth scaffolding with Blade and Tailwind.", 49 | "tightenco/ziggy": "Use your Laravel named routes in Javascript" 50 | }, 51 | "minimum-stability": "dev", 52 | "prefer-stable": true, 53 | "scripts": { 54 | "cs-fix": "php-cs-fixer fix", 55 | "cs-lint": "php-cs-fixer fix --dry-run", 56 | "doc": "doctum.php update doctum.php -v", 57 | "test": "phpunit", 58 | "test-coverage": "phpunit --coverage-html coverage" 59 | }, 60 | "config": { 61 | "sort-packages": true 62 | }, 63 | "extra": { 64 | "branch-alias": { 65 | "dev-master": "12.x-dev" 66 | }, 67 | "laravel": { 68 | "providers": [ 69 | "Bmatovu\\MultiAuth\\MultiAuthServiceProvider" 70 | ] 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /doctum.php: -------------------------------------------------------------------------------- 1 | files() 14 | ->name('*.php') 15 | ->exclude('tests') 16 | ->exclude('stubs') 17 | ->exclude('vendor') 18 | ->in($dir); 19 | 20 | $versions = GitVersionCollection::create($dir) 21 | ->add('master', 'Master branch'); 22 | 23 | $repo = new GitHubRemoteRepository( 24 | 'mtvbrianking/multi-auth', 25 | dirname($dir), 26 | 'https://github.com/' 27 | ); 28 | 29 | $options = [ 30 | 'theme' => 'default', 31 | 'versions' => $versions, 32 | 'title' => 'Laravel MultiAuth', 33 | 'build_dir' => __DIR__ . '/docs', 34 | 'cache_dir' => __DIR__ . '/docs/cache', 35 | 'remote_repository' => $repo, 36 | 'default_opened_level' => 3, 37 | ]; 38 | 39 | return new Doctum($iterator, $options); 40 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // src: https://stackoverflow.com/q/68434489 3 | "editor.formatOnSave": false, 4 | } 5 | -------------------------------------------------------------------------------- /src/Console/Traits/InstallsApiStack.php: -------------------------------------------------------------------------------- 1 | argument('guard'); 19 | $pluralClass = Str::plural(Str::studly($guard)); 20 | $singularClass = Str::singular(Str::studly($guard)); 21 | 22 | // Module... 23 | $fs->ensureDirectoryExists(app_path("Modules/{$pluralClass}")); 24 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/api/src', app_path("Modules/{$pluralClass}")); 25 | 26 | // Tests... 27 | $fs->ensureDirectoryExists(base_path("tests/Feature/{$pluralClass}")); 28 | if ($this->option('pest')) { 29 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/api/pest-tests/Feature', base_path("tests/Feature/{$pluralClass}")); 30 | } else { 31 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/api/tests/Feature', base_path("tests/Feature/{$pluralClass}")); 32 | } 33 | 34 | // Conclude... 35 | $this->info("{$singularClass} guard successfully setup."); 36 | 37 | $serviceProvider = "App\\Modules\\{$pluralClass}\\{$singularClass}ServiceProvider::class"; 38 | 39 | $this->info("\nRegister `{$serviceProvider}` in `config/app.php`"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Console/Traits/InstallsBladeStack.php: -------------------------------------------------------------------------------- 1 | argument('guard'); 20 | $pluralClass = Str::plural(Str::studly($guard)); 21 | $singularSlug = Str::singular(Str::slug($guard)); 22 | $singularClass = Str::singular(Str::studly($guard)); 23 | 24 | if (!$this->option('dark')) { 25 | $finder = (new Finder) 26 | ->in(__DIR__ . '/../../../.stubs/blade/resources/views') 27 | ->name('*.blade.php') 28 | ->notName('welcome.blade.php'); 29 | 30 | $this->removeDarkClasses($finder); 31 | } 32 | 33 | // Module... 34 | $fs->ensureDirectoryExists(app_path("Modules/{$pluralClass}")); 35 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/blade/src', app_path("Modules/{$pluralClass}")); 36 | 37 | // Views... 38 | $fs->ensureDirectoryExists(resource_path("views/{$singularSlug}")); 39 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/blade/resources/views', resource_path("views/{$singularSlug}")); 40 | 41 | // Tests... 42 | $fs->ensureDirectoryExists(base_path("tests/Feature/{$pluralClass}")); 43 | if ($this->option('pest')) { 44 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/blade/pest-tests/Feature', base_path("tests/Feature/{$pluralClass}")); 45 | } else { 46 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/blade/tests/Feature', base_path("tests/Feature/{$pluralClass}")); 47 | } 48 | 49 | // Conclude... 50 | $this->info("{$singularClass} guard successfully setup."); 51 | 52 | $serviceProvider = "App\\Modules\\{$pluralClass}\\{$singularClass}ServiceProvider::class"; 53 | 54 | $this->info("\nRegister `{$serviceProvider}` in `config/app.php`"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Console/Traits/InstallsInertiaReactStack.php: -------------------------------------------------------------------------------- 1 | argument('guard'); 20 | $pluralClass = Str::plural(Str::studly($guard)); 21 | $singularSlug = Str::singular(Str::slug($guard)); 22 | $singularClass = Str::singular(Str::studly($guard)); 23 | 24 | if (!$this->option('dark')) { 25 | $finder = (new Finder) 26 | ->in(__DIR__ . "/../../../.stubs/react/resources/js") 27 | ->name('*.jsx') 28 | ->notName('Welcome.jsx'); 29 | 30 | $this->removeDarkClasses($finder); 31 | } 32 | 33 | // Module... 34 | $fs->ensureDirectoryExists(app_path("Modules/{$pluralClass}")); 35 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/inertia/src', app_path("Modules/{$pluralClass}")); 36 | 37 | // Views... 38 | $fs->ensureDirectoryExists(resource_path("js/{$pluralClass}")); 39 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/react/resources/js', resource_path("js/{$pluralClass}")); 40 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/react/resources/views', resource_path("views")); 41 | 42 | // Tests... 43 | $fs->ensureDirectoryExists(base_path("tests/Feature/{$pluralClass}")); 44 | if ($this->option('pest')) { 45 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/inertia/pest-tests/Feature', base_path("tests/Feature/{$pluralClass}")); 46 | } else { 47 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/inertia/tests/Feature', base_path("tests/Feature/{$pluralClass}")); 48 | } 49 | 50 | // Conclude... 51 | $this->info("{$singularClass} guard successfully setup."); 52 | 53 | $serviceProvider = "App\\Modules\\{$pluralClass}\\{$singularClass}ServiceProvider::class"; 54 | 55 | $this->info("\nRegister `{$serviceProvider}` in `config/app.php`"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Console/Traits/InstallsInertiaVueStack.php: -------------------------------------------------------------------------------- 1 | argument('guard'); 20 | $pluralClass = Str::plural(Str::studly($guard)); 21 | $singularSlug = Str::singular(Str::slug($guard)); 22 | $singularClass = Str::singular(Str::studly($guard)); 23 | 24 | if (!$this->option('dark')) { 25 | $finder = (new Finder) 26 | ->in(__DIR__ . "/../../../.stubs/vue/resources/js") 27 | ->name('*.vue') 28 | ->notName('Welcome.vue'); 29 | 30 | $this->removeDarkClasses($finder); 31 | } 32 | 33 | // Module... 34 | $fs->ensureDirectoryExists(app_path("Modules/{$pluralClass}")); 35 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/inertia/src', app_path("Modules/{$pluralClass}")); 36 | 37 | // Views... 38 | $fs->ensureDirectoryExists(resource_path("js/{$pluralClass}")); 39 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/vue/resources/js', resource_path("js/{$pluralClass}")); 40 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/vue/resources/views', resource_path("views")); 41 | 42 | // Tests... 43 | $fs->ensureDirectoryExists(base_path("tests/Feature/{$pluralClass}")); 44 | if ($this->option('pest')) { 45 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/inertia/pest-tests/Feature', base_path("tests/Feature/{$pluralClass}")); 46 | } else { 47 | $fs->copyDirectory(__DIR__ . '/../../../.stubs/inertia/tests/Feature', base_path("tests/Feature/{$pluralClass}")); 48 | } 49 | 50 | // Conclude... 51 | $this->info("{$singularClass} guard successfully setup."); 52 | 53 | $serviceProvider = "App\\Modules\\{$pluralClass}\\{$singularClass}ServiceProvider::class"; 54 | 55 | $this->info("\nRegister `{$serviceProvider}` in `config/app.php`"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/MultiAuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 15 | return; 16 | } 17 | 18 | $this->commands([ 19 | Console\InstallCommand::class, 20 | ]); 21 | } 22 | 23 | /** 24 | * Get the services provided by the provider. 25 | * 26 | * @return array 27 | */ 28 | public function provides() 29 | { 30 | return [Console\InstallCommand::class]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stubs/api/pest-tests/Feature/Auth/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | create(); 7 | 8 | $response = $this->post('/{{singularSlug}}/login', [ 9 | 'email' => ${{singularCamel}}->email, 10 | 'password' => 'password', 11 | ]); 12 | 13 | $this->assertAuthenticatedAs(${{singularCamel}}, '{{singularSlug}}'); 14 | $response->assertNoContent(); 15 | }); 16 | 17 | test('users can not authenticate with invalid password', function () { 18 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 19 | 20 | $this->post('/{{singularSlug}}/login', [ 21 | 'email' => ${{singularCamel}}->email, 22 | 'password' => 'wrong-password', 23 | ]); 24 | 25 | $this->assertGuest('{{singularSlug}}'); 26 | }); 27 | -------------------------------------------------------------------------------- /stubs/api/pest-tests/Feature/Auth/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | create([ 10 | 'email_verified_at' => null, 11 | ]); 12 | 13 | Event::fake(); 14 | 15 | $verificationUrl = URL::temporarySignedRoute( 16 | '{{singularSlug}}.verification.verify', 17 | now()->addMinutes(60), 18 | ['id' => ${{singularCamel}}->id, 'hash' => sha1(${{singularCamel}}->email)] 19 | ); 20 | 21 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get($verificationUrl); 22 | 23 | Event::assertDispatched(Verified::class); 24 | expect(${{singularCamel}}->fresh()->hasVerifiedEmail())->toBeTrue(); 25 | $response->assertRedirect(config('app.frontend_url') . '/{{singularSlug}}?verified=1'); 26 | }); 27 | 28 | test('email is not verified with invalid hash', function () { 29 | ${{singularCamel}} = {{singularClass}}::factory()->create([ 30 | 'email_verified_at' => null, 31 | ]); 32 | 33 | $verificationUrl = URL::temporarySignedRoute( 34 | '{{singularSlug}}.verification.verify', 35 | now()->addMinutes(60), 36 | ['id' => ${{singularCamel}}->id, 'hash' => sha1('wrong-email')] 37 | ); 38 | 39 | $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get($verificationUrl); 40 | 41 | expect(${{singularCamel}}->fresh()->hasVerifiedEmail())->toBeFalse(); 42 | }); 43 | -------------------------------------------------------------------------------- /stubs/api/pest-tests/Feature/Auth/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | create(); 11 | 12 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 13 | 14 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class); 15 | }); 16 | 17 | test('password can be reset with valid token', function () { 18 | Notification::fake(); 19 | 20 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 21 | 22 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 23 | 24 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class, function (object $notification) use (${{singularCamel}}) { 25 | $response = $this->post('/{{singularSlug}}/reset-password', [ 26 | 'token' => $notification->token, 27 | 'email' => ${{singularCamel}}->email, 28 | 'password' => 'password', 29 | 'password_confirmation' => 'password', 30 | ]); 31 | 32 | $response->assertSessionHasNoErrors(); 33 | 34 | return true; 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /stubs/api/pest-tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | post('/{{singularSlug}}/register', [ 7 | 'name' => 'Test {{singularClass}}', 8 | 'email' => 'test@example.com', 9 | 'password' => 'password', 10 | 'password_confirmation' => 'password', 11 | ]); 12 | 13 | $this->assertAuthenticatedAs({{singularClass}}::first(), '{{singularSlug}}'); 14 | $response->assertNoContent(); 15 | }); 16 | -------------------------------------------------------------------------------- /stubs/api/pest-tests/Pest.php: -------------------------------------------------------------------------------- 1 | in('Feature'); 18 | 19 | /* 20 | |-------------------------------------------------------------------------- 21 | | Expectations 22 | |-------------------------------------------------------------------------- 23 | | 24 | | When you're writing tests, you often need to check that values meet certain conditions. The 25 | | "expect()" function gives you access to a set of "expectations" methods that you can use 26 | | to assert different things. Of course, you may extend the Expectation API at any time. 27 | | 28 | */ 29 | 30 | expect()->extend('toBeOne', function () { 31 | return $this->toBe(1); 32 | }); 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Functions 37 | |-------------------------------------------------------------------------- 38 | | 39 | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your 40 | | project that you don't want to repeat in every file. Here you can also expose helpers as 41 | | global functions to help you to reduce the number of lines of code in your test files. 42 | | 43 | */ 44 | 45 | function something() 46 | { 47 | // .. 48 | } 49 | -------------------------------------------------------------------------------- /stubs/api/src/Database/Factories/{{singularClass}}Factory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class {{singularClass}}Factory extends Factory 12 | { 13 | /** 14 | * The name of the factory's corresponding model. 15 | * 16 | * @var class-string<\Illuminate\Database\Eloquent\Model|TModel> 17 | */ 18 | protected $model = \App\Modules\{{pluralClass}}\Models\{{singularClass}}::class; 19 | 20 | /** 21 | * Define the model's default state. 22 | * 23 | * @return array 24 | */ 25 | public function definition(): array 26 | { 27 | return [ 28 | 'name' => fake()->name(), 29 | 'email' => fake()->unique()->safeEmail(), 30 | 'email_verified_at' => now(), 31 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 32 | 'remember_token' => Str::random(10), 33 | ]; 34 | } 35 | 36 | /** 37 | * Indicate that the model's email address should be unverified. 38 | * 39 | * @return $this 40 | */ 41 | public function unverified(): static 42 | { 43 | return $this->state(fn (array $attributes) => [ 44 | 'email_verified_at' => null, 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Auth/AuthenticatedSessionController.php: -------------------------------------------------------------------------------- 1 | authenticate(); 19 | 20 | $request->session()->regenerate(); 21 | 22 | return response()->noContent(); 23 | } 24 | 25 | /** 26 | * Destroy an authenticated session. 27 | */ 28 | public function destroy(Request $request): Response 29 | { 30 | Auth::guard('{{singularSlug}}')->logout(); 31 | 32 | $request->session()->invalidate(); 33 | 34 | $request->session()->regenerateToken(); 35 | 36 | return response()->noContent(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail()) { 18 | return redirect()->intended('/{{singularSlug}}'); 19 | } 20 | 21 | $request->user('{{singularSlug}}')->sendEmailVerificationNotification(); 22 | 23 | return response()->json(['status' => 'verification-link-sent']); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Auth/NewPasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 25 | 'token' => ['required'], 26 | 'email' => ['required', 'email'], 27 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 28 | ]); 29 | 30 | // Here we will attempt to reset the user's password. If it is successful we 31 | // will update the password on an actual user model and persist it to the 32 | // database. Otherwise we will parse the error and return the response. 33 | $status = Password::broker('{{pluralSlug}}')->reset( 34 | $request->only('email', 'password', 'password_confirmation', 'token'), 35 | function (${{singularCamel}}) use ($request) { 36 | ${{singularCamel}}->forceFill([ 37 | 'password' => Hash::make($request->password), 38 | 'remember_token' => Str::random(60), 39 | ])->save(); 40 | 41 | event(new PasswordReset(${{singularCamel}})); 42 | } 43 | ); 44 | 45 | if ($status != Password::PASSWORD_RESET) { 46 | throw ValidationException::withMessages([ 47 | 'email' => [__($status)], 48 | ]); 49 | } 50 | 51 | return response()->json(['status' => __($status)]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Auth/PasswordResetLinkController.php: -------------------------------------------------------------------------------- 1 | validate([ 21 | 'email' => ['required', 'email'], 22 | ]); 23 | 24 | // We will send the password reset link to this user. Once we have attempted 25 | // to send the link, we will examine the response then see the message we 26 | // need to show to the user. Finally, we'll send out a proper response. 27 | $status = Password::broker('{{pluralSlug}}')->sendResetLink( 28 | $request->only('email') 29 | ); 30 | 31 | if ($status != Password::RESET_LINK_SENT) { 32 | throw ValidationException::withMessages([ 33 | 'email' => [__($status)], 34 | ]); 35 | } 36 | 37 | return response()->json(['status' => __($status)]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Auth/Registered{{singularClass}}Controller.php: -------------------------------------------------------------------------------- 1 | validate([ 24 | 'name' => ['required', 'string', 'max:255'], 25 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:' . {{singularClass}}::class], 26 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 27 | ]); 28 | 29 | ${{singularCamel}} = {{singularClass}}::create([ 30 | 'name' => $request->name, 31 | 'email' => $request->email, 32 | 'password' => Hash::make($request->password), 33 | ]); 34 | 35 | event(new Registered(${{singularCamel}})); 36 | 37 | Auth::guard('{{singularSlug}}')->login(${{singularCamel}}); 38 | 39 | return response()->noContent(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Auth/VerifyEmailController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail()) { 18 | return redirect()->intended( 19 | config('app.frontend_url') . '/{{singularSlug}}?verified=1' 20 | ); 21 | } 22 | 23 | if ($request->user('{{singularSlug}}')->markEmailAsVerified()) { 24 | event(new Verified($request->user('{{singularSlug}}'))); 25 | } 26 | 27 | return redirect()->intended( 28 | config('app.frontend_url') . '/{{singularSlug}}?verified=1' 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}') 22 | || ($request->user('{{singularSlug}}') instanceof MustVerifyEmail 23 | && !$request->user('{{singularSlug}}')->hasVerifiedEmail()) 24 | ) { 25 | return response()->json(['message' => 'Your email address is not verified.'], 409); 26 | } 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Middleware/RedirectIfNot{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | return $next($request); 25 | } 26 | 27 | $redirectToRoute = $request->expectsJson() ? '' : route('{{singularSlug}}.login'); 28 | 29 | throw new AuthenticationException( 30 | 'Unauthenticated.', 31 | [$guard], 32 | $redirectToRoute 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Middleware/RedirectIf{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/{{singularSlug}}'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Middleware/Require{{singularClass}}Password.php: -------------------------------------------------------------------------------- 1 | responseFactory = $responseFactory; 36 | $this->urlGenerator = $urlGenerator; 37 | } 38 | 39 | /** 40 | * Handle an incoming request. 41 | */ 42 | public function handle(Request $request, Closure $next, ?string $redirectToRoute = null): Response 43 | { 44 | if ($this->shouldConfirmPassword($request)) { 45 | if ($request->expectsJson()) { 46 | return $this->responseFactory->json([ 47 | 'message' => 'Password confirmation required.', 48 | ], 423); 49 | } 50 | 51 | return $this->responseFactory->redirectGuest( 52 | $this->urlGenerator->route($redirectToRoute ?? '{{singularSlug}}.password.confirm') 53 | ); 54 | } 55 | 56 | return $next($request); 57 | } 58 | 59 | /** 60 | * Determine if the confirmation timeout has expired. 61 | */ 62 | protected function shouldConfirmPassword(Request $request): bool 63 | { 64 | $confirmedAt = time() - $request->session()->get('{{singularSlug}}.auth.password_confirmed_at', 0); 65 | 66 | return $confirmedAt > config('auth.password_timeout', 10800); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Requests/Auth/EmailVerificationRequest.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->getKey(), (string) $this->route('id'))) { 22 | return false; 23 | } 24 | 25 | if (!hash_equals(sha1($this->user('{{singularSlug}}')->getEmailForVerification()), (string) $this->route('hash'))) { 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | * Get the validation rules that apply to the request. 34 | * 35 | * @return array 36 | */ 37 | public function rules() 38 | { 39 | return [ 40 | // 41 | ]; 42 | } 43 | 44 | /** 45 | * Fulfill the email verification request. 46 | * 47 | * @return void 48 | */ 49 | public function fulfill() 50 | { 51 | if (!$this->user('{{singularSlug}}')->hasVerifiedEmail()) { 52 | $this->user('{{singularSlug}}')->markEmailAsVerified(); 53 | 54 | event(new Verified($this->user('{{singularSlug}}'))); 55 | } 56 | } 57 | 58 | /** 59 | * Configure the validator instance. 60 | * 61 | * @return void 62 | */ 63 | public function withValidator(Validator $validator) 64 | { 65 | return $validator; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /stubs/api/src/Http/Requests/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public function rules(): array 28 | { 29 | return [ 30 | 'email' => ['required', 'string', 'email'], 31 | 'password' => ['required', 'string'], 32 | ]; 33 | } 34 | 35 | /** 36 | * Attempt to authenticate the request's credentials. 37 | * 38 | * @throws \Illuminate\Validation\ValidationException 39 | */ 40 | public function authenticate(): void 41 | { 42 | $this->ensureIsNotRateLimited(); 43 | 44 | if (!Auth::guard('{{singularSlug}}')->attempt($this->only('email', 'password'), $this->boolean('remember'))) { 45 | RateLimiter::hit($this->throttleKey()); 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => trans('auth.failed'), 49 | ]); 50 | } 51 | 52 | RateLimiter::clear($this->throttleKey()); 53 | } 54 | 55 | /** 56 | * Ensure the login request is not rate limited. 57 | * 58 | * @throws \Illuminate\Validation\ValidationException 59 | */ 60 | public function ensureIsNotRateLimited(): void 61 | { 62 | if (!RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { 63 | return; 64 | } 65 | 66 | event(new Lockout($this)); 67 | 68 | $seconds = RateLimiter::availableIn($this->throttleKey()); 69 | 70 | throw ValidationException::withMessages([ 71 | 'email' => trans('auth.throttle', [ 72 | 'seconds' => $seconds, 73 | 'minutes' => ceil($seconds / 60), 74 | ]), 75 | ]); 76 | } 77 | 78 | /** 79 | * Get the rate limiting throttle key for the request. 80 | */ 81 | public function throttleKey(): string 82 | { 83 | return Str::transliterate(Str::lower($this->input('email')) . '|' . $this->ip()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /stubs/api/src/Models/{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected $fillable = [ 24 | 'name', 25 | 'email', 26 | 'password', 27 | ]; 28 | 29 | /** 30 | * The attributes that should be hidden for serialization. 31 | * 32 | * @var array 33 | */ 34 | protected $hidden = [ 35 | 'password', 36 | 'remember_token', 37 | ]; 38 | 39 | /** 40 | * The attributes that should be cast. 41 | * 42 | * @var array 43 | */ 44 | protected $casts = [ 45 | 'email_verified_at' => 'datetime', 46 | ]; 47 | 48 | /** 49 | * Create a new factory instance for the model. 50 | * 51 | * @return \Illuminate\Database\Eloquent\Factories\Factory 52 | */ 53 | protected static function newFactory() 54 | { 55 | return {{singularClass}}Factory::new(); 56 | } 57 | 58 | /** 59 | * Send the password reset notification. 60 | * 61 | * @param string $token 62 | */ 63 | public function sendPasswordResetNotification($token) 64 | { 65 | $this->notify(new ResetPassword($token)); 66 | } 67 | 68 | /** 69 | * Send the email verification notification. 70 | */ 71 | public function sendEmailVerificationNotification() 72 | { 73 | $this->notify(new VerifyEmail()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /stubs/api/src/config/cors.php: -------------------------------------------------------------------------------- 1 | ['*'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => [env('FRONTEND_URL', 'http://localhost:3000')], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => true, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /stubs/api/src/config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 17 | '%s%s%s', 18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 19 | env('APP_URL') ? ','.parse_url(env('APP_URL'), PHP_URL_HOST) : '', 20 | env('FRONTEND_URL') ? ','.parse_url(env('FRONTEND_URL'), PHP_URL_HOST) : '' 21 | ))), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Expiration Minutes 26 | |-------------------------------------------------------------------------- 27 | | 28 | | This value controls the number of minutes until an issued token will be 29 | | considered expired. If this value is null, personal access tokens do 30 | | not expire. This won't tweak the lifetime of first-party sessions. 31 | | 32 | */ 33 | 34 | 'expiration' => null, 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Sanctum Middleware 39 | |-------------------------------------------------------------------------- 40 | | 41 | | When authenticating your first-party SPA with Sanctum you may need to 42 | | customize some of the middleware Sanctum uses while processing the 43 | | request. You may change the middleware listed below as required. 44 | | 45 | */ 46 | 47 | 'middleware' => [ 48 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 49 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /stubs/api/src/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /stubs/api/src/database/migrations/2023_02_21_000001_create_{{pluralSnake}}_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('{{pluralSnake}}'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /stubs/api/src/database/migrations/2023_02_21_000002_create_{{pluralSnake}}_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('{{singularSnake}}_password_resets'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /stubs/api/src/database/migrations/2023_02_21_000003_create_{{pluralSnake}}_password_reset_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('{{singularSnake}}_password_reset_tokens'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /stubs/api/src/routes/auth.php: -------------------------------------------------------------------------------- 1 | middleware(['web', '{{singularSlug}}.guest']) 13 | ->name('{{singularSlug}}.register'); 14 | 15 | Route::post('/{{singularSlug}}/login', [AuthenticatedSessionController::class, 'store']) 16 | ->middleware(['web', '{{singularSlug}}.guest']) 17 | ->name('{{singularSlug}}.login'); 18 | 19 | Route::post('/{{singularSlug}}/forgot-password', [PasswordResetLinkController::class, 'store']) 20 | ->middleware(['web', '{{singularSlug}}.guest']) 21 | ->name('{{singularSlug}}.password.email'); 22 | 23 | Route::post('/{{singularSlug}}/reset-password', [NewPasswordController::class, 'store']) 24 | ->middleware(['web', '{{singularSlug}}.guest']) 25 | ->name('{{singularSlug}}.password.store'); 26 | 27 | Route::get('/{{singularSlug}}/verify-email/{id}/{hash}', VerifyEmailController::class) 28 | ->middleware(['web', '{{singularSlug}}.auth', 'signed', 'throttle:6,1']) 29 | ->name('{{singularSlug}}.verification.verify'); 30 | 31 | Route::post('/{{singularSlug}}/email/verification-notification', [EmailVerificationNotificationController::class, 'store']) 32 | ->middleware(['web', '{{singularSlug}}.auth', 'throttle:6,1']) 33 | ->name('{{singularSlug}}.verification.send'); 34 | 35 | Route::post('/{{singularSlug}}/logout', [AuthenticatedSessionController::class, 'destroy']) 36 | ->middleware(['web', '{{singularSlug}}.auth']) 37 | ->name('{{singularSlug}}.logout'); 38 | -------------------------------------------------------------------------------- /stubs/api/src/routes/{{singularSnake}}.php: -------------------------------------------------------------------------------- 1 | app()->version()]; 7 | })->name('{{singularSlug}}.home'); 8 | 9 | require __DIR__ . '/auth.php'; 10 | -------------------------------------------------------------------------------- /stubs/api/src/{{singularClass}}ServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/database/migrations'); 15 | $this->loadRoutesFrom(__DIR__.'/routes/{{singularSnake}}.php'); 16 | } 17 | 18 | /** 19 | * Register the application services. 20 | */ 21 | public function register(): void 22 | { 23 | $this->registerMiddleware(); 24 | $this->injectAuthConfiguration(); 25 | } 26 | 27 | /** 28 | * @see https://laracasts.com/discuss/channels/general-discussion/register-middleware-via-service-provider 29 | */ 30 | protected function registerMiddleware() 31 | { 32 | $router = $this->app['router']; 33 | $router->aliasMiddleware('{{singularSlug}}.auth', Http\Middleware\RedirectIfNot{{singularClass}}::class); 34 | $router->aliasMiddleware('{{singularSlug}}.guest', Http\Middleware\RedirectIf{{singularClass}}::class); 35 | $router->aliasMiddleware('{{singularSlug}}.verified', Http\Middleware\Ensure{{singularClass}}EmailIsVerified::class); 36 | $router->aliasMiddleware('{{singularSlug}}.password.confirm', Http\Middleware\Require{{singularClass}}Password::class); 37 | } 38 | 39 | protected function injectAuthConfiguration() 40 | { 41 | $this->app['config']->set('auth.guards.{{singularSlug}}', [ 42 | 'driver' => 'session', 43 | 'provider' => '{{pluralSlug}}', 44 | ]); 45 | 46 | $this->app['config']->set('auth.providers.{{pluralSlug}}', [ 47 | 'driver' => 'eloquent', 48 | 'model' => Models\{{singularClass}}::class, 49 | ]); 50 | 51 | $this->app['config']->set('auth.passwords.{{pluralSlug}}', [ 52 | 'provider' => '{{pluralSlug}}', 53 | 'table' => '{{singularSnake}}_password_reset_tokens', 54 | 'expire' => 60, 55 | 'throttle' => 60, 56 | ]); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /stubs/api/tests/Feature/Auth/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $response = $this->post('/{{singularSlug}}/login', [ 18 | 'email' => ${{singularCamel}}->email, 19 | 'password' => 'password', 20 | ]); 21 | 22 | $this->assertAuthenticatedAs(${{singularCamel}}, '{{singularSlug}}'); 23 | $response->assertNoContent(); 24 | } 25 | 26 | public function test_users_can_not_authenticate_with_invalid_password(): void 27 | { 28 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 29 | 30 | $this->post('/{{singularSlug}}/login', [ 31 | 'email' => ${{singularCamel}}->email, 32 | 'password' => 'wrong-password', 33 | ]); 34 | 35 | $this->assertGuest('{{singularSlug}}'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /stubs/api/tests/Feature/Auth/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | create([ 19 | 'email_verified_at' => null, 20 | ]); 21 | 22 | Event::fake(); 23 | 24 | $verificationUrl = URL::temporarySignedRoute( 25 | '{{singularSlug}}.verification.verify', 26 | now()->addMinutes(60), 27 | ['id' => ${{singularCamel}}->id, 'hash' => sha1(${{singularCamel}}->email)] 28 | ); 29 | 30 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get($verificationUrl); 31 | 32 | Event::assertDispatched(Verified::class); 33 | $this->assertTrue(${{singularCamel}}->fresh()->hasVerifiedEmail()); 34 | $response->assertRedirect(config('app.frontend_url').'/{{singularSlug}}?verified=1'); 35 | } 36 | 37 | public function test_email_is_not_verified_with_invalid_hash(): void 38 | { 39 | ${{singularCamel}} = {{singularClass}}::factory()->create([ 40 | 'email_verified_at' => null, 41 | ]); 42 | 43 | $verificationUrl = URL::temporarySignedRoute( 44 | '{{singularSlug}}.verification.verify', 45 | now()->addMinutes(60), 46 | ['id' => ${{singularCamel}}->id, 'hash' => sha1('wrong-email')] 47 | ); 48 | 49 | $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get($verificationUrl); 50 | 51 | $this->assertFalse(${{singularCamel}}->fresh()->hasVerifiedEmail()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /stubs/api/tests/Feature/Auth/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | create(); 20 | 21 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 22 | 23 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class); 24 | } 25 | 26 | public function test_password_can_be_reset_with_valid_token(): void 27 | { 28 | Notification::fake(); 29 | 30 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 31 | 32 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 33 | 34 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class, function (object $notification) use (${{singularCamel}}) { 35 | $response = $this->post('/{{singularSlug}}/reset-password', [ 36 | 'token' => $notification->token, 37 | 'email' => ${{singularCamel}}->email, 38 | 'password' => 'password', 39 | 'password_confirmation' => 'password', 40 | ]); 41 | 42 | $response->assertSessionHasNoErrors(); 43 | 44 | return true; 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stubs/api/tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | post('/{{singularSlug}}/register', [ 16 | 'name' => 'Test {{singularClass}}', 17 | 'email' => 'test@example.com', 18 | 'password' => 'password', 19 | 'password_confirmation' => 'password', 20 | ]); 21 | 22 | $this->assertAuthenticatedAs({{singularClass}}::first(), '{{singularSlug}}'); 23 | $response->assertNoContent(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /stubs/blade/pest-tests/Feature/Auth/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | create(); 7 | 8 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get('/{{singularSlug}}/confirm-password'); 9 | 10 | $response->assertStatus(200); 11 | }); 12 | 13 | test('password can be confirmed', function () { 14 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 15 | 16 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->post('/{{singularSlug}}/confirm-password', [ 17 | 'password' => 'password', 18 | ]); 19 | 20 | $response->assertRedirect(); 21 | $response->assertSessionHasNoErrors(); 22 | }); 23 | 24 | test('password is not confirmed with invalid password', function () { 25 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 26 | 27 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->post('/{{singularSlug}}/confirm-password', [ 28 | 'password' => 'wrong-password', 29 | ]); 30 | 31 | $response->assertSessionHasErrors(); 32 | }); 33 | -------------------------------------------------------------------------------- /stubs/blade/pest-tests/Feature/Auth/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}/forgot-password'); 9 | 10 | $response->assertStatus(200); 11 | }); 12 | 13 | test('reset password link can be requested', function () { 14 | Notification::fake(); 15 | 16 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 17 | 18 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 19 | 20 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class); 21 | }); 22 | 23 | test('reset password screen can be rendered', function () { 24 | Notification::fake(); 25 | 26 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 27 | 28 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 29 | 30 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class, function ($notification) { 31 | $response = $this->get('/{{singularSlug}}/reset-password/'.$notification->token); 32 | 33 | $response->assertStatus(200); 34 | 35 | return true; 36 | }); 37 | }); 38 | 39 | test('password can be reset with valid token', function () { 40 | Notification::fake(); 41 | 42 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 43 | 44 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 45 | 46 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class, function ($notification) use (${{singularCamel}}) { 47 | $response = $this->post('/{{singularSlug}}/reset-password', [ 48 | 'token' => $notification->token, 49 | 'email' => ${{singularCamel}}->email, 50 | 'password' => 'password', 51 | 'password_confirmation' => 'password', 52 | ]); 53 | 54 | $response->assertSessionHasNoErrors(); 55 | 56 | return true; 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /stubs/blade/pest-tests/Feature/Auth/PasswordUpdateTest.php: -------------------------------------------------------------------------------- 1 | create(); 8 | 9 | $response = $this 10 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 11 | ->from('/{{singularSlug}}/profile') 12 | ->put('/{{singularSlug}}/password', [ 13 | 'current_password' => 'password', 14 | 'password' => 'new-password', 15 | 'password_confirmation' => 'new-password', 16 | ]); 17 | 18 | $response 19 | ->assertSessionHasNoErrors() 20 | ->assertRedirect('/{{singularSlug}}/profile'); 21 | 22 | $this->assertTrue(Hash::check('new-password', ${{singularCamel}}->refresh()->password)); 23 | }); 24 | 25 | test('correct password must be provided to update password', function () { 26 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 27 | 28 | $response = $this 29 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 30 | ->from('/{{singularSlug}}/profile') 31 | ->put('/{{singularSlug}}/password', [ 32 | 'current_password' => 'wrong-password', 33 | 'password' => 'new-password', 34 | 'password_confirmation' => 'new-password', 35 | ]); 36 | 37 | $response 38 | ->assertSessionHasErrorsIn('updatePassword', 'current_password') 39 | ->assertRedirect('/{{singularSlug}}/profile'); 40 | }); 41 | -------------------------------------------------------------------------------- /stubs/blade/pest-tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}/register'); 7 | 8 | $response->assertStatus(200); 9 | }); 10 | 11 | test('new users can register', function () { 12 | $response = $this->post('/{{singularSlug}}/register', [ 13 | 'name' => 'Test {{singularClass}}', 14 | 'email' => 'test@example.com', 15 | 'password' => 'password', 16 | 'password_confirmation' => 'password', 17 | ]); 18 | 19 | $this->assertAuthenticatedAs({{singularClass}}::first(), '{{singularSlug}}'); 20 | $response->assertRedirect('/{{singularSlug}}'); 21 | }); 22 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/confirm-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ __('This is a secure area of the application. Please confirm your password before continuing.') }} 4 |
5 | 6 |
7 | @csrf 8 | 9 | 10 |
11 | 12 | 13 | 17 | 18 | 19 |
20 | 21 |
22 | 23 | {{ __('Confirm') }} 24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/forgot-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ __('Forgot your password? No problem. Just let us know your email address and we will email you a password reset link that will allow you to choose a new one.') }} 4 |
5 | 6 | 7 | 8 | 9 |
10 | @csrf 11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | {{ __('Email Password Reset Link') }} 22 | 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/login.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | @csrf 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 | 23 | 24 | 25 |
26 | 27 | 28 |
29 | 33 |
34 | 35 |
36 | @if (Route::has('{{singularSlug}}.password.request')) 37 | 38 | {{ __('Forgot your password?') }} 39 | 40 | @endif 41 | 42 | 43 | {{ __('Log in') }} 44 | 45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/passwords/confirm.blade.php: -------------------------------------------------------------------------------- 1 | @extends('{{singularSlug}}.layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Confirm Password') }}
9 | 10 |
11 | {{ __('Please confirm your password before continuing.') }} 12 | 13 |
14 | @csrf 15 | 16 |
17 | 18 | 19 |
20 | 21 | 22 | @error('password') 23 | 24 | {{ $message }} 25 | 26 | @enderror 27 |
28 |
29 | 30 |
31 |
32 | 35 | 36 | @if (Route::has('{{singularSlug}}.password.request')) 37 | 38 | {{ __('Forgot Your Password?') }} 39 | 40 | @endif 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | @endsection 50 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/passwords/email.blade.php: -------------------------------------------------------------------------------- 1 | @extends('{{singularSlug}}.layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Reset Password') }}
9 | 10 |
11 | @if (session('status')) 12 | 15 | @endif 16 | 17 |
18 | @csrf 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | @error('email') 27 | 28 | {{ $message }} 29 | 30 | @enderror 31 |
32 |
33 | 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | @endsection 48 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/register.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | @csrf 4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 | 20 |
21 | 22 | 23 | 27 | 28 | 29 |
30 | 31 | 32 |
33 | 34 | 35 | 38 | 39 | 40 |
41 | 42 |
43 | 44 | {{ __('Already registered?') }} 45 | 46 | 47 | 48 | {{ __('Register') }} 49 | 50 |
51 |
52 |
53 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/reset-password.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | @csrf 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 | 23 |
24 | 25 | 26 | 29 | 30 | 31 |
32 | 33 |
34 | 35 | {{ __('Reset Password') }} 36 | 37 |
38 |
39 |
40 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/auth/verify-email.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | {{ __('Thanks for signing up! Before getting started, could you verify your email address by clicking on the link we just emailed to you? If you didn\'t receive the email, we will gladly send you another.') }} 4 |
5 | 6 | @if (session('status') == 'verification-link-sent') 7 |
8 | {{ __('A new verification link has been sent to the email address you provided during registration.') }} 9 |
10 | @endif 11 | 12 |
13 |
14 | @csrf 15 | 16 |
17 | 18 | {{ __('Resend Verification Email') }} 19 | 20 |
21 |
22 | 23 |
24 | @csrf 25 | 26 | 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/dashboard.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Dashboard') }} 5 |

6 |
7 | 8 |
9 |
10 |
11 |
12 | {{ __("You're logged in!") }} 13 |
14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 |
19 | @include('{{singularSlug}}.layouts.navigation') 20 | 21 | 22 | @if (isset($header)) 23 |
24 |
25 | {{ $header }} 26 |
27 |
28 | @endif 29 | 30 | 31 |
32 | {{ $slot }} 33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/layouts/guest.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | @vite(['resources/css/app.css', 'resources/js/app.js']) 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 | 25 |
26 | {{ $slot }} 27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/profile/edit.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ __('Profile') }} 5 |

6 |
7 | 8 |
9 |
10 |
11 |
12 | @include('{{singularSlug}}.profile.partials.update-profile-information-form') 13 |
14 |
15 | 16 |
17 |
18 | @include('{{singularSlug}}.profile.partials.update-password-form') 19 |
20 |
21 | 22 |
23 |
24 | @include('{{singularSlug}}.profile.partials.delete-user-form') 25 |
26 |
27 |
28 |
29 |
30 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/profile/partials/delete-user-form.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ __('Delete Account') }} 5 |

6 | 7 |

8 | {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.') }} 9 |

10 |
11 | 12 | {{ __('Delete Account') }} 16 | 17 | 18 |
19 | @csrf 20 | @method('delete') 21 | 22 |

23 | {{ __('Are you sure you want to delete your account?') }} 24 |

25 | 26 |

27 | {{ __('Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.') }} 28 |

29 | 30 |
31 | 32 | 33 | 40 | 41 | 42 |
43 | 44 |
45 | 46 | {{ __('Cancel') }} 47 | 48 | 49 | 50 | {{ __('Delete Account') }} 51 | 52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /stubs/blade/resources/views/profile/partials/update-password-form.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | {{ __('Update Password') }} 5 |

6 | 7 |

8 | {{ __('Ensure your account is using a long, random password to stay secure.') }} 9 |

10 |
11 | 12 |
13 | @csrf 14 | @method('put') 15 | 16 |
17 | 18 | 19 | 20 |
21 | 22 |
23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 | 31 | 32 |
33 | 34 |
35 | {{ __('Save') }} 36 | 37 | @if (session('status') === 'password-updated') 38 |

{{ __('Saved.') }}

45 | @endif 46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /stubs/blade/src/Database/Factories/{{singularClass}}Factory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class {{singularClass}}Factory extends Factory 12 | { 13 | /** 14 | * The name of the factory's corresponding model. 15 | * 16 | * @var class-string<\Illuminate\Database\Eloquent\Model|TModel> 17 | */ 18 | protected $model = \App\Modules\{{pluralClass}}\Models\{{singularClass}}::class; 19 | 20 | /** 21 | * Define the model's default state. 22 | * 23 | * @return array 24 | */ 25 | public function definition(): array 26 | { 27 | return [ 28 | 'name' => fake()->name(), 29 | 'email' => fake()->unique()->safeEmail(), 30 | 'email_verified_at' => now(), 31 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 32 | 'remember_token' => Str::random(10), 33 | ]; 34 | } 35 | 36 | /** 37 | * Indicate that the model's email address should be unverified. 38 | * 39 | * @return $this 40 | */ 41 | public function unverified(): static 42 | { 43 | return $this->state(fn (array $attributes) => [ 44 | 'email_verified_at' => null, 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/AuthenticatedSessionController.php: -------------------------------------------------------------------------------- 1 | authenticate(); 28 | 29 | $request->session()->regenerate(); 30 | 31 | return redirect()->intended('/{{singularSlug}}'); 32 | } 33 | 34 | /** 35 | * Destroy an authenticated session. 36 | */ 37 | public function destroy(Request $request): RedirectResponse 38 | { 39 | Auth::guard('{{singularSlug}}')->logout(); 40 | 41 | $request->session()->invalidate(); 42 | 43 | $request->session()->regenerateToken(); 44 | 45 | return redirect()->route('{{singularSlug}}.dashboard'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/ConfirmablePasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 28 | 'email' => $request->user('{{singularSlug}}')->email, 29 | 'password' => $request->password, 30 | ])) { 31 | throw ValidationException::withMessages([ 32 | 'password' => __('auth.password'), 33 | ]); 34 | } 35 | 36 | $request->session()->put('{{singularSlug}}.auth.password_confirmed_at', time()); 37 | 38 | return redirect()->intended('/{{singularSlug}}'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail()) { 17 | return redirect()->intended('/{{singularSlug}}'); 18 | } 19 | 20 | $request->user('{{singularSlug}}')->sendEmailVerificationNotification(); 21 | 22 | return back()->with('status', 'verification-link-sent'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/EmailVerificationPromptController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail() 18 | ? redirect()->intended('/{{singularSlug}}') 19 | : view('{{singularSlug}}.auth.verify-email'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/NewPasswordController.php: -------------------------------------------------------------------------------- 1 | $request]); 23 | } 24 | 25 | /** 26 | * Handle an incoming new password request. 27 | * 28 | * @throws \Illuminate\Validation\ValidationException 29 | */ 30 | public function store(Request $request): RedirectResponse 31 | { 32 | $request->validate([ 33 | 'token' => ['required'], 34 | 'email' => ['required', 'email'], 35 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 36 | ]); 37 | 38 | // Here we will attempt to reset the {{singularSlug}}'s password. If it is successful we 39 | // will update the password on an actual user model and persist it to the 40 | // database. Otherwise we will parse the error and return the response. 41 | $status = Password::broker('{{pluralSlug}}')->reset( 42 | $request->only('email', 'password', 'password_confirmation', 'token'), 43 | function (${{singularCamel}}) use ($request) { 44 | ${{singularCamel}}->forceFill([ 45 | 'password' => Hash::make($request->password), 46 | 'remember_token' => Str::random(60), 47 | ])->save(); 48 | 49 | event(new PasswordReset(${{singularCamel}})); 50 | } 51 | ); 52 | 53 | // If the password was successfully reset, we will redirect the user back to 54 | // the application's home authenticated view. If there is an error we can 55 | // redirect them back to where they came from with their error message. 56 | return $status == Password::PASSWORD_RESET 57 | ? redirect()->route('{{singularSlug}}.login')->with('status', __($status)) 58 | : back()->withInput($request->only('email')) 59 | ->withErrors(['email' => __($status)]); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | validateWithBag('updatePassword', [ 19 | 'current_password' => ['required', 'current_password:{{singularSlug}}'], 20 | 'password' => ['required', Password::defaults(), 'confirmed'], 21 | ]); 22 | 23 | $request->user('{{singularSlug}}')->update([ 24 | 'password' => Hash::make($validated['password']), 25 | ]); 26 | 27 | return back()->with('status', 'password-updated'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/PasswordResetLinkController.php: -------------------------------------------------------------------------------- 1 | validate([ 29 | 'email' => ['required', 'email'], 30 | ]); 31 | 32 | // We will send the password reset link to this user. Once we have attempted 33 | // to send the link, we will examine the response then see the message we 34 | // need to show to the user. Finally, we'll send out a proper response. 35 | $status = Password::broker('{{pluralSlug}}')->sendResetLink( 36 | $request->only('email') 37 | ); 38 | 39 | return $status == Password::RESET_LINK_SENT 40 | ? back()->with('status', __($status)) 41 | : back()->withInput($request->only('email')) 42 | ->withErrors(['email' => __($status)]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/Registered{{singularClass}}Controller.php: -------------------------------------------------------------------------------- 1 | validate([ 33 | 'name' => ['required', 'string', 'max:255'], 34 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:'.{{singularClass}}::class], 35 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 36 | ]); 37 | 38 | ${{singularCamel}} = {{singularClass}}::create([ 39 | 'name' => $request->name, 40 | 'email' => $request->email, 41 | 'password' => Hash::make($request->password), 42 | ]); 43 | 44 | event(new Registered(${{singularCamel}})); 45 | 46 | Auth::guard('{{singularSlug}}')->login(${{singularCamel}}); 47 | 48 | return redirect('/{{singularSlug}}'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Auth/VerifyEmailController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail()) { 18 | return redirect()->intended('/{{singularSlug}}?verified=1'); 19 | } 20 | 21 | if ($request->user('{{singularSlug}}')->markEmailAsVerified()) { 22 | event(new Verified($request->user('{{singularSlug}}'))); 23 | } 24 | 25 | return redirect()->intended('/{{singularSlug}}?verified=1'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | $request->user('{{singularSlug}}'), 21 | ]); 22 | } 23 | 24 | /** 25 | * Update the {{singularSlug}}'s profile information. 26 | */ 27 | public function update(ProfileUpdateRequest $request): RedirectResponse 28 | { 29 | $request->user('{{singularSlug}}')->fill($request->validated()); 30 | 31 | if ($request->user('{{singularSlug}}')->isDirty('email')) { 32 | $request->user('{{singularSlug}}')->email_verified_at = null; 33 | } 34 | 35 | $request->user('{{singularSlug}}')->save(); 36 | 37 | return Redirect::route('{{singularSlug}}.profile.edit')->with('status', 'profile-updated'); 38 | } 39 | 40 | /** 41 | * Delete the {{singularSlug}}'s account. 42 | */ 43 | public function destroy(Request $request): RedirectResponse 44 | { 45 | $request->validateWithBag('userDeletion', [ 46 | 'password' => ['required', 'current-password:{{singularSlug}}'], 47 | ]); 48 | 49 | ${{singularCamel}} = $request->user('{{singularSlug}}'); 50 | 51 | Auth::guard('{{singularSlug}}')->logout(); 52 | 53 | ${{singularCamel}}->delete(); 54 | 55 | $request->session()->invalidate(); 56 | $request->session()->regenerateToken(); 57 | 58 | return Redirect::to('/{{singularSlug}}'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Middleware/Ensure{{singularClass}}EmailIsVerified.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}') 22 | || ($request->user('{{singularSlug}}') instanceof MustVerifyEmail 23 | && ! $request->user('{{singularSlug}}')->hasVerifiedEmail())) { 24 | return $request->expectsJson() 25 | ? abort(403, 'Your email address is not verified.') 26 | : Redirect::route($redirectToRoute ?: '{{singularSlug}}.verification.notice'); 27 | } 28 | 29 | return $next($request); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Middleware/RedirectIfNot{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | return $next($request); 25 | } 26 | 27 | $redirectToRoute = $request->expectsJson() ? '' : route('{{singularSlug}}.login'); 28 | 29 | throw new AuthenticationException( 30 | 'Unauthenticated.', 31 | [$guard], 32 | $redirectToRoute 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Middleware/RedirectIf{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/{{singularSlug}}'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Middleware/Require{{singularClass}}Password.php: -------------------------------------------------------------------------------- 1 | responseFactory = $responseFactory; 36 | $this->urlGenerator = $urlGenerator; 37 | } 38 | 39 | /** 40 | * Handle an incoming request. 41 | */ 42 | public function handle(Request $request, Closure $next, ?string $redirectToRoute = null): Response 43 | { 44 | if ($this->shouldConfirmPassword($request)) { 45 | if ($request->expectsJson()) { 46 | return $this->responseFactory->json([ 47 | 'message' => 'Password confirmation required.', 48 | ], 423); 49 | } 50 | 51 | return $this->responseFactory->redirectGuest( 52 | $this->urlGenerator->route($redirectToRoute ?? '{{singularSlug}}.password.confirm') 53 | ); 54 | } 55 | 56 | return $next($request); 57 | } 58 | 59 | /** 60 | * Determine if the confirmation timeout has expired. 61 | */ 62 | protected function shouldConfirmPassword(Request $request): bool 63 | { 64 | $confirmedAt = time() - $request->session()->get('{{singularSlug}}.auth.password_confirmed_at', 0); 65 | 66 | return $confirmedAt > config('auth.password_timeout', 10800); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Requests/Auth/EmailVerificationRequest.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->getKey(), (string) $this->route('id'))) { 22 | return false; 23 | } 24 | 25 | if (! hash_equals(sha1($this->user('{{singularSlug}}')->getEmailForVerification()), (string) $this->route('hash'))) { 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | * Get the validation rules that apply to the request. 34 | * 35 | * @return array 36 | */ 37 | public function rules() 38 | { 39 | return [ 40 | // 41 | ]; 42 | } 43 | 44 | /** 45 | * Fulfill the email verification request. 46 | * 47 | * @return void 48 | */ 49 | public function fulfill() 50 | { 51 | if (! $this->user('{{singularSlug}}')->hasVerifiedEmail()) { 52 | $this->user('{{singularSlug}}')->markEmailAsVerified(); 53 | 54 | event(new Verified($this->user('{{singularSlug}}'))); 55 | } 56 | } 57 | 58 | /** 59 | * Configure the validator instance. 60 | * 61 | * @return void 62 | */ 63 | public function withValidator(Validator $validator) 64 | { 65 | return $validator; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Requests/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public function rules(): array 28 | { 29 | return [ 30 | 'email' => ['required', 'string', 'email'], 31 | 'password' => ['required', 'string'], 32 | ]; 33 | } 34 | 35 | /** 36 | * Attempt to authenticate the request's credentials. 37 | * 38 | * @throws \Illuminate\Validation\ValidationException 39 | */ 40 | public function authenticate(): void 41 | { 42 | $this->ensureIsNotRateLimited(); 43 | 44 | if (! Auth::guard('{{singularSlug}}')->attempt($this->only('email', 'password'), $this->boolean('remember'))) { 45 | RateLimiter::hit($this->throttleKey()); 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => trans('auth.failed'), 49 | ]); 50 | } 51 | 52 | RateLimiter::clear($this->throttleKey()); 53 | } 54 | 55 | /** 56 | * Ensure the login request is not rate limited. 57 | * 58 | * @throws \Illuminate\Validation\ValidationException 59 | */ 60 | public function ensureIsNotRateLimited(): void 61 | { 62 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { 63 | return; 64 | } 65 | 66 | event(new Lockout($this)); 67 | 68 | $seconds = RateLimiter::availableIn($this->throttleKey()); 69 | 70 | throw ValidationException::withMessages([ 71 | 'email' => trans('auth.throttle', [ 72 | 'seconds' => $seconds, 73 | 'minutes' => ceil($seconds / 60), 74 | ]), 75 | ]); 76 | } 77 | 78 | /** 79 | * Get the rate limiting throttle key for the request. 80 | */ 81 | public function throttleKey(): string 82 | { 83 | return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /stubs/blade/src/Http/Requests/ProfileUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function rules(): array 17 | { 18 | return [ 19 | 'name' => ['string', 'max:255'], 20 | 'email' => ['email', 'max:255', Rule::unique({{singularClass}}::class)->ignore($this->user('{{singularSlug}}')->id)], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /stubs/blade/src/Models/{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected $fillable = [ 24 | 'name', 25 | 'email', 26 | 'password', 27 | ]; 28 | 29 | /** 30 | * The attributes that should be hidden for serialization. 31 | * 32 | * @var array 33 | */ 34 | protected $hidden = [ 35 | 'password', 36 | 'remember_token', 37 | ]; 38 | 39 | /** 40 | * The attributes that should be cast. 41 | * 42 | * @var array 43 | */ 44 | protected $casts = [ 45 | 'email_verified_at' => 'datetime', 46 | ]; 47 | 48 | /** 49 | * Create a new factory instance for the model. 50 | * 51 | * @return \Illuminate\Database\Eloquent\Factories\Factory 52 | */ 53 | protected static function newFactory() 54 | { 55 | return {{singularClass}}Factory::new(); 56 | } 57 | 58 | /** 59 | * Send the password reset notification. 60 | * 61 | * @param string $token 62 | */ 63 | public function sendPasswordResetNotification($token) 64 | { 65 | $this->notify(new ResetPassword($token)); 66 | } 67 | 68 | /** 69 | * Send the email verification notification. 70 | */ 71 | public function sendEmailVerificationNotification() 72 | { 73 | $this->notify(new VerifyEmail()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /stubs/blade/src/View/Components/{{singularClass}}AppLayout.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('{{pluralSnake}}'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /stubs/blade/src/database/migrations/2023_02_21_000002_create_{{pluralSnake}}_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('{{singularSnake}}_password_resets'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /stubs/blade/src/database/migrations/2023_02_21_000003_create_{{pluralSnake}}_password_reset_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('{{singularSnake}}_password_reset_tokens'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /stubs/blade/src/routes/{{singularSnake}}.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}', function () { 7 | return view('{{singularSlug}}.dashboard'); 8 | })->name('{{singularSlug}}.dashboard'); 9 | 10 | Route::group(['as' => '{{singularSlug}}.', 'prefix' => '/{{singularSlug}}', 'middleware' => ['web', '{{singularSlug}}.auth']], function () { 11 | Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); 12 | Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); 13 | Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); 14 | }); 15 | 16 | require __DIR__.'/auth.php'; 17 | -------------------------------------------------------------------------------- /stubs/blade/src/{{singularClass}}ServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/database/migrations'); 16 | $this->loadRoutesFrom(__DIR__.'/routes/{{singularSnake}}.php'); 17 | 18 | Blade::component('{{singularSlug}}-app-layout', View\Components\{{singularClass}}AppLayout::class); 19 | Blade::component('{{singularSlug}}-guest-layout', View\Components\{{singularClass}}GuestLayout::class); 20 | } 21 | 22 | /** 23 | * Register the application services. 24 | */ 25 | public function register(): void 26 | { 27 | $this->registerMiddleware(); 28 | $this->injectAuthConfiguration(); 29 | } 30 | 31 | /** 32 | * @see https://laracasts.com/discuss/channels/general-discussion/register-middleware-via-service-provider 33 | */ 34 | protected function registerMiddleware() 35 | { 36 | $router = $this->app['router']; 37 | $router->aliasMiddleware('{{singularSlug}}.auth', Http\Middleware\RedirectIfNot{{singularClass}}::class); 38 | $router->aliasMiddleware('{{singularSlug}}.guest', Http\Middleware\RedirectIf{{singularClass}}::class); 39 | $router->aliasMiddleware('{{singularSlug}}.verified', Http\Middleware\Ensure{{singularClass}}EmailIsVerified::class); 40 | $router->aliasMiddleware('{{singularSlug}}.password.confirm', Http\Middleware\Require{{singularClass}}Password::class); 41 | } 42 | 43 | protected function injectAuthConfiguration() 44 | { 45 | $this->app['config']->set('auth.guards.{{singularSlug}}', [ 46 | 'driver' => 'session', 47 | 'provider' => '{{pluralSlug}}', 48 | ]); 49 | 50 | $this->app['config']->set('auth.providers.{{pluralSlug}}', [ 51 | 'driver' => 'eloquent', 52 | 'model' => Models\{{singularClass}}::class, 53 | ]); 54 | 55 | $this->app['config']->set('auth.passwords.{{pluralSlug}}', [ 56 | 'provider' => '{{pluralSlug}}', 57 | 'table' => '{{singularSnake}}_password_reset_tokens', 58 | 'expire' => 60, 59 | 'throttle' => 60, 60 | ]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /stubs/blade/tests/Feature/Auth/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}/login'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | 20 | public function test_{{pluralSnake}}_can_authenticate_using_the_login_screen(): void 21 | { 22 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 23 | 24 | $response = $this->post('/{{singularSlug}}/login', [ 25 | 'email' => ${{singularCamel}}->email, 26 | 'password' => 'password', 27 | ]); 28 | 29 | $this->assertAuthenticatedAs(${{singularCamel}}, '{{singularSlug}}'); 30 | $response->assertRedirect('/{{singularSlug}}'); 31 | } 32 | 33 | public function test_{{pluralSnake}}_can_not_authenticate_with_invalid_password(): void 34 | { 35 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 36 | 37 | $this->post('/{{singularSlug}}/login', [ 38 | 'email' => ${{singularCamel}}->email, 39 | 'password' => 'wrong-password', 40 | ]); 41 | 42 | $this->assertGuest('{{singularSlug}}'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /stubs/blade/tests/Feature/Auth/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | create([ 19 | 'email_verified_at' => null, 20 | ]); 21 | 22 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get('/{{singularSlug}}/verify-email'); 23 | 24 | $response->assertStatus(200); 25 | } 26 | 27 | public function test_email_can_be_verified(): void 28 | { 29 | ${{singularCamel}} = {{singularClass}}::factory()->create([ 30 | 'email_verified_at' => null, 31 | ]); 32 | 33 | Event::fake(); 34 | 35 | $verificationUrl = URL::temporarySignedRoute( 36 | '{{singularSlug}}.verification.verify', 37 | now()->addMinutes(60), 38 | ['id' => ${{singularCamel}}->id, 'hash' => sha1(${{singularCamel}}->email)] 39 | ); 40 | 41 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get($verificationUrl); 42 | 43 | Event::assertDispatched(Verified::class); 44 | $this->assertTrue(${{singularCamel}}->fresh()->hasVerifiedEmail()); 45 | $response->assertRedirect('/{{singularSlug}}'.'?verified=1'); 46 | } 47 | 48 | public function test_email_is_not_verified_with_invalid_hash(): void 49 | { 50 | ${{singularCamel}} = {{singularClass}}::factory()->create([ 51 | 'email_verified_at' => null, 52 | ]); 53 | 54 | $verificationUrl = URL::temporarySignedRoute( 55 | '{{singularSlug}}.verification.verify', 56 | now()->addMinutes(60), 57 | ['id' => ${{singularCamel}}->id, 'hash' => sha1('wrong-email')] 58 | ); 59 | 60 | $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get($verificationUrl); 61 | 62 | $this->assertFalse(${{singularCamel}}->fresh()->hasVerifiedEmail()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /stubs/blade/tests/Feature/Auth/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->get('/{{singularSlug}}/confirm-password'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | 22 | public function test_password_can_be_confirmed(): void 23 | { 24 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 25 | 26 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->post('/{{singularSlug}}/confirm-password', [ 27 | 'password' => 'password', 28 | ]); 29 | 30 | $response->assertRedirect(); 31 | $response->assertSessionHasNoErrors(); 32 | } 33 | 34 | public function test_password_is_not_confirmed_with_invalid_password(): void 35 | { 36 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 37 | 38 | $response = $this->actingAs(${{singularCamel}}, '{{singularSlug}}')->post('/{{singularSlug}}/confirm-password', [ 39 | 'password' => 'wrong-password', 40 | ]); 41 | 42 | $response->assertSessionHasErrors(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /stubs/blade/tests/Feature/Auth/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}/forgot-password'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | 22 | public function test_reset_password_link_can_be_requested(): void 23 | { 24 | Notification::fake(); 25 | 26 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 27 | 28 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 29 | 30 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class); 31 | } 32 | 33 | public function test_reset_password_screen_can_be_rendered(): void 34 | { 35 | Notification::fake(); 36 | 37 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 38 | 39 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 40 | 41 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class, function ($notification) { 42 | $response = $this->get('/{{singularSlug}}/reset-password/'.$notification->token); 43 | 44 | $response->assertStatus(200); 45 | 46 | return true; 47 | }); 48 | } 49 | 50 | public function test_password_can_be_reset_with_valid_token(): void 51 | { 52 | Notification::fake(); 53 | 54 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 55 | 56 | $this->post('/{{singularSlug}}/forgot-password', ['email' => ${{singularCamel}}->email]); 57 | 58 | Notification::assertSentTo(${{singularCamel}}, ResetPassword::class, function ($notification) use (${{singularCamel}}) { 59 | $response = $this->post('/{{singularSlug}}/reset-password', [ 60 | 'token' => $notification->token, 61 | 'email' => ${{singularCamel}}->email, 62 | 'password' => 'password', 63 | 'password_confirmation' => 'password', 64 | ]); 65 | 66 | $response->assertSessionHasNoErrors(); 67 | 68 | return true; 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /stubs/blade/tests/Feature/Auth/PasswordUpdateTest.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | $response = $this 19 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 20 | ->from('/{{singularSlug}}/profile') 21 | ->put('/{{singularSlug}}/password', [ 22 | 'current_password' => 'password', 23 | 'password' => 'new-password', 24 | 'password_confirmation' => 'new-password', 25 | ]); 26 | 27 | $response 28 | ->assertSessionHasNoErrors() 29 | ->assertRedirect('/{{singularSlug}}/profile'); 30 | 31 | $this->assertTrue(Hash::check('new-password', ${{singularCamel}}->refresh()->password)); 32 | } 33 | 34 | public function test_correct_password_must_be_provided_to_update_password(): void 35 | { 36 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 37 | 38 | $response = $this 39 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 40 | ->from('/{{singularSlug}}/profile') 41 | ->put('/{{singularSlug}}/password', [ 42 | 'current_password' => 'wrong-password', 43 | 'password' => 'new-password', 44 | 'password_confirmation' => 'new-password', 45 | ]); 46 | 47 | $response 48 | ->assertSessionHasErrorsIn('updatePassword', 'current_password') 49 | ->assertRedirect('/{{singularSlug}}/profile'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stubs/blade/tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}/register'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | 20 | public function test_new_{{pluralSnake}}_can_register(): void 21 | { 22 | $response = $this->post('/{{singularSlug}}/register', [ 23 | 'name' => 'Test {{singularClass}}', 24 | 'email' => 'test@example.com', 25 | 'password' => 'password', 26 | 'password_confirmation' => 'password', 27 | ]); 28 | 29 | $this->assertAuthenticatedAs({{singularClass}}::first(), '{{singularSlug}}'); 30 | $response->assertRedirect('/{{singularSlug}}'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /stubs/inertia/pest-tests/Feature/Auth/PasswordUpdateTest.php: -------------------------------------------------------------------------------- 1 | create(); 8 | 9 | $response = $this 10 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 11 | ->from('/{{singularSlug}}/profile') 12 | ->put('/{{singularSlug}}/password', [ 13 | 'current_password' => 'password', 14 | 'password' => 'new-password', 15 | 'password_confirmation' => 'new-password', 16 | ]); 17 | 18 | $response 19 | ->assertSessionHasNoErrors() 20 | ->assertRedirect('/{{singularSlug}}/profile'); 21 | 22 | $this->assertTrue(Hash::check('new-password', ${{singularCamel}}->refresh()->password)); 23 | }); 24 | 25 | test('correct password must be provided to update password', function () { 26 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 27 | 28 | $response = $this 29 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 30 | ->from('/{{singularSlug}}/profile') 31 | ->put('/{{singularSlug}}/password', [ 32 | 'current_password' => 'wrong-password', 33 | 'password' => 'new-password', 34 | 'password_confirmation' => 'new-password', 35 | ]); 36 | 37 | $response 38 | ->assertSessionHasErrors('current_password') 39 | ->assertRedirect('/{{singularSlug}}/profile'); 40 | }); 41 | -------------------------------------------------------------------------------- /stubs/inertia/src/Database/Factories/{{singularClass}}Factory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class {{singularClass}}Factory extends Factory 12 | { 13 | /** 14 | * The name of the factory's corresponding model. 15 | * 16 | * @var class-string<\Illuminate\Database\Eloquent\Model|TModel> 17 | */ 18 | protected $model = \App\Modules\{{pluralClass}}\Models\{{singularClass}}::class; 19 | 20 | /** 21 | * Define the model's default state. 22 | * 23 | * @return array 24 | */ 25 | public function definition(): array 26 | { 27 | return [ 28 | 'name' => fake()->name(), 29 | 'email' => fake()->unique()->safeEmail(), 30 | 'email_verified_at' => now(), 31 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 32 | 'remember_token' => Str::random(10), 33 | ]; 34 | } 35 | 36 | /** 37 | * Indicate that the model's email address should be unverified. 38 | * 39 | * @return $this 40 | */ 41 | public function unverified(): static 42 | { 43 | return $this->state(fn (array $attributes) => [ 44 | 'email_verified_at' => null, 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/AuthenticatedSessionController.php: -------------------------------------------------------------------------------- 1 | Route::has('password.request'), 23 | 'status' => session('status'), 24 | ]); 25 | } 26 | 27 | /** 28 | * Handle an incoming authentication request. 29 | */ 30 | public function store(LoginRequest $request): RedirectResponse 31 | { 32 | $request->authenticate(); 33 | 34 | $request->session()->regenerate(); 35 | 36 | return redirect()->intended('/{{singularSlug}}'); 37 | } 38 | 39 | /** 40 | * Destroy an authenticated session. 41 | */ 42 | public function destroy(Request $request): RedirectResponse 43 | { 44 | Auth::guard('{{singularSlug}}')->logout(); 45 | 46 | $request->session()->invalidate(); 47 | 48 | $request->session()->regenerateToken(); 49 | 50 | return redirect('/{{singularSlug}}'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/ConfirmablePasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 29 | 'email' => $request->user('{{singularSlug}}')->email, 30 | 'password' => $request->password, 31 | ])) { 32 | throw ValidationException::withMessages([ 33 | 'password' => __('auth.password'), 34 | ]); 35 | } 36 | 37 | $request->session()->put('auth.password_confirmed_at', time()); 38 | 39 | return redirect()->intended('/{{singularSlug}}'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail()) { 17 | return redirect()->intended('/{{singularSlug}}'); 18 | } 19 | 20 | $request->user('{{singularSlug}}')->sendEmailVerificationNotification(); 21 | 22 | return back()->with('status', 'verification-link-sent'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/EmailVerificationPromptController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail() 19 | ? redirect()->intended('/{{singularSlug}}') 20 | : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/NewPasswordController.php: -------------------------------------------------------------------------------- 1 | $request->email, 26 | 'token' => $request->route('token'), 27 | ]); 28 | } 29 | 30 | /** 31 | * Handle an incoming new password request. 32 | * 33 | * @throws \Illuminate\Validation\ValidationException 34 | */ 35 | public function store(Request $request): RedirectResponse 36 | { 37 | $request->validate([ 38 | 'token' => 'required', 39 | 'email' => 'required|email', 40 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 41 | ]); 42 | 43 | // Here we will attempt to reset the user's password. If it is successful we 44 | // will update the password on an actual user model and persist it to the 45 | // database. Otherwise we will parse the error and return the response. 46 | $status = Password::broker('{{pluralSlug}}')->reset( 47 | $request->only('email', 'password', 'password_confirmation', 'token'), 48 | function (${{singularCamel}}) use ($request) { 49 | ${{singularCamel}}->forceFill([ 50 | 'password' => Hash::make($request->password), 51 | 'remember_token' => Str::random(60), 52 | ])->save(); 53 | 54 | event(new PasswordReset(${{singularCamel}})); 55 | } 56 | ); 57 | 58 | // If the password was successfully reset, we will redirect the user back to 59 | // the application's home authenticated view. If there is an error we can 60 | // redirect them back to where they came from with their error message. 61 | if ($status == Password::PASSWORD_RESET) { 62 | return redirect()->route('{{singularSlug}}.login')->with('status', __($status)); 63 | } 64 | 65 | throw ValidationException::withMessages([ 66 | 'email' => [trans($status)], 67 | ]); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 19 | 'current_password' => ['required', 'current_password'], 20 | 'password' => ['required', Password::defaults(), 'confirmed'], 21 | ]); 22 | 23 | $request->user('{{singularSlug}}')->update([ 24 | 'password' => Hash::make($validated['password']), 25 | ]); 26 | 27 | return back(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/PasswordResetLinkController.php: -------------------------------------------------------------------------------- 1 | session('status'), 22 | ]); 23 | } 24 | 25 | /** 26 | * Handle an incoming password reset link request. 27 | * 28 | * @throws \Illuminate\Validation\ValidationException 29 | */ 30 | public function store(Request $request): RedirectResponse 31 | { 32 | $request->validate([ 33 | 'email' => 'required|email', 34 | ]); 35 | 36 | // We will send the password reset link to this user. Once we have attempted 37 | // to send the link, we will examine the response then see the message we 38 | // need to show to the user. Finally, we'll send out a proper response. 39 | $status = Password::broker('{{pluralSlug}}')->sendResetLink( 40 | $request->only('email') 41 | ); 42 | 43 | if ($status == Password::RESET_LINK_SENT) { 44 | return back()->with('status', __($status)); 45 | } 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => [trans($status)], 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/Registered{{singularClass}}Controller.php: -------------------------------------------------------------------------------- 1 | validate([ 34 | 'name' => 'required|string|max:255', 35 | 'email' => 'required|string|email|max:255|unique:'.{{singularClass}}::class, 36 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 37 | ]); 38 | 39 | ${{singularCamel}} = {{singularClass}}::create([ 40 | 'name' => $request->name, 41 | 'email' => $request->email, 42 | 'password' => Hash::make($request->password), 43 | ]); 44 | 45 | event(new Registered(${{singularCamel}})); 46 | 47 | Auth::guard('{{singularSlug}}')->login(${{singularCamel}}); 48 | 49 | return redirect('/{{singularSlug}}'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Auth/VerifyEmailController.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->hasVerifiedEmail()) { 18 | return redirect()->intended('/{{singularSlug}}?verified=1'); 19 | } 20 | 21 | if ($request->user('{{singularSlug}}')->markEmailAsVerified()) { 22 | event(new Verified($request->user('{{singularSlug}}'))); 23 | } 24 | 25 | return redirect()->intended('/{{singularSlug}}?verified=1'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | $request->user('{{singularSlug}}') instanceof MustVerifyEmail, 23 | 'status' => session('status'), 24 | ]); 25 | } 26 | 27 | /** 28 | * Update the user's profile information. 29 | */ 30 | public function update(ProfileUpdateRequest $request): RedirectResponse 31 | { 32 | $request->user('{{singularSlug}}')->fill($request->validated()); 33 | 34 | if ($request->user('{{singularSlug}}')->isDirty('email')) { 35 | $request->user('{{singularSlug}}')->email_verified_at = null; 36 | } 37 | 38 | $request->user('{{singularSlug}}')->save(); 39 | 40 | return Redirect::route('{{singularSlug}}.profile.edit'); 41 | } 42 | 43 | /** 44 | * Delete the user's account. 45 | */ 46 | public function destroy(Request $request): RedirectResponse 47 | { 48 | $request->validate([ 49 | 'password' => ['required', 'current-password:{{singularSlug}}'], 50 | ]); 51 | 52 | ${{singularCamel}} = $request->user('{{singularSlug}}'); 53 | 54 | Auth::guard('{{singularSlug}}')->logout(); 55 | 56 | ${{singularCamel}}->delete(); 57 | 58 | $request->session()->invalidate(); 59 | $request->session()->regenerateToken(); 60 | 61 | return Redirect::to('/{{singularSlug}}'); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Middleware/Ensure{{singularClass}}EmailIsVerified.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}') 22 | || ($request->user('{{singularSlug}}') instanceof MustVerifyEmail 23 | && ! $request->user('{{singularSlug}}')->hasVerifiedEmail())) { 24 | return $request->expectsJson() 25 | ? abort(403, 'Your email address is not verified.') 26 | : Redirect::route($redirectToRoute ?: '{{singularSlug}}.verification.notice'); 27 | } 28 | 29 | return $next($request); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Middleware/HandleInertiaRequests.php: -------------------------------------------------------------------------------- 1 | 30 | */ 31 | public function share(Request $request): array 32 | { 33 | return array_merge(parent::share($request), [ 34 | 'auth' => [ 35 | 'user' => $request->user('{{singularSlug}}'), 36 | '{{singularSnake}}' => $request->user('{{singularSlug}}'), 37 | ], 38 | 'ziggy' => function () use ($request) { 39 | return array_merge((new Ziggy)->toArray(), [ 40 | 'location' => $request->url(), 41 | ]); 42 | }, 43 | ]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Middleware/RedirectIfNot{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | return $next($request); 25 | } 26 | 27 | $redirectToRoute = $request->expectsJson() ? '' : route('{{singularSlug}}.login'); 28 | 29 | throw new AuthenticationException( 30 | 'Unauthenticated.', 31 | [$guard], 32 | $redirectToRoute 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Middleware/RedirectIf{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | check()) { 21 | return redirect('/{{singularSlug}}'); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Middleware/Require{{singularClass}}Password.php: -------------------------------------------------------------------------------- 1 | responseFactory = $responseFactory; 36 | $this->urlGenerator = $urlGenerator; 37 | } 38 | 39 | /** 40 | * Handle an incoming request. 41 | */ 42 | public function handle(Request $request, Closure $next, ?string $redirectToRoute = null): Response 43 | { 44 | if ($this->shouldConfirmPassword($request)) { 45 | if ($request->expectsJson()) { 46 | return $this->responseFactory->json([ 47 | 'message' => 'Password confirmation required.', 48 | ], 423); 49 | } 50 | 51 | return $this->responseFactory->redirectGuest( 52 | $this->urlGenerator->route($redirectToRoute ?? '{{singularSlug}}.password.confirm') 53 | ); 54 | } 55 | 56 | return $next($request); 57 | } 58 | 59 | /** 60 | * Determine if the confirmation timeout has expired. 61 | */ 62 | protected function shouldConfirmPassword(Request $request): bool 63 | { 64 | $confirmedAt = time() - $request->session()->get('{{singularSlug}}.auth.password_confirmed_at', 0); 65 | 66 | return $confirmedAt > config('auth.password_timeout', 10800); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Requests/Auth/EmailVerificationRequest.php: -------------------------------------------------------------------------------- 1 | user('{{singularSlug}}')->getKey(), (string) $this->route('id'))) { 22 | return false; 23 | } 24 | 25 | if (! hash_equals(sha1($this->user('{{singularSlug}}')->getEmailForVerification()), (string) $this->route('hash'))) { 26 | return false; 27 | } 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | * Get the validation rules that apply to the request. 34 | * 35 | * @return array 36 | */ 37 | public function rules() 38 | { 39 | return [ 40 | // 41 | ]; 42 | } 43 | 44 | /** 45 | * Fulfill the email verification request. 46 | * 47 | * @return void 48 | */ 49 | public function fulfill() 50 | { 51 | if (! $this->user('{{singularSlug}}')->hasVerifiedEmail()) { 52 | $this->user('{{singularSlug}}')->markEmailAsVerified(); 53 | 54 | event(new Verified($this->user('{{singularSlug}}'))); 55 | } 56 | } 57 | 58 | /** 59 | * Configure the validator instance. 60 | * 61 | * @return void 62 | */ 63 | public function withValidator(Validator $validator) 64 | { 65 | return $validator; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Requests/Auth/LoginRequest.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public function rules(): array 28 | { 29 | return [ 30 | 'email' => ['required', 'string', 'email'], 31 | 'password' => ['required', 'string'], 32 | ]; 33 | } 34 | 35 | /** 36 | * Attempt to authenticate the request's credentials. 37 | * 38 | * @throws \Illuminate\Validation\ValidationException 39 | */ 40 | public function authenticate(): void 41 | { 42 | $this->ensureIsNotRateLimited(); 43 | 44 | if (! Auth::guard('{{singularSlug}}')->attempt($this->only('email', 'password'), $this->boolean('remember'))) { 45 | RateLimiter::hit($this->throttleKey()); 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => trans('auth.failed'), 49 | ]); 50 | } 51 | 52 | RateLimiter::clear($this->throttleKey()); 53 | } 54 | 55 | /** 56 | * Ensure the login request is not rate limited. 57 | * 58 | * @throws \Illuminate\Validation\ValidationException 59 | */ 60 | public function ensureIsNotRateLimited(): void 61 | { 62 | if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { 63 | return; 64 | } 65 | 66 | event(new Lockout($this)); 67 | 68 | $seconds = RateLimiter::availableIn($this->throttleKey()); 69 | 70 | throw ValidationException::withMessages([ 71 | 'email' => trans('auth.throttle', [ 72 | 'seconds' => $seconds, 73 | 'minutes' => ceil($seconds / 60), 74 | ]), 75 | ]); 76 | } 77 | 78 | /** 79 | * Get the rate limiting throttle key for the request. 80 | */ 81 | public function throttleKey(): string 82 | { 83 | return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip()); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /stubs/inertia/src/Http/Requests/ProfileUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function rules(): array 17 | { 18 | return [ 19 | 'name' => ['string', 'max:255'], 20 | 'email' => ['email', 'max:255', Rule::unique({{singularClass}}::class)->ignore($this->user('{{singularSlug}}')->id)], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /stubs/inertia/src/Models/{{singularClass}}.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | protected $fillable = [ 24 | 'name', 25 | 'email', 26 | 'password', 27 | ]; 28 | 29 | /** 30 | * The attributes that should be hidden for serialization. 31 | * 32 | * @var array 33 | */ 34 | protected $hidden = [ 35 | 'password', 36 | 'remember_token', 37 | ]; 38 | 39 | /** 40 | * The attributes that should be cast. 41 | * 42 | * @var array 43 | */ 44 | protected $casts = [ 45 | 'email_verified_at' => 'datetime', 46 | ]; 47 | 48 | /** 49 | * Create a new factory instance for the model. 50 | * 51 | * @return \Illuminate\Database\Eloquent\Factories\Factory 52 | */ 53 | protected static function newFactory() 54 | { 55 | return {{singularClass}}Factory::new(); 56 | } 57 | 58 | /** 59 | * Send the password reset notification. 60 | * 61 | * @param string $token 62 | */ 63 | public function sendPasswordResetNotification($token) 64 | { 65 | $this->notify(new ResetPassword($token)); 66 | } 67 | 68 | /** 69 | * Send the email verification notification. 70 | */ 71 | public function sendEmailVerificationNotification() 72 | { 73 | $this->notify(new VerifyEmail()); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /stubs/inertia/src/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /stubs/inertia/src/database/migrations/2023_02_21_000001_create_{{pluralSnake}}_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('{{pluralSnake}}'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /stubs/inertia/src/database/migrations/2023_02_21_000002_create_{{pluralSnake}}_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('{{singularSnake}}_password_resets'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /stubs/inertia/src/database/migrations/2023_02_21_000003_create_{{pluralSnake}}_password_reset_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('{{singularSnake}}_password_reset_tokens'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /stubs/inertia/src/routes/{{singularSnake}}.php: -------------------------------------------------------------------------------- 1 | get('/{{singularSlug}}', function () { 8 | return Inertia::render('Dashboard'); 9 | })->name('{{singularSlug}}.dashboard'); 10 | 11 | Route::group(['as' => '{{singularSlug}}.', 'prefix' => '/{{singularSlug}}', 'middleware' => ['{{singularSlug}}', '{{singularSlug}}.auth']], function () { 12 | Route::get('profile', [ProfileController::class, 'edit'])->name('profile.edit'); 13 | Route::patch('profile', [ProfileController::class, 'update'])->name('profile.update'); 14 | Route::delete('profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); 15 | }); 16 | 17 | require __DIR__ . '/auth.php'; 18 | -------------------------------------------------------------------------------- /stubs/inertia/src/{{singularClass}}ServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(__DIR__.'/database/migrations'); 15 | $this->loadRoutesFrom(__DIR__.'/routes/{{singularSnake}}.php'); 16 | } 17 | 18 | /** 19 | * Register the application services. 20 | */ 21 | public function register(): void 22 | { 23 | $this->registerMiddleware(); 24 | $this->injectAuthConfiguration(); 25 | } 26 | 27 | /** 28 | * @see https://laracasts.com/discuss/channels/general-discussion/register-middleware-via-service-provider 29 | */ 30 | protected function registerMiddleware() 31 | { 32 | $router = $this->app['router']; 33 | $router->middlewareGroup('{{singularSlug}}', [ 34 | \App\Http\Middleware\EncryptCookies::class, 35 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 36 | \Illuminate\Session\Middleware\StartSession::class, 37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 38 | \App\Http\Middleware\VerifyCsrfToken::class, 39 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 40 | \App\Modules\{{pluralClass}}\Http\Middleware\HandleInertiaRequests::class, 41 | \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, 42 | ]); 43 | $router->aliasMiddleware('{{singularSlug}}.auth', Http\Middleware\RedirectIfNot{{singularClass}}::class); 44 | $router->aliasMiddleware('{{singularSlug}}.guest', Http\Middleware\RedirectIf{{singularClass}}::class); 45 | $router->aliasMiddleware('{{singularSlug}}.verified', Http\Middleware\Ensure{{singularClass}}EmailIsVerified::class); 46 | $router->aliasMiddleware('{{singularSlug}}.password.confirm', Http\Middleware\Require{{singularClass}}Password::class); 47 | } 48 | 49 | protected function injectAuthConfiguration() 50 | { 51 | $this->app['config']->set('auth.guards.{{singularSlug}}', [ 52 | 'driver' => 'session', 53 | 'provider' => '{{pluralSlug}}', 54 | ]); 55 | 56 | $this->app['config']->set('auth.providers.{{pluralSlug}}', [ 57 | 'driver' => 'eloquent', 58 | 'model' => Models\{{singularClass}}::class, 59 | ]); 60 | 61 | $this->app['config']->set('auth.passwords.{{pluralSlug}}', [ 62 | 'provider' => '{{pluralSlug}}', 63 | 'table' => '{{singularSnake}}_password_reset_tokens', 64 | 'expire' => 60, 65 | 'throttle' => 60, 66 | ]); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /stubs/inertia/tests/Feature/Auth/PasswordUpdateTest.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | $response = $this 19 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 20 | ->from('/{{singularSlug}}/profile') 21 | ->put('/{{singularSlug}}/password', [ 22 | 'current_password' => 'password', 23 | 'password' => 'new-password', 24 | 'password_confirmation' => 'new-password', 25 | ]); 26 | 27 | $response 28 | ->assertSessionHasNoErrors() 29 | ->assertRedirect('/{{singularSlug}}/profile'); 30 | 31 | $this->assertTrue(Hash::check('new-password', ${{singularCamel}}->refresh()->password)); 32 | } 33 | 34 | public function test_correct_password_must_be_provided_to_update_password(): void 35 | { 36 | ${{singularCamel}} = {{singularClass}}::factory()->create(); 37 | 38 | $response = $this 39 | ->actingAs(${{singularCamel}}, '{{singularSlug}}') 40 | ->from('/{{singularSlug}}/profile') 41 | ->put('/{{singularSlug}}/password', [ 42 | 'current_password' => 'wrong-password', 43 | 'password' => 'new-password', 44 | 'password_confirmation' => 'new-password', 45 | ]); 46 | 47 | $response 48 | ->assertSessionHasErrors('current_password') 49 | ->assertRedirect('/{{singularSlug}}/profile'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /stubs/react/resources/js/Layouts/GuestLayout.jsx: -------------------------------------------------------------------------------- 1 | import ApplicationLogo from '@/Components/ApplicationLogo'; 2 | import { Link } from '@inertiajs/react'; 3 | 4 | export default function Guest({ children }) { 5 | return ( 6 |
7 |
8 | 9 | 10 | 11 |
12 | 13 |
14 | {children} 15 |
16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /stubs/react/resources/js/Pages/Auth/ConfirmPassword.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import GuestLayout from '@/{{pluralClass}}/Layouts/GuestLayout'; 3 | import InputError from '@/Components/InputError'; 4 | import InputLabel from '@/Components/InputLabel'; 5 | import PrimaryButton from '@/Components/PrimaryButton'; 6 | import TextInput from '@/Components/TextInput'; 7 | import { Head, useForm } from '@inertiajs/react'; 8 | 9 | export default function ConfirmPassword() { 10 | const { data, setData, post, processing, errors, reset } = useForm({ 11 | password: '', 12 | }); 13 | 14 | useEffect(() => { 15 | return () => { 16 | reset('password'); 17 | }; 18 | }, []); 19 | 20 | const handleOnChange = (event) => { 21 | setData(event.target.name, event.target.value); 22 | }; 23 | 24 | const submit = (e) => { 25 | e.preventDefault(); 26 | 27 | post(route('{{singularSlug}}.password.confirm')); 28 | }; 29 | 30 | return ( 31 | 32 | 33 | 34 |
35 | This is a secure area of the application. Please confirm your password before continuing. 36 |
37 | 38 |
39 |
40 | 41 | 42 | 51 | 52 | 53 |
54 | 55 |
56 | 57 | Confirm 58 | 59 |
60 |
61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /stubs/react/resources/js/Pages/Auth/ForgotPassword.jsx: -------------------------------------------------------------------------------- 1 | import GuestLayout from '@/{{pluralClass}}/Layouts/GuestLayout'; 2 | import InputError from '@/Components/InputError'; 3 | import PrimaryButton from '@/Components/PrimaryButton'; 4 | import TextInput from '@/Components/TextInput'; 5 | import { Head, useForm } from '@inertiajs/react'; 6 | 7 | export default function ForgotPassword({ status }) { 8 | const { data, setData, post, processing, errors } = useForm({ 9 | email: '', 10 | }); 11 | 12 | const onHandleChange = (event) => { 13 | setData(event.target.name, event.target.value); 14 | }; 15 | 16 | const submit = (e) => { 17 | e.preventDefault(); 18 | 19 | post(route('{{singularSlug}}.password.email')); 20 | }; 21 | 22 | return ( 23 | 24 | 25 | 26 |
27 | Forgot your password? No problem. Just let us know your email address and we will email you a password 28 | reset link that will allow you to choose a new one. 29 |
30 | 31 | {status &&
{status}
} 32 | 33 |
34 | 43 | 44 | 45 | 46 |
47 | 48 | Email Password Reset Link 49 | 50 |
51 | 52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /stubs/react/resources/js/Pages/Auth/VerifyEmail.jsx: -------------------------------------------------------------------------------- 1 | import GuestLayout from '@/{{pluralClass}}/Layouts/GuestLayout'; 2 | import PrimaryButton from '@/Components/PrimaryButton'; 3 | import { Head, Link, useForm } from '@inertiajs/react'; 4 | 5 | export default function VerifyEmail({ status }) { 6 | const { post, processing } = useForm({}); 7 | 8 | const submit = (e) => { 9 | e.preventDefault(); 10 | 11 | post(route('{{singularSlug}}.verification.send')); 12 | }; 13 | 14 | return ( 15 | 16 | 17 | 18 |
19 | Thanks for signing up! Before getting started, could you verify your email address by clicking on the 20 | link we just emailed to you? If you didn't receive the email, we will gladly send you another. 21 |
22 | 23 | {status === 'verification-link-sent' && ( 24 |
25 | A new verification link has been sent to the email address you provided during registration. 26 |
27 | )} 28 | 29 |
30 |
31 | Resend Verification Email 32 | 33 | 39 | Log Out 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /stubs/react/resources/js/Pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import AuthenticatedLayout from '@/{{pluralClass}}/Layouts/AuthenticatedLayout'; 2 | import { Head } from '@inertiajs/react'; 3 | 4 | export default function Dashboard(props) { 5 | return ( 6 | Dashboard} 10 | > 11 | 12 | 13 |
14 |
15 |
16 |
You're logged in!
17 |
18 |
19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /stubs/react/resources/js/Pages/Profile/Edit.jsx: -------------------------------------------------------------------------------- 1 | import AuthenticatedLayout from '@/{{pluralClass}}/Layouts/AuthenticatedLayout'; 2 | import DeleteUserForm from './Partials/DeleteUserForm'; 3 | import UpdatePasswordForm from './Partials/UpdatePasswordForm'; 4 | import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm'; 5 | import { Head } from '@inertiajs/react'; 6 | 7 | export default function Edit({ auth, mustVerifyEmail, status }) { 8 | return ( 9 | Profile} 12 | > 13 | 14 | 15 |
16 |
17 |
18 | 23 |
24 | 25 |
26 | 27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 |
35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /stubs/react/resources/js/app.jsx: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | import '../../css/app.css'; 3 | 4 | import { createRoot } from 'react-dom/client'; 5 | import { createInertiaApp } from '@inertiajs/react'; 6 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; 7 | 8 | const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel'; 9 | 10 | createInertiaApp({ 11 | title: (title) => `${title} - ${appName}`, 12 | resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')), 13 | setup({ el, App, props }) { 14 | const root = createRoot(el); 15 | 16 | root.render(); 17 | }, 18 | progress: { 19 | color: '#4B5563', 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /stubs/react/resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We'll load the axios HTTP library which allows us to easily issue requests 3 | * to our Laravel back-end. This library automatically handles sending the 4 | * CSRF token as a header based on the value of the "XSRF" token cookie. 5 | */ 6 | 7 | import axios from 'axios'; 8 | window.axios = axios; 9 | 10 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 11 | 12 | /** 13 | * Echo exposes an expressive API for subscribing to channels and listening 14 | * for events that are broadcast by Laravel. Echo and event broadcasting 15 | * allows your team to easily build robust real-time web applications. 16 | */ 17 | 18 | // import Echo from 'laravel-echo'; 19 | 20 | // import Pusher from 'pusher-js'; 21 | // window.Pusher = Pusher; 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: import.meta.env.VITE_PUSHER_APP_KEY, 26 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', 27 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, 28 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, 29 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, 30 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', 31 | // enabledTransports: ['ws', 'wss'], 32 | // }); 33 | -------------------------------------------------------------------------------- /stubs/react/resources/js/ssr.jsx: -------------------------------------------------------------------------------- 1 | import ReactDOMServer from 'react-dom/server'; 2 | import { createInertiaApp } from '@inertiajs/react'; 3 | import createServer from '@inertiajs/react/server'; 4 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; 5 | import route from '../../../vendor/tightenco/ziggy/dist/index.m'; 6 | 7 | const appName = 'Laravel'; 8 | 9 | createServer((page) => 10 | createInertiaApp({ 11 | page, 12 | render: ReactDOMServer.renderToString, 13 | title: (title) => `${title} - ${appName}`, 14 | resolve: (name) => resolvePageComponent(`./Pages/${name}.jsx`, import.meta.glob('./Pages/**/*.jsx')), 15 | setup: ({ App, props }) => { 16 | global.route = (name, params, absolute) => 17 | route(name, params, absolute, { 18 | ...page.props.ziggy, 19 | location: new URL(page.props.ziggy.location), 20 | }); 21 | 22 | return ; 23 | }, 24 | }) 25 | ); 26 | -------------------------------------------------------------------------------- /stubs/react/resources/views/{{singularSlug}}.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ config('app.name', 'Laravel') }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | @routes 15 | @viteReactRefresh 16 | @vite(['resources/js/{{pluralClass}}/app.jsx', "resources/js/{{pluralClass}}/Pages/{$page['component']}.jsx"]) 17 | @inertiaHead 18 | 19 | 20 | @inertia 21 | 22 | 23 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Layouts/GuestLayout.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Pages/Auth/ConfirmPassword.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 45 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Pages/Auth/ForgotPassword.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 54 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Pages/Auth/ResetPassword.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 69 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Pages/Auth/VerifyEmail.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 47 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Pages/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/Pages/Profile/Edit.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 39 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | import '../../css/app.css'; 3 | 4 | import { createApp, h } from 'vue'; 5 | import { createInertiaApp } from '@inertiajs/vue3'; 6 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; 7 | import { ZiggyVue } from '../../../vendor/tightenco/ziggy/dist/vue.m'; 8 | 9 | const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel'; 10 | 11 | createInertiaApp({ 12 | title: (title) => `${title} - ${appName}`, 13 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), 14 | setup({ el, App, props, plugin }) { 15 | return createApp({ render: () => h(App, props) }) 16 | .use(plugin) 17 | .use(ZiggyVue, Ziggy) 18 | .mount(el); 19 | }, 20 | progress: { 21 | color: '#4B5563', 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We'll load the axios HTTP library which allows us to easily issue requests 3 | * to our Laravel back-end. This library automatically handles sending the 4 | * CSRF token as a header based on the value of the "XSRF" token cookie. 5 | */ 6 | 7 | import axios from 'axios'; 8 | window.axios = axios; 9 | 10 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 11 | 12 | /** 13 | * Echo exposes an expressive API for subscribing to channels and listening 14 | * for events that are broadcast by Laravel. Echo and event broadcasting 15 | * allows your team to easily build robust real-time web applications. 16 | */ 17 | 18 | // import Echo from 'laravel-echo'; 19 | 20 | // import Pusher from 'pusher-js'; 21 | // window.Pusher = Pusher; 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: import.meta.env.VITE_PUSHER_APP_KEY, 26 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', 27 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, 28 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, 29 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, 30 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', 31 | // enabledTransports: ['ws', 'wss'], 32 | // }); 33 | -------------------------------------------------------------------------------- /stubs/vue/resources/js/ssr.js: -------------------------------------------------------------------------------- 1 | import { createSSRApp, h } from 'vue'; 2 | import { renderToString } from '@vue/server-renderer'; 3 | import { createInertiaApp } from '@inertiajs/vue3'; 4 | import createServer from '@inertiajs/vue3/server'; 5 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; 6 | import { ZiggyVue } from '../../../vendor/tightenco/ziggy/dist/vue.m'; 7 | 8 | const appName = 'Laravel'; 9 | 10 | createServer((page) => 11 | createInertiaApp({ 12 | page, 13 | render: renderToString, 14 | title: (title) => `${title} - ${appName}`, 15 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), 16 | setup({ App, props, plugin }) { 17 | return createSSRApp({ render: () => h(App, props) }) 18 | .use(plugin) 19 | .use(ZiggyVue, { 20 | ...page.props.ziggy, 21 | location: new URL(page.props.ziggy.location), 22 | }); 23 | }, 24 | }) 25 | ); 26 | -------------------------------------------------------------------------------- /stubs/vue/resources/views/{{singularSlug}}.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ config('app.name', 'Laravel') }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | @routes 15 | @vite(['resources/js/{{pluralClass}}/app.js', "resources/js/{{pluralClass}}/Pages/{$page['component']}.vue"]) 16 | @inertiaHead 17 | 18 | 19 | @inertia 20 | 21 | 22 | --------------------------------------------------------------------------------