Skip to content

feat: (LAR-107) Authentification avec Breeze #216

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions resources/views/livewire/pages/auth/forgot-password.blade.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
<x-app-layout :title="__('pages/auth.forgot.page_title')">
<?php

use Illuminate\Support\Facades\Password;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;

new class extends Component
{
public string $email = '';

public function sendPasswordResetLink(): void
{
$this->validate([
'email' => ['required', 'string', 'email'],
]);

$status = Password::sendResetLink(
$this->only('email')
);

if ($status != Password::RESET_LINK_SENT) {
$this->addError('email', __($status));

return;
}

$this->reset('email');

session()->flash('status', __($status));
}
}; ?>

<div>
<div class="flex min-h-full items-center justify-center py-16 sm:py-24">
<div class="w-full max-w-md">
<div>
<x-status-message class="mb-5" />
<div class="space-y-5">
<x-status-message />

<x-validation-errors />

<h2 class="text-center font-heading text-3xl font-extrabold text-gray-900 dark:text-white sm:text-left">
{{ __('pages/auth.forgot.page_title') }}
Expand All @@ -11,10 +45,8 @@
{{ __('pages/auth.forgot.description') }}
</div>
</div>

<form class="mt-8">
@csrf


<form class="mt-8" wire:submit="sendPasswordResetLink">
<div class="block">
<x-filament-forms::field-wrapper.label for="email">
{{ __('validation.attributes.email') }}
Expand All @@ -24,6 +56,7 @@
type="text"
id="email"
name="email"
wire:model="email"
autocomplete="email"
required="true"
:value="old('email')"
Expand All @@ -42,4 +75,4 @@
</div>

<x-join-sponsors :title="__('global.sponsor_thanks')" />
</x-app-layout>
</div>
5 changes: 4 additions & 1 deletion resources/views/livewire/pages/auth/login.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ public function login(): void
<div>
<x-container class="flex min-h-full items-center justify-center py-16 sm:pt-24">
<div class="w-full max-w-md space-y-8">
<div>
<div class="space-y-5">
<x-validation-errors />

<x-status-message />

<h2 class="text-center font-heading text-3xl font-extrabold text-gray-900 dark:text-white">
{{ __('pages/auth.login.title') }}
</h2>
</div>

<form class="space-y-6" wire:submit="login">
<div class="space-y-4">
<x-filament::input.wrapper>
Expand Down
8 changes: 5 additions & 3 deletions resources/views/livewire/pages/auth/register.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ public function register(): void
->mixedCase()],
]);

$validated['password'] = Hash::make($validated['password']);
$user = User::create($validated);

event(new Registered(User::create($validated)));
$user->assignRole('user');

event(new Registered($user));

session()->flash('status', __('pages/auth.register.email_verification_status'));
}
Expand Down Expand Up @@ -175,4 +177,4 @@ public function register(): void
</x-container>

<x-join-sponsors :title="__('global.sponsor_thanks')" />
</div>
</div>
101 changes: 86 additions & 15 deletions resources/views/livewire/pages/auth/reset-password.blade.php
Original file line number Diff line number Diff line change
@@ -1,54 +1,125 @@
<x-app-layout :title="__('pages/auth.reset.page_title')">
<?php

use Illuminate\Auth\Events\PasswordReset;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Volt\Component;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\Rules\Password as PasswordRules;

new class extends Component
{
#[Locked]
public string $token = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';

/**
* Mount the component.
*/
public function mount(string $token): void
{
$this->token = $token;

$this->email = request()->string('email');
}

/**
* Reset the password for the given user.
*/
public function resetPassword(): void
{
$this->validate([
'token' => ['required'],
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string', 'confirmed', PasswordRules::min(8)
->uncompromised()
->numbers()
->mixedCase()],
]);

$status = Password::reset(
$this->only('email', 'password', 'password_confirmation', 'token'),
function ($user) {
$user->forceFill([
'password' => Hash::make($this->password),
'remember_token' => Str::random(60),
])->save();

event(new PasswordReset($user));
}
);

if ($status != Password::PASSWORD_RESET) {
$this->addError('email', __($status));

return;
}

Session::flash('status', __($status));

$this->redirectRoute('login', navigate: true);
}
}; ?>

<div>
<div class="flex min-h-full items-center justify-center py-16 sm:py-24">
<div class="w-full max-w-md">

<x-status-message class="mb-5" />

<x-validation-errors class="mb-5"/>

<h2 class="text-center font-heading text-3xl font-bold text-gray-900 sm:text-left">
{{ __('pages/auth.reset.page_title') }}
</h2>

<form action="{{ route('password.update') }}" method="POST" class="mt-8 space-y-6">
@csrf
<input type="hidden" name="token" value="{{ $request->route('token') }}" />
<form wire:submit="resetPassword" class="mt-8 space-y-6">

<div>
<x-label for="email-address">
{{ __('validation.attributes.email') }}
</x-label>
<x-filament::input.wrapper>
<x-filament::input
type="text"
id="email-address"
name="email"
wire:model="email"
autocomplete="email"
required="true"
:value="old('email', $request->email)"
aria-label="{{ __('validation.attributes.email') }}"
:placeholder="__('validation.attributes.email')"
/>
</x-filament::input.wrapper>
</div>

<div>
<x-label for="password">
{{ __('validation.attributes.password') }}
</x-label>
<x-filament::input.wrapper>
<x-filament::input
type="password"
id="password"
name="password"
wire:model="password"
required="true"
aria-label="{{ __('validation.attributes.password') }}"
:placeholder="__('validation.attributes.password')"
/>
</x-filament::input.wrapper>
</div>

<div>
<x-label for="password_confirmation">
{{ __('validation.attributes.password_confirmation') }}
</x-label>
<x-filament::input.wrapper>
<x-filament::input
type="password"
id="password_confirmation"
name="password_confirmation"
wire:model="password_confirmation"
required="true"
aria-label="{{ __('validation.attributes.password_confirmation') }}"
:placeholder="__('validation.attributes.password_confirmation')"
/>
</x-filament::input.wrapper>
</div>
Expand All @@ -63,4 +134,4 @@
</div>

<x-join-sponsors :title="__('global.sponsor_thanks')" />
</x-app-layout>
</div>
35 changes: 33 additions & 2 deletions resources/views/livewire/pages/auth/verify-email.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,35 @@
<x-app-layout :title="__('pages/auth.verify.page_title')">
<?php

use App\Livewire\Actions\Logout;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
use Livewire\Attributes\Layout;
use Livewire\Volt\Component;

new class extends Component
{
public function sendVerification(): void
{
if (Auth::user()->hasVerifiedEmail()) {
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);

return;
}

Auth::user()->sendEmailVerificationNotification();

Session::flash('status', 'verification-link-sent');
}

public function logout(Logout $logout): void
{
$logout();

$this->redirect('/', navigate: true);
}
}; ?>

<div>
<div class="flex min-h-screen flex-col items-center pt-6 sm:justify-center sm:pt-0">
<div class="mt-6 w-full sm:max-w-md lg:mt-10 lg:max-w-xl">
<p class="mb-4 text-center text-sm text-gray-500 dark:text-gray-400">
Expand Down Expand Up @@ -44,4 +75,4 @@ class="text-sm text-gray-500 dark:text-gray-400 underline hover:text-gray-900 fo
</div>

<x-join-sponsors :title="__('global.sponsor_thanks')" />
</x-app-layout>
</div>
48 changes: 48 additions & 0 deletions tests/Feature/Auth/EmailVerificationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

use App\Models\User;
use Illuminate\Auth\Events\Verified;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\URL;

test('email verification screen can be rendered', function (): void {
$user = User::factory()->unverified()->create();

$response = $this->actingAs($user)->get('/verify-email');

$response->assertStatus(200);
});

test('email can be verified', function (): void {
$user = User::factory()->unverified()->create();

Event::fake();

$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1($user->email)]
);

$response = $this->actingAs($user)->get($verificationUrl);

Event::assertDispatched(Verified::class);
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
});

test('email is not verified with invalid hash', function (): void {
$user = User::factory()->unverified()->create();

$verificationUrl = URL::temporarySignedRoute(
'verification.verify',
now()->addMinutes(60),
['id' => $user->id, 'hash' => sha1('wrong-email')]
);

$this->actingAs($user)->get($verificationUrl);

expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
});
48 changes: 48 additions & 0 deletions tests/Feature/Auth/PasswordConfirmationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace Tests\Feature\Auth;

use App\Models\User;
use Livewire\Volt\Volt;

test('confirm password screen can be rendered', function (): void {
$user = User::factory()->create();

$response = $this->actingAs($user)->get('/confirm-password');

$response
->assertSeeVolt('pages.auth.confirm-password')
->assertStatus(200);
});

test('password can be confirmed', function (): void {
$user = User::factory()->create();

$this->actingAs($user);

$component = Volt::test('pages.auth.confirm-password')
->set('password', 'password');

$component->call('confirmPassword');

$component
->assertRedirect('/dashboard')
->assertHasNoErrors();
});

test('password is not confirmed with invalid password', function (): void {
$user = User::factory()->create();

$this->actingAs($user);

$component = Volt::test('pages.auth.confirm-password')
->set('password', 'wrong-password');

$component->call('confirmPassword');

$component
->assertNoRedirect()
->assertHasErrors('password');
});
Loading
Loading