diff --git a/app/Livewire/Settings/Appearance.php b/app/Livewire/Settings/Appearance.php deleted file mode 100644 index 7550e32..0000000 --- a/app/Livewire/Settings/Appearance.php +++ /dev/null @@ -1,12 +0,0 @@ -validate([ - 'password' => $this->currentPasswordRules(), - ]); - - tap(Auth::user(), $logout(...))->delete(); - - $this->redirect('/', navigate: true); - } -} diff --git a/app/Livewire/Settings/Profile.php b/app/Livewire/Settings/Profile.php deleted file mode 100644 index db7bf31..0000000 --- a/app/Livewire/Settings/Profile.php +++ /dev/null @@ -1,81 +0,0 @@ -name = Auth::user()->name; - $this->email = Auth::user()->email; - } - - /** - * Update the profile information for the currently authenticated user. - */ - public function updateProfileInformation(): void - { - $user = Auth::user(); - - $validated = $this->validate($this->profileRules($user->id)); - - $user->fill($validated); - - if ($user->isDirty('email')) { - $user->email_verified_at = null; - } - - $user->save(); - - Flux::toast(variant: 'success', text: __('Profile updated.')); - } - - /** - * Send an email verification notification to the current user. - */ - public function resendVerificationNotification(): void - { - $user = Auth::user(); - - if ($user->hasVerifiedEmail()) { - $this->redirectIntended(default: route('dashboard', absolute: false)); - - return; - } - - $user->sendEmailVerificationNotification(); - - Flux::toast(text: __('A new verification link has been sent to your email address.')); - } - - #[Computed] - public function hasUnverifiedEmail(): bool - { - return Auth::user() instanceof MustVerifyEmail && ! Auth::user()->hasVerifiedEmail(); - } - - #[Computed] - public function showDeleteUser(): bool - { - return ! Auth::user() instanceof MustVerifyEmail - || (Auth::user() instanceof MustVerifyEmail && Auth::user()->hasVerifiedEmail()); - } -} diff --git a/app/Livewire/Settings/Security.php b/app/Livewire/Settings/Security.php deleted file mode 100644 index 3edf160..0000000 --- a/app/Livewire/Settings/Security.php +++ /dev/null @@ -1,225 +0,0 @@ -canManageTwoFactor = Features::canManageTwoFactorAuthentication(); - - if ($this->canManageTwoFactor) { - if (Fortify::confirmsTwoFactorAuthentication() && is_null(auth()->user()->two_factor_confirmed_at)) { - $disableTwoFactorAuthentication(auth()->user()); - } - - $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); - $this->requiresConfirmation = Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'); - } - } - - /** - * Update the password for the currently authenticated user. - */ - public function updatePassword(): void - { - try { - $validated = $this->validate([ - 'current_password' => $this->currentPasswordRules(), - 'password' => $this->passwordRules(), - ]); - } catch (ValidationException $e) { - $this->reset('current_password', 'password', 'password_confirmation'); - - throw $e; - } - - Auth::user()->update([ - 'password' => $validated['password'], - ]); - - $this->reset('current_password', 'password', 'password_confirmation'); - - Flux::toast(variant: 'success', text: __('Password updated.')); - } - - /** - * Enable two-factor authentication for the user. - */ - public function enable(EnableTwoFactorAuthentication $enableTwoFactorAuthentication): void - { - $enableTwoFactorAuthentication(auth()->user()); - - if (! $this->requiresConfirmation) { - $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); - } - - $this->loadSetupData(); - - $this->showModal = true; - } - - /** - * Load the two-factor authentication setup data for the user. - */ - private function loadSetupData(): void - { - $user = auth()->user(); - - try { - $this->qrCodeSvg = $user?->twoFactorQrCodeSvg(); - $this->manualSetupKey = decrypt($user->two_factor_secret); - } catch (Exception) { - $this->addError('setupData', 'Failed to fetch setup data.'); - - $this->reset('qrCodeSvg', 'manualSetupKey'); - } - } - - /** - * Show the two-factor verification step if necessary. - */ - public function showVerificationIfNecessary(): void - { - if ($this->requiresConfirmation) { - $this->showVerificationStep = true; - - $this->resetErrorBag(); - - return; - } - - $this->closeModal(); - } - - /** - * Close the two-factor authentication modal. - */ - public function closeModal(): void - { - $this->reset( - 'code', - 'manualSetupKey', - 'qrCodeSvg', - 'showModal', - 'showVerificationStep', - ); - - $this->resetErrorBag(); - - if (! $this->requiresConfirmation) { - $this->twoFactorEnabled = auth()->user()->hasEnabledTwoFactorAuthentication(); - } - } - - /** - * Confirm two-factor authentication for the user. - */ - public function confirmTwoFactor(ConfirmTwoFactorAuthentication $confirmTwoFactorAuthentication): void - { - $this->validate(); - - $confirmTwoFactorAuthentication(auth()->user(), $this->code); - - $this->closeModal(); - - $this->twoFactorEnabled = true; - } - - /** - * Reset two-factor verification state. - */ - public function resetVerification(): void - { - $this->reset('code', 'showVerificationStep'); - - $this->resetErrorBag(); - } - - /** - * Disable two-factor authentication for the user. - */ - public function disable(DisableTwoFactorAuthentication $disableTwoFactorAuthentication): void - { - $disableTwoFactorAuthentication(auth()->user()); - - $this->twoFactorEnabled = false; - } - - /** - * Get the current modal configuration state. - */ - public function getModalConfigProperty(): array - { - if ($this->twoFactorEnabled) { - return [ - 'title' => __('Two-factor authentication enabled'), - 'description' => __('Two-factor authentication is now enabled. Scan the QR code or enter the setup key in your authenticator app.'), - 'buttonText' => __('Close'), - ]; - } - - if ($this->showVerificationStep) { - return [ - 'title' => __('Verify authentication code'), - 'description' => __('Enter the 6-digit code from your authenticator app.'), - 'buttonText' => __('Continue'), - ]; - } - - return [ - 'title' => __('Enable two-factor authentication'), - 'description' => __('To finish enabling two-factor authentication, scan the QR code or enter the setup key in your authenticator app.'), - 'buttonText' => __('Continue'), - ]; - } -} diff --git a/app/Livewire/Settings/TwoFactor/RecoveryCodes.php b/app/Livewire/Settings/TwoFactor/RecoveryCodes.php deleted file mode 100644 index ae0acba..0000000 --- a/app/Livewire/Settings/TwoFactor/RecoveryCodes.php +++ /dev/null @@ -1,50 +0,0 @@ -loadRecoveryCodes(); - } - - /** - * Load the recovery codes for the user. - */ - private function loadRecoveryCodes(): void - { - $user = auth()->user(); - - if ($user->hasEnabledTwoFactorAuthentication() && $user->two_factor_recovery_codes) { - try { - $this->recoveryCodes = json_decode(decrypt($user->two_factor_recovery_codes), true); - } catch (Exception) { - $this->addError('recoveryCodes', 'Failed to load recovery codes'); - - $this->recoveryCodes = []; - } - } - } - - /** - * Generate new recovery codes for the user. - */ - public function regenerateRecoveryCodes(GenerateNewRecoveryCodes $generateNewRecoveryCodes): void - { - $generateNewRecoveryCodes(auth()->user()); - - $this->loadRecoveryCodes(); - } -} diff --git a/resources/views/components/settings/layout.blade.php b/resources/views/components/settings/layout.blade.php deleted file mode 100644 index 3a65247..0000000 --- a/resources/views/components/settings/layout.blade.php +++ /dev/null @@ -1,20 +0,0 @@ -
-
- - {{ __('Profile') }} - {{ __('Security') }} - {{ __('Appearance') }} - -
- - - -
- {{ $heading ?? '' }} - {{ $subheading ?? '' }} - -
- {{ $slot }} -
-
-
diff --git a/resources/views/livewire/settings/appearance.blade.php b/resources/views/livewire/settings/appearance.blade.php deleted file mode 100644 index 86c7ec7..0000000 --- a/resources/views/livewire/settings/appearance.blade.php +++ /dev/null @@ -1,13 +0,0 @@ -
- @include('partials.settings-heading') - - {{ __('Appearance settings') }} - - - - {{ __('Light') }} - {{ __('Dark') }} - {{ __('System') }} - - -
diff --git a/resources/views/livewire/settings/delete-user-form.blade.php b/resources/views/livewire/settings/delete-user-form.blade.php deleted file mode 100644 index a641407..0000000 --- a/resources/views/livewire/settings/delete-user-form.blade.php +++ /dev/null @@ -1,34 +0,0 @@ -
-
- {{ __('Delete account') }} - {{ __('Delete your account and all of its resources') }} -
- - - - {{ __('Delete account') }} - - - - -
-
- {{ __('Are you sure you want to delete your account?') }} - - - {{ __('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.') }} - -
- - - -
- - {{ __('Cancel') }} - - - {{ __('Delete account') }} -
- -
-
diff --git a/resources/views/livewire/settings/profile.blade.php b/resources/views/livewire/settings/profile.blade.php deleted file mode 100644 index 6e9da81..0000000 --- a/resources/views/livewire/settings/profile.blade.php +++ /dev/null @@ -1,36 +0,0 @@ -
- @include('partials.settings-heading') - - {{ __('Profile settings') }} - - -
- - -
- - - @if ($this->hasUnverifiedEmail) -
- - {{ __('Your email address is unverified.') }} - - - {{ __('Click here to re-send the verification email.') }} - - - -
- @endif -
- -
- {{ __('Save') }} -
- - - @if ($this->showDeleteUser) - - @endif -
-
diff --git a/resources/views/livewire/settings/security.blade.php b/resources/views/livewire/settings/security.blade.php deleted file mode 100644 index 7beea38..0000000 --- a/resources/views/livewire/settings/security.blade.php +++ /dev/null @@ -1,233 +0,0 @@ -
- @include('partials.settings-heading') - - {{ __('Security settings') }} - - -
- - - - -
- {{ __('Save') }} -
- - - @if ($canManageTwoFactor) -
- {{ __('Two-factor authentication') }} - {{ __('Manage your two-factor authentication settings') }} - -
- @if ($twoFactorEnabled) -
- - {{ __('You will be prompted for a secure, random pin during login, which you can retrieve from the TOTP-supported application on your phone.') }} - - -
- - {{ __('Disable 2FA') }} - -
- - -
- @else -
- - {{ __('When you enable two-factor authentication, you will be prompted for a secure pin during login. This pin can be retrieved from a TOTP-supported application on your phone.') }} - - - - {{ __('Enable 2FA') }} - -
- @endif -
-
- - -
-
-
-
-
- @for ($i = 1; $i <= 5; $i++) -
- @endfor -
- -
- @for ($i = 1; $i <= 5; $i++) -
- @endfor -
- - -
-
- -
- {{ $this->modalConfig['title'] }} - {{ $this->modalConfig['description'] }} -
-
- - @if ($showVerificationStep) -
-
- -
- -
- - {{ __('Back') }} - - - - {{ __('Confirm') }} - -
-
- @else - @error('setupData') - - @enderror - -
-
- @empty($qrCodeSvg) -
- -
- @else -
-
- {!! $qrCodeSvg !!} -
-
- @endempty -
-
- -
- - {{ $this->modalConfig['buttonText'] }} - -
- -
-
-
- - {{ __('or, enter the code manually') }} - -
- -
-
- @empty($manualSetupKey) -
- -
- @else - - - - @endempty -
-
-
- @endif -
-
- @endif -
-
diff --git a/resources/views/livewire/settings/two-factor/recovery-codes.blade.php b/resources/views/livewire/settings/two-factor/recovery-codes.blade.php deleted file mode 100644 index 9ed2674..0000000 --- a/resources/views/livewire/settings/two-factor/recovery-codes.blade.php +++ /dev/null @@ -1,89 +0,0 @@ -
-
-
- - {{ __('2FA recovery codes') }} -
- - {{ __('Recovery codes let you regain access if you lose your 2FA device. Store them in a secure password manager.') }} - -
- -
-
- - - - {{ __('Hide recovery codes') }} - - - @if (filled($recoveryCodes)) - - {{ __('Regenerate codes') }} - - @endif -
- -
-
- @error('recoveryCodes') - - @enderror - - @if (filled($recoveryCodes)) -
- @foreach($recoveryCodes as $code) -
- {{ $code }} -
- @endforeach -
- - {{ __('Each recovery code can be used once to access your account and will be removed after use. If you need more, click Regenerate codes above.') }} - - @endif -
-
-
-
diff --git a/routes/settings.php b/routes/settings.php deleted file mode 100644 index d533e34..0000000 --- a/routes/settings.php +++ /dev/null @@ -1,28 +0,0 @@ -group(function () { - Route::redirect('settings', 'settings/profile'); - - Route::livewire('settings/profile', Profile::class)->name('profile.edit'); -}); - -Route::middleware(['auth', 'verified'])->group(function () { - Route::livewire('settings/appearance', Appearance::class)->name('appearance.edit'); - - Route::livewire('settings/security', Security::class) - ->middleware( - when( - Features::canManageTwoFactorAuthentication() - && Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'), - ['password.confirm'], - [], - ), - ) - ->name('security.edit'); -}); diff --git a/routes/web.php b/routes/web.php index 6b8bdc7..0ba2f7e 100644 --- a/routes/web.php +++ b/routes/web.php @@ -2,8 +2,6 @@ use Illuminate\Support\Facades\Route; -require __DIR__.'/settings.php'; - // Named dashboard route so route('dashboard') resolves; Vue Router handles rendering Route::get('/dashboard', fn () => view('app'))->middleware(['auth', 'verified'])->name('dashboard'); diff --git a/tests/Feature/Settings/ProfileUpdateTest.php b/tests/Feature/Settings/ProfileUpdateTest.php deleted file mode 100644 index e0b3742..0000000 --- a/tests/Feature/Settings/ProfileUpdateTest.php +++ /dev/null @@ -1,75 +0,0 @@ -actingAs($user = User::factory()->create()); - - $this->get(route('profile.edit'))->assertOk(); -}); - -test('profile information can be updated', function () { - $user = User::factory()->create(); - - $this->actingAs($user); - - $response = Livewire::test('pages::settings.profile') - ->set('name', 'Test User') - ->set('email', 'test@example.com') - ->call('updateProfileInformation'); - - $response->assertHasNoErrors(); - - $user->refresh(); - - expect($user->name)->toEqual('Test User'); - expect($user->email)->toEqual('test@example.com'); - expect($user->email_verified_at)->toBeNull(); -}); - -test('email verification status is unchanged when email address is unchanged', function () { - $user = User::factory()->create(); - - $this->actingAs($user); - - $response = Livewire::test('pages::settings.profile') - ->set('name', 'Test User') - ->set('email', $user->email) - ->call('updateProfileInformation'); - - $response->assertHasNoErrors(); - - expect($user->refresh()->email_verified_at)->not->toBeNull(); -}); - -test('user can delete their account', function () { - $user = User::factory()->create(); - - $this->actingAs($user); - - $response = Livewire::test('pages::settings.delete-user-modal') - ->set('password', 'password') - ->call('deleteUser'); - - $response - ->assertHasNoErrors() - ->assertRedirect('/'); - - expect($user->fresh())->toBeNull(); - expect(auth()->check())->toBeFalse(); -}); - -test('correct password must be provided to delete account', function () { - $user = User::factory()->create(); - - $this->actingAs($user); - - $response = Livewire::test('pages::settings.delete-user-modal') - ->set('password', 'wrong-password') - ->call('deleteUser'); - - $response->assertHasErrors(['password']); - - expect($user->fresh())->not->toBeNull(); -}); \ No newline at end of file diff --git a/tests/Feature/Settings/SecurityTest.php b/tests/Feature/Settings/SecurityTest.php deleted file mode 100644 index fbf95c5..0000000 --- a/tests/Feature/Settings/SecurityTest.php +++ /dev/null @@ -1,104 +0,0 @@ -skipUnlessFortifyHas(Features::twoFactorAuthentication()); - - Features::twoFactorAuthentication([ - 'confirm' => true, - 'confirmPassword' => true, - ]); -}); - -test('security settings page can be rendered', function () { - $user = User::factory()->create(); - - $this->actingAs($user) - ->withSession(['auth.password_confirmed_at' => time()]) - ->get(route('security.edit')) - ->assertOk() - ->assertSee('Two-factor authentication') - ->assertSee('Enable 2FA'); -}); - -test('security settings page requires password confirmation when enabled', function () { - $user = User::factory()->create(); - - $response = $this->actingAs($user) - ->get(route('security.edit')); - - $response->assertRedirect(route('password.confirm')); -}); - -test('security settings page renders without two factor when feature is disabled', function () { - config(['fortify.features' => []]); - - $user = User::factory()->create(); - - $this->actingAs($user) - ->withSession(['auth.password_confirmed_at' => time()]) - ->get(route('security.edit')) - ->assertOk() - ->assertSee('Update password') - ->assertDontSee('Two-factor authentication'); -}); - -test('two factor authentication disabled when confirmation abandoned between requests', function () { - $user = User::factory()->create(); - - $user->forceFill([ - 'two_factor_secret' => encrypt('test-secret'), - 'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])), - 'two_factor_confirmed_at' => null, - ])->save(); - - $this->actingAs($user); - - $component = Livewire::test('pages::settings.security'); - - $component->assertSet('twoFactorEnabled', false); - - $this->assertDatabaseHas('users', [ - 'id' => $user->id, - 'two_factor_secret' => null, - 'two_factor_recovery_codes' => null, - ]); -}); - -test('password can be updated', function () { - $user = User::factory()->create([ - 'password' => Hash::make('password'), - ]); - - $this->actingAs($user); - - $response = Livewire::test('pages::settings.security') - ->set('current_password', 'password') - ->set('password', 'new-password') - ->set('password_confirmation', 'new-password') - ->call('updatePassword'); - - $response->assertHasNoErrors(); - - expect(Hash::check('new-password', $user->refresh()->password))->toBeTrue(); -}); - -test('correct password must be provided to update password', function () { - $user = User::factory()->create([ - 'password' => Hash::make('password'), - ]); - - $this->actingAs($user); - - $response = Livewire::test('pages::settings.security') - ->set('current_password', 'wrong-password') - ->set('password', 'new-password') - ->set('password_confirmation', 'new-password') - ->call('updatePassword'); - - $response->assertHasErrors(['current_password']); -}); \ No newline at end of file