Skip to content

Commit b9bf608

Browse files
authored
fix: authentication tests and PHPStan errors after Flux migration (#448)
1 parent 3f57556 commit b9bf608

File tree

68 files changed

+1091
-934
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+1091
-934
lines changed

app/Enums/UserRole.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Enums;
6+
7+
enum UserRole: string
8+
{
9+
case Admin = 'admin';
10+
11+
case Company = 'company';
12+
13+
case Moderator = 'moderator';
14+
15+
case User = 'user';
16+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Forms;
6+
7+
use App\Enums\UserRole;
8+
use App\Models\User;
9+
use Illuminate\Auth\Events\Registered;
10+
use Illuminate\Support\Facades\Hash;
11+
use Illuminate\Validation\Rules\Password;
12+
use Livewire\Form;
13+
14+
final class RegisterForm extends Form
15+
{
16+
public string $name = '';
17+
18+
public string $email = '';
19+
20+
public string $password = '';
21+
22+
public string $username = '';
23+
24+
public function createUser(): void
25+
{
26+
$password = Hash::make($this->password);
27+
28+
$user = User::query()->create([
29+
'password' => $password,
30+
...$this->only(['name', 'username', 'email']),
31+
]);
32+
33+
$user->assignRole(UserRole::User->value);
34+
35+
event(new Registered($user));
36+
}
37+
38+
protected function rules(): array
39+
{
40+
$emailRules = ['required', 'string', 'lowercase', 'email:rfc', 'max:255', 'unique:users'];
41+
42+
if (! app()->environment('testing')) {
43+
$emailRules = ['required', 'string', 'lowercase', 'email:rfc,dns', 'max:255', 'unique:users'];
44+
}
45+
46+
return [
47+
'name' => ['required', 'string', 'max:255'],
48+
'email' => $emailRules,
49+
'username' => ['required', 'string', 'max:30', 'unique:users', 'lowercase'],
50+
'password' => [
51+
'required',
52+
'string',
53+
Password::min(8)
54+
->uncompromised()
55+
->numbers()
56+
->mixedCase(),
57+
],
58+
];
59+
}
60+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Pages\Auth;
6+
7+
use Flux\Flux;
8+
use Illuminate\Contracts\View\View;
9+
use Illuminate\Support\Facades\Password;
10+
use Livewire\Attributes\Layout;
11+
use Livewire\Attributes\Validate;
12+
use Livewire\Component;
13+
14+
#[Layout('layouts.base')]
15+
final class ForgotPassword extends Component
16+
{
17+
#[Validate('required|string|email')]
18+
public string $email = '';
19+
20+
public function sendPasswordResetLink(): void
21+
{
22+
$this->validate();
23+
24+
$status = Password::sendResetLink(
25+
$this->only('email')
26+
);
27+
28+
if ($status !== Password::RESET_LINK_SENT) {
29+
$this->addError('email', __($status));
30+
31+
return;
32+
}
33+
34+
$this->reset('email');
35+
36+
Flux::toast(text: __($status), variant: 'success');
37+
}
38+
39+
public function render(): View
40+
{
41+
return view('livewire.pages.auth.forgot-password')
42+
->title(__('pages/auth.forgot.page_title'));
43+
}
44+
}

app/Livewire/Pages/Auth/Login.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Pages\Auth;
6+
7+
use App\Livewire\Forms\LoginForm;
8+
use Illuminate\Contracts\View\View;
9+
use Illuminate\Support\Facades\Session;
10+
use Livewire\Attributes\Layout;
11+
use Livewire\Component;
12+
13+
#[Layout('layouts.base')]
14+
final class Login extends Component
15+
{
16+
public LoginForm $form;
17+
18+
public function login(): void
19+
{
20+
$this->validate();
21+
22+
$this->form->authenticate();
23+
24+
Session::regenerate();
25+
26+
$this->redirectIntended(default: route('dashboard', absolute: false), navigate: true);
27+
}
28+
29+
public function render(): View
30+
{
31+
return view('livewire.pages.auth.login')
32+
->title(__('pages/auth.login.page_title'));
33+
}
34+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Pages\Auth;
6+
7+
use App\Livewire\Forms\RegisterForm;
8+
use App\Models\User;
9+
use Flux\Flux;
10+
use Illuminate\Contracts\View\View;
11+
use Illuminate\Database\Eloquent\Collection;
12+
use Illuminate\Support\Facades\Cache;
13+
use Livewire\Attributes\Computed;
14+
use Livewire\Attributes\Layout;
15+
use Livewire\Component;
16+
17+
/**
18+
* @property-read Collection<int, User> $users
19+
*/
20+
#[Layout('layouts.base')]
21+
final class Register extends Component
22+
{
23+
public RegisterForm $form;
24+
25+
public function register(): void
26+
{
27+
$this->form->validate();
28+
29+
$this->form->createUser();
30+
31+
Flux::toast(
32+
text: __('pages/auth.register.email_verification_status'),
33+
heading: __('pages/auth.register.email_verification_title'),
34+
variant: 'success'
35+
);
36+
37+
$this->form->reset();
38+
}
39+
40+
#[Computed(persist: true, seconds: 3600 * 7, cache: true, key: 'registration.users')]
41+
public function users(): Collection
42+
{
43+
/** @var Collection<int, User> $users */
44+
$users = Cache::remember(
45+
key: 'avatars',
46+
ttl: now()->addWeek(),
47+
callback: fn (): Collection => User::query() // @phpstan-ignore-line
48+
->select('id', 'email_verified_at', 'username', 'avatar_type', 'name')
49+
->scopes('verifiedUsers')
50+
->inRandomOrder()
51+
->take(10)
52+
->get()
53+
);
54+
55+
return $users;
56+
}
57+
58+
public function render(): View
59+
{
60+
return view('livewire.pages.auth.register')
61+
->title(__('pages/auth.register.page_title'));
62+
}
63+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Livewire\Pages\Auth;
6+
7+
use App\Models\User;
8+
use Flux\Flux;
9+
use Illuminate\Auth\Events\PasswordReset;
10+
use Illuminate\Contracts\View\View;
11+
use Illuminate\Support\Facades\Hash;
12+
use Illuminate\Support\Facades\Password;
13+
use Illuminate\Support\Str;
14+
use Illuminate\Validation\Rules\Password as PasswordRules;
15+
use Livewire\Attributes\Layout;
16+
use Livewire\Attributes\Locked;
17+
use Livewire\Component;
18+
19+
#[Layout('layouts.base')]
20+
final class ResetPassword extends Component
21+
{
22+
#[Locked]
23+
public string $token = '';
24+
25+
public string $email = '';
26+
27+
public string $password = '';
28+
29+
public string $password_confirmation = '';
30+
31+
public function mount(string $token): void
32+
{
33+
$this->token = $token;
34+
$this->email = (string) request()->string('email');
35+
}
36+
37+
public function resetPassword(): void
38+
{
39+
$this->validate([
40+
'token' => ['required'],
41+
'email' => ['required', 'string', 'email'],
42+
'password' => [
43+
'required',
44+
'string',
45+
'confirmed',
46+
PasswordRules::min(8)
47+
->uncompromised()
48+
->numbers()
49+
->mixedCase(),
50+
],
51+
]);
52+
53+
/** @var string $status */
54+
$status = Password::reset(
55+
credentials: $this->only('email', 'password', 'password_confirmation', 'token'),
56+
callback: function (User $user): void {
57+
$user->forceFill([
58+
'password' => Hash::make($this->password),
59+
'remember_token' => Str::random(60),
60+
])->save();
61+
62+
event(new PasswordReset($user));
63+
}
64+
);
65+
66+
if ($status !== Password::PASSWORD_RESET) {
67+
$this->addError('email', __($status));
68+
69+
return;
70+
}
71+
72+
Flux::toast(text: __($status), variant: 'success');
73+
74+
$this->redirectRoute('login', navigate: true);
75+
}
76+
77+
public function render(): View
78+
{
79+
return view('livewire.pages.auth.reset-password')
80+
->title(__('pages/auth.reset.page_title'));
81+
}
82+
}

app/Livewire/Pages/Home.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
use App\Models\Discussion;
99
use App\Models\Thread;
1010
use Illuminate\Contracts\View\View;
11-
use Illuminate\Support\Collection;
11+
use Illuminate\Database\Eloquent\Collection;
1212
use Illuminate\Support\Facades\Cache;
1313
use Livewire\Component;
1414

app/Models/User.php

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,15 +43,15 @@
4343
* @property-read string $username
4444
* @property-read string $avatar_type
4545
* @property-read string $profile_photo_url
46-
* @property-read string|null $location
47-
* @property-read string|null $phone_number
48-
* @property-read string|null $github_profile
49-
* @property-read string|null $twitter_profile
50-
* @property-read string|null $linkedin_profile
51-
* @property-read string|null $bio
52-
* @property-read string|null $website
53-
* @property-read string|null $banned_reason
54-
* @property-read array<array-key, mixed>|null $settings
46+
* @property-read ?string $location
47+
* @property-read ?string $phone_number
48+
* @property-read ?string $github_profile
49+
* @property-read ?string $twitter_profile
50+
* @property-read ?string $linkedin_profile
51+
* @property-read ?string $bio
52+
* @property-read ?string $website
53+
* @property-read ?string $banned_reason
54+
* @property-read array<string, mixed>|null $settings
5555
* @property-read CarbonInterface|null $email_verified_at
5656
* @property-read CarbonInterface|null $last_login_at
5757
* @property-read CarbonInterface|null $banned_at
@@ -92,9 +92,9 @@ final class User extends Authenticatable implements FilamentUser, HasAvatar, Has
9292
'last_active_at',
9393
];
9494

95-
public static function findByEmailAddress(string $emailAddress): self
95+
public static function findByEmailAddress(string $emailAddress): ?self
9696
{
97-
return self::query()->where('email', $emailAddress)->firstOrFail();
97+
return self::query()->where('email', $emailAddress)->first();
9898
}
9999

100100
public static function findOrCreateSocialUserProvider(SocialUser $socialUser, string $provider, string $role = 'user'): self
@@ -152,11 +152,7 @@ public function isLoggedInUser(): bool
152152

153153
public function canAccessPanel(Panel $panel): bool
154154
{
155-
if (str_ends_with($this->email, '@laravel.cm')) {
156-
return true;
157-
}
158-
159-
if ($this->isModerator()) {
155+
if (str_ends_with($this->email, '@laravel.cm') && $this->isModerator()) {
160156
return true;
161157
}
162158

@@ -215,6 +211,9 @@ public function deleteReplies(): void
215211
}
216212
}
217213

214+
/**
215+
* @return Collection<int, Article>
216+
*/
218217
public function latestArticles(int $amount = 10): Collection
219218
{
220219
return $this->articles()->latest()->limit($amount)->get();

0 commit comments

Comments
 (0)