diff --git a/.cursor/rules/laravel-boost.mdc b/.cursor/rules/laravel-boost.mdc index 9464f06..0b60289 100644 --- a/.cursor/rules/laravel-boost.mdc +++ b/.cursor/rules/laravel-boost.mdc @@ -210,7 +210,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components +- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a331541..737877e 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -207,7 +207,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components +- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. diff --git a/.gitignore b/.gitignore index 3a2ae5a..33806df 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,3 @@ yarn-error.log /.vscode /.zed /database/seeders/PersonalDeviceSeeder.php -/.junie/mcp/mcp.json diff --git a/.junie/guidelines.md b/.junie/guidelines.md index a331541..737877e 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -207,7 +207,7 @@ avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, h ## Livewire Core - Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\CreatePost]` artisan command to create new components +- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 737877e..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,531 +0,0 @@ - -=== foundation rules === - -# Laravel Boost Guidelines - -The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to enhance the user's satisfaction building Laravel applications. - -## Foundational Context -This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. - -- php - 8.3.24 -- laravel/framework (LARAVEL) - v12 -- laravel/prompts (PROMPTS) - v0 -- livewire/flux (FLUXUI_FREE) - v2 -- livewire/livewire (LIVEWIRE) - v3 -- livewire/volt (VOLT) - v1 -- larastan/larastan (LARASTAN) - v3 -- laravel/pint (PINT) - v1 -- pestphp/pest (PEST) - v3 -- tailwindcss (TAILWINDCSS) - v4 - - -## Conventions -- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, naming. -- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. -- Check for existing components to reuse before writing a new one. - -## Verification Scripts -- Do not create verification scripts or tinker when tests cover that functionality and prove it works. Unit and feature tests are more important. - -## Application Structure & Architecture -- Stick to existing directory structure - don't create new base folders without approval. -- Do not change the application's dependencies without approval. - -## Frontend Bundling -- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them. - -## Replies -- Be concise in your explanations - focus on what's important rather than explaining obvious details. - -## Documentation Files -- You must only create documentation files if explicitly requested by the user. - - -=== boost rules === - -## Laravel Boost -- Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. - -## Artisan -- Use the `list-artisan-commands` tool when you need to call an Artisan command to double check the available parameters. - -## URLs -- Whenever you share a project URL with the user you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain / IP, and port. - -## Tinker / Debugging -- You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. -- Use the `database-query` tool when you only need to read from the database. - -## Reading Browser Logs With the `browser-logs` Tool -- You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. -- Only recent browser logs will be useful - ignore old logs. - -## Searching Documentation (Critically Important) -- Boost comes with a powerful `search-docs` tool you should use before any other approaches. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation specific for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. -- The 'search-docs' tool is perfect for all Laravel related packages, including Laravel, Inertia, Livewire, Filament, Tailwind, Pest, Nova, Nightwatch, etc. -- You must use this tool to search for Laravel-ecosystem documentation before falling back to other approaches. -- Search the documentation before making code changes to ensure we are taking the correct approach. -- Use multiple, broad, simple, topic based queries to start. For example: `['rate limiting', 'routing rate limiting', 'routing']`. -- Do not add package names to queries - package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. - -### Available Search Syntax -- You can and should pass multiple queries at once. The most relevant results will be returned first. - -1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth' -2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit" -3. Quoted Phrases (Exact Position) - query="infinite scroll" - Words must be adjacent and in that order -4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit" -5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms - - -=== php rules === - -## PHP - -- Always use curly braces for control structures, even if it has one line. - -### Constructors -- Use PHP 8 constructor property promotion in `__construct()`. - - public function __construct(public GitHub $github) { } -- Do not allow empty `__construct()` methods with zero parameters. - -### Type Declarations -- Always use explicit return type declarations for methods and functions. -- Use appropriate PHP type hints for method parameters. - - -protected function isAccessible(User $user, ?string $path = null): bool -{ - ... -} - - -## Comments -- Prefer PHPDoc blocks over comments. Never use comments within the code itself unless there is something _very_ complex going on. - -## PHPDoc Blocks -- Add useful array shape type definitions for arrays when appropriate. - -## Enums -- Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. - - -=== laravel/core rules === - -## Do Things the Laravel Way - -- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. -- If you're creating a generic PHP class, use `artisan make:class`. -- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. - -### Database -- Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. -- Use Eloquent models and relationships before suggesting raw database queries -- Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. -- Generate code that prevents N+1 query problems by using eager loading. -- Use Laravel's query builder for very complex database operations. - -### Model Creation -- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `php artisan make:model`. - -### APIs & Eloquent Resources -- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. - -### Controllers & Validation -- Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. -- Check sibling Form Requests to see if the application uses array or string based validation rules. - -### Queues -- Use queued jobs for time-consuming operations with the `ShouldQueue` interface. - -### Authentication & Authorization -- Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). - -### URL Generation -- When generating links to other pages, prefer named routes and the `route()` function. - -### Configuration -- Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. - -### Testing -- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model. -- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`. -- When creating tests, make use of `php artisan make:test [options] ` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests. - -### Vite Error -- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`. - - -=== laravel/v12 rules === - -## Laravel 12 - -- Use the `search-docs` tool to get version specific documentation. -- Since Laravel 11, Laravel has a new streamlined file structure which this project uses. - -### Laravel 12 Structure -- No middleware files in `app/Http/Middleware/`. -- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files. -- `bootstrap/providers.php` contains application specific service providers. -- **No app\Console\Kernel.php** - use `bootstrap/app.php` or `routes/console.php` for console configuration. -- **Commands auto-register** - files in `app/Console/Commands/` are automatically available and do not require manual registration. - -### Database -- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. -- Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. - -### Models -- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. - - -=== fluxui-free/core rules === - -## Flux UI Free - -- This project is using the free edition of Flux UI. It has full access to the free components and variants, but does not have access to the Pro components. -- Flux UI is a component library for Livewire. Flux is a robust, hand-crafted, UI component library for your Livewire applications. It's built using Tailwind CSS and provides a set of components that are easy to use and customize. -- You should use Flux UI components when available. -- Fallback to standard Blade components if Flux is unavailable. -- If available, use Laravel Boost's `search-docs` tool to get the exact documentation and code snippets available for this project. -- Flux UI components look like this: - - - - - - -### Available Components -This is correct as of Boost installation, but there may be additional components within the codebase. - - -avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, profile, radio, select, separator, switch, text, textarea, tooltip - - - -=== livewire/core rules === - -## Livewire Core -- Use the `search-docs` tool to find exact version specific documentation for how to write Livewire & Livewire tests. -- Use the `php artisan make:livewire [Posts\\CreatePost]` artisan command to create new components -- State should live on the server, with the UI reflecting it. -- All Livewire requests hit the Laravel backend, they're like regular HTTP requests. Always validate form data, and run authorization checks in Livewire actions. - -## Livewire Best Practices -- Livewire components require a single root element. -- Use `wire:loading` and `wire:dirty` for delightful loading states. -- Add `wire:key` in loops: - - ```blade - @foreach ($items as $item) -
- {{ $item->name }} -
- @endforeach - ``` - -- Prefer lifecycle hooks like `mount()`, `updatedFoo()`) for initialization and reactive side effects: - - - public function mount(User $user) { $this->user = $user; } - public function updatedSearch() { $this->resetPage(); } - - - -## Testing Livewire - - - Livewire::test(Counter::class) - ->assertSet('count', 0) - ->call('increment') - ->assertSet('count', 1) - ->assertSee(1) - ->assertStatus(200); - - - - - $this->get('/posts/create') - ->assertSeeLivewire(CreatePost::class); - - - -=== livewire/v3 rules === - -## Livewire 3 - -### Key Changes From Livewire 2 -- These things changed in Livewire 2, but may not have been updated in this application. Verify this application's setup to ensure you conform with application conventions. - - Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default. - - Components now use the `App\Livewire` namespace (not `App\Http\Livewire`). - - Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`). - - Use the `components.layouts.app` view as the typical layout path (not `layouts.app`). - -### New Directives -- `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. Use the documentation to find usage examples. - -### Alpine -- Alpine is now included with Livewire, don't manually include Alpine.js. -- Plugins included with Alpine: persist, intersect, collapse, and focus. - -### Lifecycle Hooks -- You can listen for `livewire:init` to hook into Livewire initialization, and `fail.status === 419` for the page expiring: - - -document.addEventListener('livewire:init', function () { - Livewire.hook('request', ({ fail }) => { - if (fail && fail.status === 419) { - alert('Your session expired'); - } - }); - - Livewire.hook('message.failed', (message, component) => { - console.error(message); - }); -}); - - - -=== volt/core rules === - -## Livewire Volt - -- This project uses Livewire Volt for interactivity within its pages. New pages requiring interactivity must also use Livewire Volt. There is documentation available for it. -- Make new Volt components using `php artisan make:volt [name] [--test] [--pest]` -- Volt is a **class-based** and **functional** API for Livewire that supports single-file components, allowing a component's PHP logic and Blade templates to co-exist in the same file -- Livewire Volt allows PHP logic and Blade templates in one file. Components use the `@livewire("volt-anonymous-fragment-eyJuYW1lIjoidm9sdC1hbm9ueW1vdXMtZnJhZ21lbnQtYmQ5YWJiNTE3YWMyMTgwOTA1ZmUxMzAxODk0MGJiZmIiLCJwYXRoIjoic3RvcmFnZVwvZnJhbWV3b3JrXC92aWV3c1wvMTUxYWRjZWRjMzBhMzllOWIxNzQ0ZDRiMWRjY2FjYWIuYmxhZGUucGhwIn0=", Livewire\Volt\Precompilers\ExtractFragments::componentArguments([...get_defined_vars(), ...array ( -)])) - - - -### Volt Class Based Component Example -To get started, define an anonymous class that extends Livewire\Volt\Component. Within the class, you may utilize all of the features of Livewire using traditional Livewire syntax: - - - -use Livewire\Volt\Component; - -new class extends Component { - public $count = 0; - - public function increment() - { - $this->count++; - } -} ?> - -
-

{{ $count }}

- -
-
- - -### Testing Volt & Volt Components -- Use the existing directory for tests if it already exists. Otherwise, fallback to `tests/Feature/Volt`. - - -use Livewire\Volt\Volt; - -test('counter increments', function () { - Volt::test('counter') - ->assertSee('Count: 0') - ->call('increment') - ->assertSee('Count: 1'); -}); - - - - -declare(strict_types=1); - -use App\Models\{User, Product}; -use Livewire\Volt\Volt; - -test('product form creates product', function () { - $user = User::factory()->create(); - - Volt::test('pages.products.create') - ->actingAs($user) - ->set('form.name', 'Test Product') - ->set('form.description', 'Test Description') - ->set('form.price', 99.99) - ->call('create') - ->assertHasNoErrors(); - - expect(Product::where('name', 'Test Product')->exists())->toBeTrue(); -}); - - - -### Common Patterns - - - - null, 'search' => '']); - -$products = computed(fn() => Product::when($this->search, - fn($q) => $q->where('name', 'like', "%{$this->search}%") -)->get()); - -$edit = fn(Product $product) => $this->editing = $product->id; -$delete = fn(Product $product) => $product->delete(); - -?> - - - - - - - - - - - - - - - Save - Saving... - - - - -=== pint/core rules === - -## Laravel Pint Code Formatter - -- You must run `vendor/bin/pint --dirty` before finalizing changes to ensure your code matches the project's expected style. -- Do not run `vendor/bin/pint --test`, simply run `vendor/bin/pint` to fix any formatting issues. - - -=== pest/core rules === - -## Pest - -### Testing -- If you need to verify a feature is working, write or update a Unit / Feature test. - -### Pest Tests -- All tests must be written using Pest. Use `php artisan make:test --pest `. -- You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. -- Tests should test all of the happy paths, failure paths, and weird paths. -- Tests live in the `tests/Feature` and `tests/Unit` directories. -- Pest tests look and behave like this: - -it('is true', function () { - expect(true)->toBeTrue(); -}); - - -### Running Tests -- Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). -- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. - -### Pest Assertions -- When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: - -it('returns all', function () { - $response = $this->postJson('/api/docs', []); - - $response->assertSuccessful(); -}); - - -### Mocking -- Mocking can be very helpful when appropriate. -- When mocking, you can use the `Pest\Laravel\mock` Pest function, but always import it via `use function Pest\Laravel\mock;` before using it. Alternatively, you can use `$this->mock()` if existing tests do. -- You can also create partial mocks using the same import or self method. - -### Datasets -- Use datasets in Pest to simplify tests which have a lot of duplicated data. This is often the case when testing validation rules, so consider going with this solution when writing tests for validation rules. - - -it('has emails', function (string $email) { - expect($email)->not->toBeEmpty(); -})->with([ - 'james' => 'james@laravel.com', - 'taylor' => 'taylor@laravel.com', -]); - - - -=== tailwindcss/core rules === - -## Tailwind Core - -- Use Tailwind CSS classes to style HTML, check and use existing tailwind conventions within the project before writing your own. -- Offer to extract repeated patterns into components that match the project's conventions (i.e. Blade, JSX, Vue, etc..) -- Think through class placement, order, priority, and defaults - remove redundant classes, add classes to parent or child carefully to limit repetition, group elements logically -- You can use the `search-docs` tool to get exact examples from the official documentation when needed. - -### Spacing -- When listing items, use gap utilities for spacing, don't use margins. - - -
-
Superior
-
Michigan
-
Erie
-
-
- - -### Dark Mode -- If existing pages and components support dark mode, new pages and components must support dark mode in a similar way, typically using `dark:`. - - -=== tailwindcss/v4 rules === - -## Tailwind 4 - -- Always use Tailwind CSS v4 - do not use the deprecated utilities. -- `corePlugins` is not supported in Tailwind v4. -- In Tailwind v4, you import Tailwind using a regular CSS `@import` statement, not using the `@tailwind` directives used in v3: - - - - -### Replaced Utilities -- Tailwind v4 removed deprecated utilities. Do not use the deprecated option - use the replacement. -- Opacity values are still numeric. - -| Deprecated | Replacement | -|------------+--------------| -| bg-opacity-* | bg-black/* | -| text-opacity-* | text-black/* | -| border-opacity-* | border-black/* | -| divide-opacity-* | divide-black/* | -| ring-opacity-* | ring-black/* | -| placeholder-opacity-* | placeholder-black/* | -| flex-shrink-* | shrink-* | -| flex-grow-* | grow-* | -| overflow-ellipsis | text-ellipsis | -| decoration-slice | box-decoration-slice | -| decoration-clone | box-decoration-clone | - - -=== tests rules === - -## Test Enforcement - -- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. -
\ No newline at end of file diff --git a/app/Console/Commands/OidcTestCommand.php b/app/Console/Commands/OidcTestCommand.php deleted file mode 100644 index 2ecfef2..0000000 --- a/app/Console/Commands/OidcTestCommand.php +++ /dev/null @@ -1,90 +0,0 @@ -info('Testing OIDC Configuration...'); - $this->newLine(); - - // Check if OIDC is enabled - $enabled = config('services.oidc.enabled'); - $this->line("OIDC Enabled: " . ($enabled ? '✅ Yes' : '❌ No')); - - // Check configuration values - $endpoint = config('services.oidc.endpoint'); - $clientId = config('services.oidc.client_id'); - $clientSecret = config('services.oidc.client_secret'); - $redirect = config('services.oidc.redirect'); - $scopes = config('services.oidc.scopes', []); - - $this->line("OIDC Endpoint: " . ($endpoint ? "✅ {$endpoint}" : '❌ Not set')); - $this->line("Client ID: " . ($clientId ? "✅ {$clientId}" : '❌ Not set')); - $this->line("Client Secret: " . ($clientSecret ? '✅ Set' : '❌ Not set')); - $this->line("Redirect URL: " . ($redirect ? "✅ {$redirect}" : '❌ Not set')); - $this->line("Scopes: " . (empty($scopes) ? '❌ Not set' : '✅ ' . implode(', ', $scopes))); - - $this->newLine(); - - // Test driver registration - try { - // Only test driver if we have basic configuration - if ($endpoint && $clientId && $clientSecret) { - $driver = Socialite::driver('oidc'); - $this->line("OIDC Driver: ✅ Successfully registered and accessible"); - - if ($enabled) { - $this->info("✅ OIDC is fully configured and ready to use!"); - $this->line("You can test the login flow at: /auth/oidc/redirect"); - } else { - $this->warn("⚠️ OIDC driver is working but OIDC_ENABLED is false."); - } - } else { - $this->line("OIDC Driver: ✅ Registered (configuration test skipped due to missing values)"); - $this->warn("⚠️ OIDC driver is registered but missing required configuration."); - $this->line("Please set the following environment variables:"); - if (!$enabled) $this->line(" - OIDC_ENABLED=true"); - if (!$endpoint) { - $this->line(" - OIDC_ENDPOINT=https://your-oidc-provider.com (base URL)"); - $this->line(" OR"); - $this->line(" - OIDC_ENDPOINT=https://your-oidc-provider.com/.well-known/openid-configuration (full URL)"); - } - if (!$clientId) $this->line(" - OIDC_CLIENT_ID=your-client-id"); - if (!$clientSecret) $this->line(" - OIDC_CLIENT_SECRET=your-client-secret"); - } - } catch (\InvalidArgumentException $e) { - if (str_contains($e->getMessage(), 'Driver [oidc] not supported')) { - $this->error("❌ OIDC Driver registration failed: Driver not supported"); - } else { - $this->error("❌ OIDC Driver error: " . $e->getMessage()); - } - } catch (\Exception $e) { - $this->warn("⚠️ OIDC Driver registered but configuration error: " . $e->getMessage()); - } - - $this->newLine(); - return Command::SUCCESS; - } -} diff --git a/app/Http/Controllers/Auth/OidcController.php b/app/Http/Controllers/Auth/OidcController.php deleted file mode 100644 index 305dd49..0000000 --- a/app/Http/Controllers/Auth/OidcController.php +++ /dev/null @@ -1,116 +0,0 @@ -route('login')->withErrors(['oidc' => 'OIDC authentication is not enabled.']); - } - - // Check if all required OIDC configuration is present - $requiredConfig = ['endpoint', 'client_id', 'client_secret']; - foreach ($requiredConfig as $key) { - if (!config("services.oidc.{$key}")) { - Log::error("OIDC configuration missing: {$key}"); - return redirect()->route('login')->withErrors(['oidc' => 'OIDC is not properly configured.']); - } - } - - try { - return Socialite::driver('oidc')->redirect(); - } catch (\Exception $e) { - Log::error('OIDC redirect error: ' . $e->getMessage()); - return redirect()->route('login')->withErrors(['oidc' => 'Failed to initiate OIDC authentication.']); - } - } - - /** - * Obtain the user information from the OIDC provider. - */ - public function callback(Request $request) - { - if (!config('services.oidc.enabled')) { - return redirect()->route('login')->withErrors(['oidc' => 'OIDC authentication is not enabled.']); - } - - // Check if all required OIDC configuration is present - $requiredConfig = ['endpoint', 'client_id', 'client_secret']; - foreach ($requiredConfig as $key) { - if (!config("services.oidc.{$key}")) { - Log::error("OIDC configuration missing: {$key}"); - return redirect()->route('login')->withErrors(['oidc' => 'OIDC is not properly configured.']); - } - } - - try { - $oidcUser = Socialite::driver('oidc')->user(); - - // Find or create the user - $user = $this->findOrCreateUser($oidcUser); - - // Log the user in - Auth::login($user, true); - - return redirect()->intended(route('dashboard', absolute: false)); - - } catch (\Exception $e) { - Log::error('OIDC callback error: ' . $e->getMessage()); - return redirect()->route('login')->withErrors(['oidc' => 'Failed to authenticate with OIDC provider.']); - } - } - - /** - * Find or create a user based on OIDC information. - */ - protected function findOrCreateUser($oidcUser) - { - // First, try to find user by OIDC subject ID - $user = User::where('oidc_sub', $oidcUser->getId())->first(); - - if ($user) { - // Update user information from OIDC - $user->update([ - 'name' => $oidcUser->getName() ?: $user->name, - 'email' => $oidcUser->getEmail() ?: $user->email, - ]); - return $user; - } - - // If not found by OIDC sub, try to find by email - if ($oidcUser->getEmail()) { - $user = User::where('email', $oidcUser->getEmail())->first(); - - if ($user) { - // Link the existing user with OIDC - $user->update([ - 'oidc_sub' => $oidcUser->getId(), - 'name' => $oidcUser->getName() ?: $user->name, - ]); - return $user; - } - } - - // Create new user - return User::create([ - 'oidc_sub' => $oidcUser->getId(), - 'name' => $oidcUser->getName() ?: 'OIDC User', - 'email' => $oidcUser->getEmail() ?: $oidcUser->getId() . '@oidc.local', - 'password' => bcrypt(Str::random(32)), // Random password since we're using OIDC - 'email_verified_at' => now(), // OIDC users are considered verified - ]); - } -} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index a1c83ab..1f524a7 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -26,7 +26,6 @@ class User extends Authenticatable // implements MustVerifyEmail 'password', 'assign_new_devices', 'assign_new_device_id', - 'oidc_sub', ]; /** diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 6ac75bf..9e5761f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,10 +2,8 @@ namespace App\Providers; -use App\Services\OidcProvider; -use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; -use Laravel\Socialite\Facades\Socialite; +use URL; class AppServiceProvider extends ServiceProvider { @@ -25,17 +23,5 @@ class AppServiceProvider extends ServiceProvider if (app()->isProduction() && config('app.force_https')) { URL::forceScheme('https'); } - - // Register OIDC provider with Socialite - Socialite::extend('oidc', function ($app) { - $config = $app['config']['services.oidc'] ?? []; - return new OidcProvider( - $app['request'], - $config['client_id'] ?? null, - $config['client_secret'] ?? null, - $config['redirect'] ?? null, - $config['scopes'] ?? ['openid', 'profile', 'email'] - ); - }); } } diff --git a/app/Services/OidcProvider.php b/app/Services/OidcProvider.php deleted file mode 100644 index ad9799d..0000000 --- a/app/Services/OidcProvider.php +++ /dev/null @@ -1,156 +0,0 @@ -baseUrl = str_replace('/.well-known/openid-configuration', '', $endpoint); - } else { - $this->baseUrl = rtrim($endpoint, '/'); - } - - $this->scopes = $scopes ?: ['openid', 'profile', 'email']; - $this->loadOidcConfiguration(); - } - - /** - * Load OIDC configuration from the well-known endpoint. - */ - protected function loadOidcConfiguration() - { - try { - $url = $this->baseUrl . '/.well-known/openid-configuration'; - $client = new Client(); - $response = $client->get($url); - $this->oidcConfig = json_decode($response->getBody()->getContents(), true); - - if (!$this->oidcConfig) { - throw new \Exception('OIDC configuration is empty or invalid JSON'); - } - - if (!isset($this->oidcConfig['authorization_endpoint'])) { - throw new \Exception('authorization_endpoint not found in OIDC configuration'); - } - - } catch (\Exception $e) { - throw new \Exception('Failed to load OIDC configuration: ' . $e->getMessage()); - } - } - - /** - * Get the authentication URL for the provider. - */ - protected function getAuthUrl($state) - { - if (!$this->oidcConfig || !isset($this->oidcConfig['authorization_endpoint'])) { - throw new \Exception('OIDC configuration not loaded or authorization_endpoint not found.'); - } - return $this->buildAuthUrlFromBase($this->oidcConfig['authorization_endpoint'], $state); - } - - /** - * Get the token URL for the provider. - */ - protected function getTokenUrl() - { - if (!$this->oidcConfig || !isset($this->oidcConfig['token_endpoint'])) { - throw new \Exception('OIDC configuration not loaded or token_endpoint not found.'); - } - return $this->oidcConfig['token_endpoint']; - } - - /** - * Get the raw user for the given access token. - */ - protected function getUserByToken($token) - { - if (!$this->oidcConfig || !isset($this->oidcConfig['userinfo_endpoint'])) { - throw new \Exception('OIDC configuration not loaded or userinfo_endpoint not found.'); - } - - $response = $this->getHttpClient()->get($this->oidcConfig['userinfo_endpoint'], [ - 'headers' => [ - 'Authorization' => 'Bearer ' . $token, - ], - ]); - - return json_decode($response->getBody(), true); - } - - /** - * Map the raw user array to a Socialite User instance. - */ - protected function mapUserToObject(array $user) - { - return (new User)->setRaw($user)->map([ - 'id' => $user['sub'], - 'nickname' => $user['preferred_username'] ?? null, - 'name' => $user['name'] ?? null, - 'email' => $user['email'] ?? null, - 'avatar' => $user['picture'] ?? null, - ]); - } - - /** - * Get the access token response for the given code. - */ - public function getAccessTokenResponse($code) - { - $response = $this->getHttpClient()->post($this->getTokenUrl(), [ - 'headers' => ['Accept' => 'application/json'], - 'form_params' => $this->getTokenFields($code), - ]); - - return json_decode($response->getBody(), true); - } - - /** - * Get the POST fields for the token request. - */ - protected function getTokenFields($code) - { - return array_merge(parent::getTokenFields($code), [ - 'grant_type' => 'authorization_code', - ]); - } -} \ No newline at end of file diff --git a/composer.json b/composer.json index a2c72e2..43d88d4 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ "keepsuit/laravel-liquid": "^0.5.2", "laravel/framework": "^12.1", "laravel/sanctum": "^4.0", - "laravel/socialite": "^5.23", "laravel/tinker": "^2.10.1", "livewire/flux": "^2.0", "livewire/volt": "^1.7", diff --git a/composer.lock b/composer.lock index 0c56e9b..eba4a54 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9143c36674f3ae13a9e9bad15014d508", + "content-hash": "44fd2c8aec6f954930c2ba3378fdf6b2", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.354.1", + "version": "3.354.0", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "6aa524596cd83416085777a3bd037d06a70b5c65" + "reference": "014ce3465277cf78a05e60c04ce04c9893733bf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6aa524596cd83416085777a3bd037d06a70b5c65", - "reference": "6aa524596cd83416085777a3bd037d06a70b5c65", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/014ce3465277cf78a05e60c04ce04c9893733bf2", + "reference": "014ce3465277cf78a05e60c04ce04c9893733bf2", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.354.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.354.0" }, - "time": "2025-08-15T18:05:41+00:00" + "time": "2025-08-14T18:10:08+00:00" }, { "name": "bnussbau/laravel-trmnl-blade", @@ -744,69 +744,6 @@ ], "time": "2025-03-06T22:45:56+00:00" }, - { - "name": "firebase/php-jwt", - "version": "v6.11.1", - "source": { - "type": "git", - "url": "https://github.com/firebase/php-jwt.git", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", - "shasum": "" - }, - "require": { - "php": "^8.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^7.4", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.5", - "psr/cache": "^2.0||^3.0", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0" - }, - "suggest": { - "ext-sodium": "Support EdDSA (Ed25519) signatures", - "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" - }, - "type": "library", - "autoload": { - "psr-4": { - "Firebase\\JWT\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Neuman Vong", - "email": "neuman+pear@twilio.com", - "role": "Developer" - }, - { - "name": "Anant Narayanan", - "email": "anant@php.net", - "role": "Developer" - } - ], - "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", - "homepage": "https://github.com/firebase/php-jwt", - "keywords": [ - "jwt", - "php" - ], - "support": { - "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" - }, - "time": "2025-04-09T20:32:01+00:00" - }, { "name": "fruitcake/php-cors", "version": "v1.3.0", @@ -2090,78 +2027,6 @@ }, "time": "2025-03-19T13:51:03+00:00" }, - { - "name": "laravel/socialite", - "version": "v5.23.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/socialite.git", - "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", - "reference": "e9e0fc83b9d8d71c8385a5da20e5b95ca6234cf5", - "shasum": "" - }, - "require": { - "ext-json": "*", - "firebase/php-jwt": "^6.4", - "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0|^12.0", - "league/oauth1-client": "^1.11", - "php": "^7.2|^8.0", - "phpseclib/phpseclib": "^3.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0|^10.0", - "phpstan/phpstan": "^1.12.23", - "phpunit/phpunit": "^8.0|^9.3|^10.4|^11.5" - }, - "type": "library", - "extra": { - "laravel": { - "aliases": { - "Socialite": "Laravel\\Socialite\\Facades\\Socialite" - }, - "providers": [ - "Laravel\\Socialite\\SocialiteServiceProvider" - ] - }, - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "Laravel\\Socialite\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", - "homepage": "https://laravel.com", - "keywords": [ - "laravel", - "oauth" - ], - "support": { - "issues": "https://github.com/laravel/socialite/issues", - "source": "https://github.com/laravel/socialite" - }, - "time": "2025-07-23T14:16:08+00:00" - }, { "name": "laravel/tinker", "version": "v2.10.1", @@ -2605,82 +2470,6 @@ ], "time": "2024-09-21T08:32:55+00:00" }, - { - "name": "league/oauth1-client", - "version": "v1.11.0", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055", - "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-openssl": "*", - "guzzlehttp/guzzle": "^6.0|^7.0", - "guzzlehttp/psr7": "^1.7|^2.0", - "php": ">=7.1||>=8.0" - }, - "require-dev": { - "ext-simplexml": "*", - "friendsofphp/php-cs-fixer": "^2.17", - "mockery/mockery": "^1.3.3", - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5||9.5" - }, - "suggest": { - "ext-simplexml": "For decoding XML-based responses." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev", - "dev-develop": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "League\\OAuth1\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Corlett", - "email": "bencorlett@me.com", - "homepage": "http://www.webcomm.com.au", - "role": "Developer" - } - ], - "description": "OAuth 1.0 Client Library", - "keywords": [ - "Authentication", - "SSO", - "authorization", - "bitbucket", - "identity", - "idp", - "oauth", - "oauth1", - "single sign on", - "trello", - "tumblr", - "twitter" - ], - "support": { - "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0" - }, - "time": "2024-12-10T19:59:05+00:00" - }, { "name": "league/uri", "version": "7.5.1", @@ -3714,123 +3503,6 @@ ], "time": "2025-05-08T08:14:37+00:00" }, - { - "name": "paragonie/constant_time_encoding", - "version": "v3.0.0", - "source": { - "type": "git", - "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", - "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", - "shasum": "" - }, - "require": { - "php": "^8" - }, - "require-dev": { - "phpunit/phpunit": "^9", - "vimeo/psalm": "^4|^5" - }, - "type": "library", - "autoload": { - "psr-4": { - "ParagonIE\\ConstantTime\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com", - "role": "Maintainer" - }, - { - "name": "Steve 'Sc00bz' Thomas", - "email": "steve@tobtu.com", - "homepage": "https://www.tobtu.com", - "role": "Original Developer" - } - ], - "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", - "keywords": [ - "base16", - "base32", - "base32_decode", - "base32_encode", - "base64", - "base64_decode", - "base64_encode", - "bin2hex", - "encoding", - "hex", - "hex2bin", - "rfc4648" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/constant_time_encoding/issues", - "source": "https://github.com/paragonie/constant_time_encoding" - }, - "time": "2024-05-08T12:36:18+00:00" - }, - { - "name": "paragonie/random_compat", - "version": "v9.99.100", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", - "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", - "shasum": "" - }, - "require": { - "php": ">= 7" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" - ], - "support": { - "email": "info@paragonie.com", - "issues": "https://github.com/paragonie/random_compat/issues", - "source": "https://github.com/paragonie/random_compat" - }, - "time": "2020-10-15T08:29:30+00:00" - }, { "name": "phpoption/phpoption", "version": "1.9.3", @@ -3906,116 +3578,6 @@ ], "time": "2024-07-20T21:41:07+00:00" }, - { - "name": "phpseclib/phpseclib", - "version": "3.0.46", - "source": { - "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", - "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6", - "shasum": "" - }, - "require": { - "paragonie/constant_time_encoding": "^1|^2|^3", - "paragonie/random_compat": "^1.4|^2.0|^9.99.99", - "php": ">=5.6.1" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "suggest": { - "ext-dom": "Install the DOM extension to load XML formatted public keys.", - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." - }, - "type": "library", - "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], - "psr-4": { - "phpseclib3\\": "phpseclib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } - ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", - "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" - ], - "support": { - "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46" - }, - "funding": [ - { - "url": "https://github.com/terrafrost", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpseclib", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", - "type": "tidelift" - } - ], - "time": "2025-06-26T16:29:55+00:00" - }, { "name": "psr/clock", "version": "1.0.0", diff --git a/config/services.php b/config/services.php index 7fe0344..14b9dd1 100644 --- a/config/services.php +++ b/config/services.php @@ -50,16 +50,4 @@ return [ ], ], - 'oidc' => [ - 'enabled' => env('OIDC_ENABLED', false), - // OIDC_ENDPOINT can be either: - // - Base URL: https://your-provider.com (will append /.well-known/openid-configuration) - // - Full well-known URL: https://your-provider.com/.well-known/openid-configuration - 'endpoint' => env('OIDC_ENDPOINT'), - 'client_id' => env('OIDC_CLIENT_ID'), - 'client_secret' => env('OIDC_CLIENT_SECRET'), - 'redirect' => env('APP_URL', 'http://localhost:8000') . '/auth/oidc/callback', - 'scopes' => explode(',', env('OIDC_SCOPES', 'openid,profile,email')), - ], - ]; diff --git a/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php b/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php deleted file mode 100644 index d8dba38..0000000 --- a/database/migrations/2025_08_04_064514_add_oidc_sub_to_users_table.php +++ /dev/null @@ -1,28 +0,0 @@ -string('oidc_sub')->nullable()->unique()->after('email'); - }); - } - - /** - * Reverse the migrations. - */ - public function down(): void - { - Schema::table('users', function (Blueprint $table) { - $table->dropColumn('oidc_sub'); - }); - } -}; diff --git a/resources/views/livewire/auth/login.blade.php b/resources/views/livewire/auth/login.blade.php index 6f8488a..d1ea315 100644 --- a/resources/views/livewire/auth/login.blade.php +++ b/resources/views/livewire/auth/login.blade.php @@ -118,29 +118,6 @@ new #[Layout('components.layouts.auth')] class extends Component { - @if (config('services.oidc.enabled')) -
-
- -
-
- - {{ __('Or') }} - -
-
- -
- - {{ __('Continue with OIDC') }} - -
- @endif @if (Route::has('register'))
diff --git a/resources/views/livewire/device-dashboard.blade.php b/resources/views/livewire/device-dashboard.blade.php index 5db65d1..76ce414 100644 --- a/resources/views/livewire/device-dashboard.blade.php +++ b/resources/views/livewire/device-dashboard.blade.php @@ -95,11 +95,7 @@ new class extends Component { @elseif($current_image_path) -
-
- Current Image -
-
+ Current Image @endif
diff --git a/resources/views/livewire/devices/configure.blade.php b/resources/views/livewire/devices/configure.blade.php index 32a16f6..488e904 100644 --- a/resources/views/livewire/devices/configure.blade.php +++ b/resources/views/livewire/devices/configure.blade.php @@ -455,11 +455,7 @@ new class extends Component { @if(!$device->mirror_device_id) @if($current_image_path) -
-
- Next Image -
-
+ Next Image @endif diff --git a/routes/auth.php b/routes/auth.php index 49b2173..5647405 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -1,6 +1,5 @@ group(function () { Volt::route('reset-password/{token}', 'auth.reset-password') ->name('password.reset'); - // OIDC authentication routes - Route::get('auth/oidc/redirect', [OidcController::class, 'redirect']) - ->name('auth.oidc.redirect'); - - Route::get('auth/oidc/callback', [OidcController::class, 'callback']) - ->name('auth.oidc.callback'); - }); Route::middleware('auth')->group(function () { diff --git a/tests/Feature/Auth/OidcAuthenticationTest.php b/tests/Feature/Auth/OidcAuthenticationTest.php deleted file mode 100644 index 30d1bc2..0000000 --- a/tests/Feature/Auth/OidcAuthenticationTest.php +++ /dev/null @@ -1,158 +0,0 @@ -get(route('auth.oidc.redirect')); - - // Since we're using a mock OIDC provider, this will likely fail - // but we can check that the route exists and is accessible - $this->assertNotEquals(404, $response->getStatusCode()); - } - - public function test_oidc_redirect_fails_when_disabled() - { - Config::set('services.oidc.enabled', false); - - $response = $this->get(route('auth.oidc.redirect')); - - $response->assertRedirect(route('login')); - $response->assertSessionHasErrors(['oidc' => 'OIDC authentication is not enabled.']); - } - - public function test_oidc_callback_creates_new_user() - { - $mockUser = $this->mockSocialiteUser(); - - $response = $this->get(route('auth.oidc.callback')); - - // We expect to be redirected to dashboard after successful authentication - // In a real test, this would be mocked properly - $this->assertTrue(true); // Placeholder assertion - } - - public function test_oidc_callback_updates_existing_user_by_oidc_sub() - { - // Create a user with OIDC sub - $user = User::factory()->create([ - 'oidc_sub' => 'test-sub-123', - 'name' => 'Old Name', - 'email' => 'old@example.com', - ]); - - $mockUser = $this->mockSocialiteUser([ - 'id' => 'test-sub-123', - 'name' => 'Updated Name', - 'email' => 'updated@example.com', - ]); - - // This would need proper mocking of Socialite in a real test - $this->assertTrue(true); // Placeholder assertion - } - - public function test_oidc_callback_links_existing_user_by_email() - { - // Create a user without OIDC sub but with matching email - $user = User::factory()->create([ - 'oidc_sub' => null, - 'email' => 'test@example.com', - ]); - - $mockUser = $this->mockSocialiteUser([ - 'id' => 'test-sub-456', - 'email' => 'test@example.com', - ]); - - // This would need proper mocking of Socialite in a real test - $this->assertTrue(true); // Placeholder assertion - } - - public function test_oidc_callback_fails_when_disabled() - { - Config::set('services.oidc.enabled', false); - - $response = $this->get(route('auth.oidc.callback')); - - $response->assertRedirect(route('login')); - $response->assertSessionHasErrors(['oidc' => 'OIDC authentication is not enabled.']); - } - - public function test_login_view_shows_oidc_button_when_enabled() - { - $response = $this->get(route('login')); - - $response->assertStatus(200); - $response->assertSee('Continue with OIDC'); - $response->assertSee('Or'); - } - - public function test_login_view_hides_oidc_button_when_disabled() - { - Config::set('services.oidc.enabled', false); - - $response = $this->get(route('login')); - - $response->assertStatus(200); - $response->assertDontSee('Continue with OIDC'); - } - - public function test_user_model_has_oidc_sub_fillable() - { - $user = new User(); - - $this->assertContains('oidc_sub', $user->getFillable()); - } - - /** - * Mock a Socialite user for testing. - */ - protected function mockSocialiteUser(array $userData = []) - { - $defaultData = [ - 'id' => 'test-sub-123', - 'name' => 'Test User', - 'email' => 'test@example.com', - 'avatar' => null, - ]; - - $userData = array_merge($defaultData, $userData); - - $socialiteUser = Mockery::mock(SocialiteUser::class); - $socialiteUser->shouldReceive('getId')->andReturn($userData['id']); - $socialiteUser->shouldReceive('getName')->andReturn($userData['name']); - $socialiteUser->shouldReceive('getEmail')->andReturn($userData['email']); - $socialiteUser->shouldReceive('getAvatar')->andReturn($userData['avatar']); - - return $socialiteUser; - } - - protected function tearDown(): void - { - Mockery::close(); - parent::tearDown(); - } -} \ No newline at end of file diff --git a/tests/Feature/Devices/DeviceConfigureTest.php b/tests/Feature/DeviceConfigureTest.php similarity index 100% rename from tests/Feature/Devices/DeviceConfigureTest.php rename to tests/Feature/DeviceConfigureTest.php diff --git a/tests/Feature/Devices/DeviceRotationTest.php b/tests/Feature/Devices/DeviceRotationTest.php deleted file mode 100644 index 2f2374f..0000000 --- a/tests/Feature/Devices/DeviceRotationTest.php +++ /dev/null @@ -1,86 +0,0 @@ -create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'rotate' => 90, - 'current_screen_image' => 'test-image-uuid', - ]); - - // Mock the file existence check - \Illuminate\Support\Facades\Storage::fake('public'); - \Illuminate\Support\Facades\Storage::disk('public')->put('images/generated/test-image-uuid.png', 'fake-image-content'); - - $response = $this->actingAs($user) - ->get(route('dashboard')); - - $response->assertSuccessful(); - $response->assertSee('-rotate-[90deg]'); - $response->assertSee('origin-center'); -}); - -test('device configure page shows device image with correct rotation', function () { - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'rotate' => 90, - 'current_screen_image' => 'test-image-uuid', - ]); - - // Mock the file existence check - \Illuminate\Support\Facades\Storage::fake('public'); - \Illuminate\Support\Facades\Storage::disk('public')->put('images/generated/test-image-uuid.png', 'fake-image-content'); - - $response = $this->actingAs($user) - ->get(route('devices.configure', $device)); - - $response->assertSuccessful(); - $response->assertSee('-rotate-[90deg]'); - $response->assertSee('origin-center'); -}); - -test('device with no rotation shows no transform style', function () { - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'rotate' => 0, - 'current_screen_image' => 'test-image-uuid', - ]); - - // Mock the file existence check - \Illuminate\Support\Facades\Storage::fake('public'); - \Illuminate\Support\Facades\Storage::disk('public')->put('images/generated/test-image-uuid.png', 'fake-image-content'); - - $response = $this->actingAs($user) - ->get(route('dashboard')); - - $response->assertSuccessful(); - $response->assertSee('-rotate-[0deg]'); -}); - -test('device with null rotation defaults to 0', function () { - $user = User::factory()->create(); - $device = Device::factory()->create([ - 'user_id' => $user->id, - 'rotate' => null, - 'current_screen_image' => 'test-image-uuid', - ]); - - // Mock the file existence check - \Illuminate\Support\Facades\Storage::fake('public'); - \Illuminate\Support\Facades\Storage::disk('public')->put('images/generated/test-image-uuid.png', 'fake-image-content'); - - $response = $this->actingAs($user) - ->get(route('dashboard')); - - $response->assertSuccessful(); - $response->assertSee('-rotate-[0deg]'); -}); diff --git a/tests/Pest.php b/tests/Pest.php index 624dd1c..627fd57 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -17,10 +17,7 @@ pest()->extend(Tests\TestCase::class) registerSpatiePestHelpers(); -arch() - ->preset() - ->laravel() - ->ignoring(App\Http\Controllers\Auth\OidcController::class); +arch()->preset()->laravel(); arch() ->expect('App')