diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 0de4458f1..9051350e9 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -35,6 +35,7 @@ class InstallCommand extends Command implements PromptsForMissingInput {--ssr : Indicates if Inertia SSR support should be installed} {--typescript : Indicates if TypeScript is preferred for the Inertia stack} {--eslint : Indicates if ESLint with Prettier should be installed} + {--oauth : Indicates that OAuth support via Laravel Passport should be installed for the API stack} {--composer=global : Absolute path to the Composer binary which should be used to install packages}'; /** @@ -416,6 +417,13 @@ protected function afterPromptingForMissingArguments(InputInterface $input, Outp )); } + if (in_array($stack, ['api', 'blade', 'react', 'vue'])) { + $input->setOption('oauth', confirm( + label: 'Would you like OAuth support via Laravel Passport?', + default: false + )); + } + $input->setOption('pest', select( label: 'Which testing framework do you prefer?', options: ['Pest', 'PHPUnit'], diff --git a/src/Console/InstallsApiStack.php b/src/Console/InstallsApiStack.php index f88f73cf6..759e3f11b 100644 --- a/src/Console/InstallsApiStack.php +++ b/src/Console/InstallsApiStack.php @@ -13,7 +13,9 @@ trait InstallsApiStack */ protected function installApiStack() { - $this->runCommands(['php artisan install:api']); + $this->runCommands([ + $this->option('oauth') ? 'php artisan install:api --passport' : 'php artisan install:api', + ]); $files = new Filesystem; @@ -28,9 +30,15 @@ protected function installApiStack() 'verified' => '\App\Http\Middleware\EnsureEmailIsVerified::class', ]); - $this->installMiddleware([ - '\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class', - ], 'api', 'prepend'); + if ($this->option('oauth')) { + $this->installMiddleware([ + '\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class', + ], 'web', 'append'); + } else { + $this->installMiddleware([ + '\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class', + ], 'api', 'prepend'); + } // Requests... $files->ensureDirectoryExists(app_path('Http/Requests/Auth')); @@ -44,6 +52,11 @@ protected function installApiStack() copy(__DIR__.'/../../stubs/api/routes/web.php', base_path('routes/web.php')); copy(__DIR__.'/../../stubs/api/routes/auth.php', base_path('routes/auth.php')); + // OAuth... + if ($this->option('oauth')) { + $this->replaceInFile('auth:sanctum', 'auth:api', base_path('routes/api.php')); + } + // Configuration... $files->copyDirectory(__DIR__.'/../../stubs/api/config', config_path()); diff --git a/src/Console/InstallsBladeStack.php b/src/Console/InstallsBladeStack.php index 78f700b69..103e32ded 100644 --- a/src/Console/InstallsBladeStack.php +++ b/src/Console/InstallsBladeStack.php @@ -25,6 +25,16 @@ protected function installBladeStack() ] + $packages; }); + // Install Passport... + if ($this->option('oauth')) { + if (! $this->requireComposerPackages(['laravel/passport:^13.0'])) { + return 1; + } + + // Providers... + (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/app/Providers', app_path('Providers')); + } + // Controllers... (new Filesystem)->ensureDirectoryExists(app_path('Http/Controllers')); (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/default/app/Http/Controllers', app_path('Http/Controllers')); diff --git a/src/Console/InstallsInertiaStacks.php b/src/Console/InstallsInertiaStacks.php index 0b2fd8b08..f7f114d84 100644 --- a/src/Console/InstallsInertiaStacks.php +++ b/src/Console/InstallsInertiaStacks.php @@ -15,7 +15,11 @@ trait InstallsInertiaStacks protected function installInertiaVueStack() { // Install Inertia... - if (! $this->requireComposerPackages(['inertiajs/inertia-laravel:^2.0', 'laravel/sanctum:^4.0', 'tightenco/ziggy:^2.0'])) { + if (! $this->requireComposerPackages([ + 'inertiajs/inertia-laravel:^2.0', + $this->option('oauth') ? 'laravel/passport:^13.0' : 'laravel/sanctum:^4.0', + 'tightenco/ziggy:^2.0', + ])) { return 1; } @@ -226,7 +230,11 @@ protected function installInertiaVueSsrStack() protected function installInertiaReactStack() { // Install Inertia... - if (! $this->requireComposerPackages(['inertiajs/inertia-laravel:^2.0', 'laravel/sanctum:^4.0', 'tightenco/ziggy:^2.0'])) { + if (! $this->requireComposerPackages([ + 'inertiajs/inertia-laravel:^2.0', + $this->option('oauth') ? 'laravel/passport:^13.0' : 'laravel/sanctum:^4.0', + 'tightenco/ziggy:^2.0', + ])) { return 1; } diff --git a/stubs/default/app/Providers/AppServiceProvider.php b/stubs/default/app/Providers/AppServiceProvider.php new file mode 100644 index 000000000..6a67b5b7f --- /dev/null +++ b/stubs/default/app/Providers/AppServiceProvider.php @@ -0,0 +1,25 @@ + +
+

{{ $user->name }}

+

{{ $user->email }}

+
+ +
+ {{ __(':client is requesting permission to access your account.', ['client' => $client->name]) }} +
+ + @if (count($scopes) > 0) +
+

{{ __('This application will be able to:') }}

+ + +
+ @endif + +
+
+ @csrf + + + + + + + {{ __('Authorize') }} + +
+ +
+ @csrf + @method('DELETE') + + + + + + + {{ __('Decline') }} + +
+ + + {{ __('Log into another account') }} + +
+ diff --git a/stubs/inertia-common/app/Providers/AppServiceProvider.php b/stubs/inertia-common/app/Providers/AppServiceProvider.php index 96e9f6c0e..6cc724db5 100644 --- a/stubs/inertia-common/app/Providers/AppServiceProvider.php +++ b/stubs/inertia-common/app/Providers/AppServiceProvider.php @@ -4,6 +4,8 @@ use Illuminate\Support\Facades\Vite; use Illuminate\Support\ServiceProvider; +use Inertia\Inertia; +use Laravel\Passport\Passport; class AppServiceProvider extends ServiceProvider { @@ -21,5 +23,9 @@ public function register(): void public function boot(): void { Vite::prefetch(concurrency: 3); + + if (class_exists(Passport::class)) { + Passport::authorizationView(fn ($params) => Inertia::render('Auth/OAuth/Authorize', $params)); + } } } diff --git a/stubs/inertia-react-ts/resources/js/Pages/Auth/OAuth/Authorize.tsx b/stubs/inertia-react-ts/resources/js/Pages/Auth/OAuth/Authorize.tsx new file mode 100644 index 000000000..bc6996f3e --- /dev/null +++ b/stubs/inertia-react-ts/resources/js/Pages/Auth/OAuth/Authorize.tsx @@ -0,0 +1,88 @@ +import { FormEventHandler } from 'react'; +import GuestLayout from '@/Layouts/GuestLayout'; +import PrimaryButton from '@/Components/PrimaryButton'; +import SecondaryButton from '@/Components/SecondaryButton'; +import { Head, Link, useForm } from '@inertiajs/react'; + +export default function Authorize({ + user, + client, + scopes, + authToken, +}: { + user: { name: string; email: string }, + client: { id: string; name: string }, + scopes: { description: string }[], + authToken: string, +}) { + const { post, processing, transform } = useForm({ + state: route().params.state, + client_id: client.id, + auth_token: authToken, + }); + + const approve: FormEventHandler = (e) => { + e.preventDefault(); + + post(route('passport.authorizations.approve')); + }; + + const deny: FormEventHandler = (e) => { + e.preventDefault(); + + transform((data) => ({ + ...data, + _method: 'delete', + })); + + post(route('passport.authorizations.deny')); + }; + + return ( + + + +
+

+ {user.name} +

+

{user.email}

+
+ +
+ {client.name} is requesting permission to access your account. +
+ + {scopes.length > 0 && ( +
+

This application will be able to:

+ + +
+ )} + +
+
+ Authorize +
+ +
+ + Decline + +
+ + + Log into another account + +
+
+ ); +} diff --git a/stubs/inertia-react/resources/js/Pages/Auth/OAuth/Authorize.jsx b/stubs/inertia-react/resources/js/Pages/Auth/OAuth/Authorize.jsx new file mode 100644 index 000000000..cee4b1ff2 --- /dev/null +++ b/stubs/inertia-react/resources/js/Pages/Auth/OAuth/Authorize.jsx @@ -0,0 +1,77 @@ +import GuestLayout from '@/Layouts/GuestLayout'; +import PrimaryButton from '@/Components/PrimaryButton'; +import SecondaryButton from '@/Components/SecondaryButton'; +import { Head, Link, useForm } from '@inertiajs/react'; + +export default function Authorize({ user, client, scopes, authToken }) { + const { post, processing, transform } = useForm({ + state: route().params.state, + client_id: client.id, + auth_token: authToken, + }); + + const approve = (e) => { + e.preventDefault(); + + post(route('passport.authorizations.approve')); + }; + + const deny = (e) => { + e.preventDefault(); + + transform((data) => ({ + ...data, + _method: 'delete', + })); + + post(route('passport.authorizations.deny')); + }; + + return ( + + + +
+

+ {user.name} +

+

{user.email}

+
+ +
+ {client.name} is requesting permission to access your account. +
+ + {scopes.length > 0 && ( +
+

This application will be able to:

+ + +
+ )} + +
+
+ Authorize +
+ +
+ + Decline + +
+ + + Log into another account + +
+
+ ); +} diff --git a/stubs/inertia-vue-ts/resources/js/Pages/Auth/OAuth/Authorize.vue b/stubs/inertia-vue-ts/resources/js/Pages/Auth/OAuth/Authorize.vue new file mode 100644 index 000000000..e05388988 --- /dev/null +++ b/stubs/inertia-vue-ts/resources/js/Pages/Auth/OAuth/Authorize.vue @@ -0,0 +1,75 @@ + + + diff --git a/stubs/inertia-vue/resources/js/Pages/Auth/OAuth/Authorize.vue b/stubs/inertia-vue/resources/js/Pages/Auth/OAuth/Authorize.vue new file mode 100644 index 000000000..9abf5a5a5 --- /dev/null +++ b/stubs/inertia-vue/resources/js/Pages/Auth/OAuth/Authorize.vue @@ -0,0 +1,75 @@ + + +