Skip to content

Commit 9687fde

Browse files
authored
feat: (LAR-107) Authentification avec Breeze (#216)
2 parents c1cdbcd + a8cd309 commit 9687fde

File tree

9 files changed

+383
-29
lines changed

9 files changed

+383
-29
lines changed

resources/views/livewire/pages/auth/forgot-password.blade.php

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,42 @@
1-
<x-app-layout :title="__('pages/auth.forgot.page_title')">
1+
<?php
2+
3+
use Illuminate\Support\Facades\Password;
4+
use Livewire\Attributes\Layout;
5+
use Livewire\Volt\Component;
6+
7+
new class extends Component
8+
{
9+
public string $email = '';
10+
11+
public function sendPasswordResetLink(): void
12+
{
13+
$this->validate([
14+
'email' => ['required', 'string', 'email'],
15+
]);
16+
17+
$status = Password::sendResetLink(
18+
$this->only('email')
19+
);
20+
21+
if ($status != Password::RESET_LINK_SENT) {
22+
$this->addError('email', __($status));
23+
24+
return;
25+
}
26+
27+
$this->reset('email');
28+
29+
session()->flash('status', __($status));
30+
}
31+
}; ?>
32+
33+
<div>
234
<div class="flex min-h-full items-center justify-center py-16 sm:py-24">
335
<div class="w-full max-w-md">
4-
<div>
5-
<x-status-message class="mb-5" />
36+
<div class="space-y-5">
37+
<x-status-message />
38+
39+
<x-validation-errors />
640

741
<h2 class="text-center font-heading text-3xl font-extrabold text-gray-900 dark:text-white sm:text-left">
842
{{ __('pages/auth.forgot.page_title') }}
@@ -11,10 +45,8 @@
1145
{{ __('pages/auth.forgot.description') }}
1246
</div>
1347
</div>
14-
15-
<form class="mt-8">
16-
@csrf
17-
48+
49+
<form class="mt-8" wire:submit="sendPasswordResetLink">
1850
<div class="block">
1951
<x-filament-forms::field-wrapper.label for="email">
2052
{{ __('validation.attributes.email') }}
@@ -24,6 +56,7 @@
2456
type="text"
2557
id="email"
2658
name="email"
59+
wire:model="email"
2760
autocomplete="email"
2861
required="true"
2962
:value="old('email')"
@@ -42,4 +75,4 @@
4275
</div>
4376

4477
<x-join-sponsors :title="__('global.sponsor_thanks')" />
45-
</x-app-layout>
78+
</div>

resources/views/livewire/pages/auth/login.blade.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ public function login(): void
2626
<div>
2727
<x-container class="flex min-h-full items-center justify-center py-16 sm:pt-24">
2828
<div class="w-full max-w-md space-y-8">
29-
<div>
29+
<div class="space-y-5">
3030
<x-validation-errors />
3131

32+
<x-status-message />
33+
3234
<h2 class="text-center font-heading text-3xl font-extrabold text-gray-900 dark:text-white">
3335
{{ __('pages/auth.login.title') }}
3436
</h2>
3537
</div>
38+
3639
<form class="space-y-6" wire:submit="login">
3740
<div class="space-y-4">
3841
<x-filament::input.wrapper>

resources/views/livewire/pages/auth/register.blade.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ public function register(): void
2525
->mixedCase()],
2626
]);
2727
28-
$validated['password'] = Hash::make($validated['password']);
28+
$user = User::create($validated);
2929
30-
event(new Registered(User::create($validated)));
30+
$user->assignRole('user');
31+
32+
event(new Registered($user));
3133
3234
session()->flash('status', __('pages/auth.register.email_verification_status'));
3335
}
@@ -175,4 +177,4 @@ public function register(): void
175177
</x-container>
176178

177179
<x-join-sponsors :title="__('global.sponsor_thanks')" />
178-
</div>
180+
</div>
Lines changed: 86 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,125 @@
1-
<x-app-layout :title="__('pages/auth.reset.page_title')">
1+
<?php
2+
3+
use Illuminate\Auth\Events\PasswordReset;
4+
use Illuminate\Support\Facades\Hash;
5+
use Illuminate\Support\Facades\Session;
6+
use Illuminate\Support\Str;
7+
use Illuminate\Validation\Rules;
8+
use Livewire\Attributes\Layout;
9+
use Livewire\Attributes\Locked;
10+
use Livewire\Volt\Component;
11+
use Illuminate\Support\Facades\Password;
12+
use Illuminate\Validation\Rules\Password as PasswordRules;
13+
14+
new class extends Component
15+
{
16+
#[Locked]
17+
public string $token = '';
18+
public string $email = '';
19+
public string $password = '';
20+
public string $password_confirmation = '';
21+
22+
/**
23+
* Mount the component.
24+
*/
25+
public function mount(string $token): void
26+
{
27+
$this->token = $token;
28+
29+
$this->email = request()->string('email');
30+
}
31+
32+
/**
33+
* Reset the password for the given user.
34+
*/
35+
public function resetPassword(): void
36+
{
37+
$this->validate([
38+
'token' => ['required'],
39+
'email' => ['required', 'string', 'email'],
40+
'password' => ['required', 'string', 'confirmed', PasswordRules::min(8)
41+
->uncompromised()
42+
->numbers()
43+
->mixedCase()],
44+
]);
45+
46+
$status = Password::reset(
47+
$this->only('email', 'password', 'password_confirmation', 'token'),
48+
function ($user) {
49+
$user->forceFill([
50+
'password' => Hash::make($this->password),
51+
'remember_token' => Str::random(60),
52+
])->save();
53+
54+
event(new PasswordReset($user));
55+
}
56+
);
57+
58+
if ($status != Password::PASSWORD_RESET) {
59+
$this->addError('email', __($status));
60+
61+
return;
62+
}
63+
64+
Session::flash('status', __($status));
65+
66+
$this->redirectRoute('login', navigate: true);
67+
}
68+
}; ?>
69+
70+
<div>
271
<div class="flex min-h-full items-center justify-center py-16 sm:py-24">
372
<div class="w-full max-w-md">
73+
74+
<x-status-message class="mb-5" />
75+
76+
<x-validation-errors class="mb-5"/>
77+
478
<h2 class="text-center font-heading text-3xl font-bold text-gray-900 sm:text-left">
579
{{ __('pages/auth.reset.page_title') }}
680
</h2>
781

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

1284
<div>
13-
<x-label for="email-address">
14-
{{ __('validation.attributes.email') }}
15-
</x-label>
1685
<x-filament::input.wrapper>
1786
<x-filament::input
1887
type="text"
1988
id="email-address"
2089
name="email"
90+
wire:model="email"
2191
autocomplete="email"
2292
required="true"
23-
:value="old('email', $request->email)"
93+
aria-label="{{ __('validation.attributes.email') }}"
94+
:placeholder="__('validation.attributes.email')"
2495
/>
2596
</x-filament::input.wrapper>
2697
</div>
2798

2899
<div>
29-
<x-label for="password">
30-
{{ __('validation.attributes.password') }}
31-
</x-label>
32100
<x-filament::input.wrapper>
33101
<x-filament::input
34102
type="password"
35103
id="password"
36104
name="password"
105+
wire:model="password"
37106
required="true"
107+
aria-label="{{ __('validation.attributes.password') }}"
108+
:placeholder="__('validation.attributes.password')"
38109
/>
39110
</x-filament::input.wrapper>
40111
</div>
41112

42113
<div>
43-
<x-label for="password_confirmation">
44-
{{ __('validation.attributes.password_confirmation') }}
45-
</x-label>
46114
<x-filament::input.wrapper>
47115
<x-filament::input
48116
type="password"
49117
id="password_confirmation"
50118
name="password_confirmation"
119+
wire:model="password_confirmation"
51120
required="true"
121+
aria-label="{{ __('validation.attributes.password_confirmation') }}"
122+
:placeholder="__('validation.attributes.password_confirmation')"
52123
/>
53124
</x-filament::input.wrapper>
54125
</div>
@@ -63,4 +134,4 @@
63134
</div>
64135

65136
<x-join-sponsors :title="__('global.sponsor_thanks')" />
66-
</x-app-layout>
137+
</div>

resources/views/livewire/pages/auth/verify-email.blade.php

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,35 @@
1-
<x-app-layout :title="__('pages/auth.verify.page_title')">
1+
<?php
2+
3+
use App\Livewire\Actions\Logout;
4+
use Illuminate\Support\Facades\Auth;
5+
use Illuminate\Support\Facades\Session;
6+
use Livewire\Attributes\Layout;
7+
use Livewire\Volt\Component;
8+
9+
new class extends Component
10+
{
11+
public function sendVerification(): void
12+
{
13+
if (Auth::user()->hasVerifiedEmail()) {
14+
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
15+
16+
return;
17+
}
18+
19+
Auth::user()->sendEmailVerificationNotification();
20+
21+
Session::flash('status', 'verification-link-sent');
22+
}
23+
24+
public function logout(Logout $logout): void
25+
{
26+
$logout();
27+
28+
$this->redirect('/', navigate: true);
29+
}
30+
}; ?>
31+
32+
<div>
233
<div class="flex min-h-screen flex-col items-center pt-6 sm:justify-center sm:pt-0">
334
<div class="mt-6 w-full sm:max-w-md lg:mt-10 lg:max-w-xl">
435
<p class="mb-4 text-center text-sm text-gray-500 dark:text-gray-400">
@@ -44,4 +75,4 @@ class="text-sm text-gray-500 dark:text-gray-400 underline hover:text-gray-900 fo
4475
</div>
4576

4677
<x-join-sponsors :title="__('global.sponsor_thanks')" />
47-
</x-app-layout>
78+
</div>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use App\Models\User;
6+
use Illuminate\Auth\Events\Verified;
7+
use Illuminate\Support\Facades\Event;
8+
use Illuminate\Support\Facades\URL;
9+
10+
test('email verification screen can be rendered', function (): void {
11+
$user = User::factory()->unverified()->create();
12+
13+
$response = $this->actingAs($user)->get('/verify-email');
14+
15+
$response->assertStatus(200);
16+
});
17+
18+
test('email can be verified', function (): void {
19+
$user = User::factory()->unverified()->create();
20+
21+
Event::fake();
22+
23+
$verificationUrl = URL::temporarySignedRoute(
24+
'verification.verify',
25+
now()->addMinutes(60),
26+
['id' => $user->id, 'hash' => sha1($user->email)]
27+
);
28+
29+
$response = $this->actingAs($user)->get($verificationUrl);
30+
31+
Event::assertDispatched(Verified::class);
32+
expect($user->fresh()->hasVerifiedEmail())->toBeTrue();
33+
$response->assertRedirect(route('dashboard', absolute: false).'?verified=1');
34+
});
35+
36+
test('email is not verified with invalid hash', function (): void {
37+
$user = User::factory()->unverified()->create();
38+
39+
$verificationUrl = URL::temporarySignedRoute(
40+
'verification.verify',
41+
now()->addMinutes(60),
42+
['id' => $user->id, 'hash' => sha1('wrong-email')]
43+
);
44+
45+
$this->actingAs($user)->get($verificationUrl);
46+
47+
expect($user->fresh()->hasVerifiedEmail())->toBeFalse();
48+
});
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Feature\Auth;
6+
7+
use App\Models\User;
8+
use Livewire\Volt\Volt;
9+
10+
test('confirm password screen can be rendered', function (): void {
11+
$user = User::factory()->create();
12+
13+
$response = $this->actingAs($user)->get('/confirm-password');
14+
15+
$response
16+
->assertSeeVolt('pages.auth.confirm-password')
17+
->assertStatus(200);
18+
});
19+
20+
test('password can be confirmed', function (): void {
21+
$user = User::factory()->create();
22+
23+
$this->actingAs($user);
24+
25+
$component = Volt::test('pages.auth.confirm-password')
26+
->set('password', 'password');
27+
28+
$component->call('confirmPassword');
29+
30+
$component
31+
->assertRedirect('/dashboard')
32+
->assertHasNoErrors();
33+
});
34+
35+
test('password is not confirmed with invalid password', function (): void {
36+
$user = User::factory()->create();
37+
38+
$this->actingAs($user);
39+
40+
$component = Volt::test('pages.auth.confirm-password')
41+
->set('password', 'wrong-password');
42+
43+
$component->call('confirmPassword');
44+
45+
$component
46+
->assertNoRedirect()
47+
->assertHasErrors('password');
48+
});

0 commit comments

Comments
 (0)