Skip to content
This repository was archived by the owner on Oct 1, 2021. It is now read-only.

Commit 1dd922d

Browse files
authored
feature: optional username column to login and register (#6)
1 parent c970e60 commit 1dd922d

File tree

12 files changed

+337
-41
lines changed

12 files changed

+337
-41
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,19 @@
2626
Laravel\Fortify\FortifyServiceProvider::class,
2727
ARKEcosystem\Fortify\FortifyServiceProvider::class,
2828
```
29+
30+
5. Enable or disable the login/register with username or email by using the `username_alt` setting in the `config/fortify.php` file
31+
32+
```php
33+
<?php
34+
35+
return [
36+
// ...
37+
'username_alt' => 'username',
38+
// Or set that setting to `null` so the user can only login/register with email:
39+
// 'username_alt' => null,
40+
// ...
41+
];
42+
```
43+
44+
**Note:** If you use the `username_alt` setting, you need to ensure that your users table has that column.

config/fortify.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747

4848
'username' => 'email',
4949

50+
'username_alt' => 'username',
51+
5052
'email' => 'email',
5153

5254
/*

resources/views/auth/login.blade.php

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,29 @@ class="flex flex-col p-8 mx-4 border rounded-lg border-theme-secondary-200 md:mx
3535

3636
<div class="mb-4">
3737
<div class="flex flex-1">
38+
@php
39+
$username = \Laravel\Fortify\Fortify::username();
40+
$usernameAlt = Config::get('fortify.username_alt');
41+
$type = 'text';
42+
43+
if ($usernameAlt) {
44+
$label = __('fortify::forms.'.$username).' or '.__('fortify::forms.'.$usernameAlt);
45+
} else {
46+
$label = __('fortify::forms.'.$username);
47+
if ($username === 'email') {
48+
$type = 'email';
49+
}
50+
}
51+
@endphp
52+
3853
<x-ark-input
39-
name="email"
40-
label="Username or Email"
54+
:type="$type"
55+
:name="$username"
56+
:label="$label"
4157
autocomplete="email"
4258
class="w-full"
4359
:autofocus="true"
44-
:value="old('email')"
60+
:value="old($username)"
4561
:required="true"
4662
:errors="$errors"
4763
/>
@@ -53,7 +69,7 @@ class="w-full"
5369
<x-ark-input
5470
type="password"
5571
name="password"
56-
label="Password"
72+
:label="__('fortify::forms.password')"
5773
autocomplete="password"
5874
class="w-full"
5975
:required="true"

resources/views/auth/register-form.blade.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class="w-full"
2424
</div>
2525
@endif
2626

27+
@if(Config::get('fortify.username_alt'))
2728
<div class="mb-4">
2829
<div class="flex flex-1">
2930
<x-ark-input
@@ -38,6 +39,7 @@ class="w-full"
3839
/>
3940
</div>
4041
</div>
42+
@endif
4143

4244
@if($invitation)
4345
<input type="hidden" name="email" value="{{ $invitation->email }}" />

src/Actions/AuthenticateUser.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace ARKEcosystem\Fortify\Actions;
4+
5+
use ARKEcosystem\Fortify\Models;
6+
use Carbon\Carbon;
7+
use Illuminate\Contracts\Auth\Authenticatable;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Support\Facades\Config;
10+
use Illuminate\Support\Facades\Hash;
11+
use Laravel\Fortify\Fortify;
12+
13+
class AuthenticateUser
14+
{
15+
protected Request $request;
16+
17+
/**
18+
* @var \Illuminate\Http\Request
19+
*/
20+
public function __construct(Request $request)
21+
{
22+
$this->request = $request;
23+
$this->username = Fortify::username();
24+
$this->usernameAlt = Config::get('fortify.username_alt');
25+
}
26+
27+
public function handle(): ?Authenticatable
28+
{
29+
/** @var \Illuminate\Database\Eloquent\Model */
30+
$user = $this->fetchUser();
31+
32+
if (! $user) {
33+
return null;
34+
}
35+
36+
if (! Hash::check($this->request->password, $user->password)) {
37+
return null;
38+
}
39+
40+
$user->update(['last_login_at' => Carbon::now()]);
41+
42+
return $user;
43+
}
44+
45+
private function fetchUser(): ?Authenticatable
46+
{
47+
$username = $this->getUsername();
48+
49+
$query = Models::user()::query();
50+
51+
$query->where(Fortify::username(), $username);
52+
53+
if ($usernameAlt = Config::get('fortify.username_alt')) {
54+
$query->orWhere($usernameAlt, $username);
55+
}
56+
57+
return $query->first();
58+
}
59+
60+
private function getUsername(): ?string
61+
{
62+
return $this->request->get($this->username);
63+
}
64+
}

src/Actions/CreateNewUser.php

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
namespace ARKEcosystem\Fortify\Actions;
44

55
use ARKEcosystem\Fortify\Models;
6+
use Illuminate\Support\Facades\Config;
67
use Illuminate\Support\Facades\Hash;
78
use Illuminate\Support\Facades\Validator;
9+
use Illuminate\Validation\Validator as ValidationValidator;
810
use Laravel\Fortify\Contracts\CreatesNewUsers;
11+
use Laravel\Fortify\Fortify;
912

1013
class CreateNewUser implements CreatesNewUsers
1114
{
@@ -20,19 +23,50 @@ class CreateNewUser implements CreatesNewUsers
2023
*/
2124
public function create(array $input)
2225
{
23-
Validator::make($input, [
24-
'name' => ['required', 'string', 'max:255'],
25-
'username' => ['required', 'string', 'max:255', 'unique:users'],
26-
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
27-
'password' => $this->passwordRules(),
28-
'terms' => ['required', 'accepted'],
29-
])->validate();
30-
31-
return Models::user()::create([
32-
'name' => $input['name'],
33-
'username' => $input['username'],
34-
'email' => $input['email'],
35-
'password' => Hash::make($input['password']),
36-
]);
26+
$input = $this->buildValidator($input)->validate();
27+
28+
return Models::user()::create($this->getUserData($input));
29+
}
30+
31+
private function buildValidator(array $input): ValidationValidator
32+
{
33+
$rules = [
34+
'name' => ['required', 'string', 'max:255'],
35+
Fortify::username() => $this->usernameRules(),
36+
'password' => $this->passwordRules(),
37+
'terms' => ['required', 'accepted'],
38+
];
39+
40+
if ($usernameAlt = Config::get('fortify.username_alt')) {
41+
$rules[$usernameAlt] = ['required', 'string', 'max:255', 'unique:users'];
42+
}
43+
44+
return Validator::make($input, $rules);
45+
}
46+
47+
private function getUserData(array $input): array
48+
{
49+
$userData = [
50+
'name' => $input['name'],
51+
Fortify::username() => $input[Fortify::username()],
52+
'password' => Hash::make($input['password']),
53+
];
54+
55+
if ($usernameAlt = Config::get('fortify.username_alt')) {
56+
$userData[$usernameAlt] = $input[$usernameAlt];
57+
}
58+
59+
return $userData;
60+
}
61+
62+
private function usernameRules(): array
63+
{
64+
$rules = ['required', 'string', 'max:255', 'unique:users'];
65+
66+
if (Fortify::username() === 'email') {
67+
$rules[] = 'email';
68+
}
69+
70+
return $rules;
3771
}
3872
}

src/FortifyServiceProvider.php

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace ARKEcosystem\Fortify;
44

5+
use ARKEcosystem\Fortify\Actions\AuthenticateUser;
56
use ARKEcosystem\Fortify\Actions\CreateNewUser;
67
use ARKEcosystem\Fortify\Actions\ResetUserPassword;
78
use ARKEcosystem\Fortify\Actions\UpdateUserPassword;
@@ -17,9 +18,7 @@
1718
use ARKEcosystem\Fortify\Components\UpdateTimezoneForm;
1819
use ARKEcosystem\Fortify\Responses\FailedTwoFactorLoginResponse;
1920
use ARKEcosystem\Fortify\Responses\TwoFactorLoginResponse;
20-
use Carbon\Carbon;
2121
use Illuminate\Http\Request;
22-
use Illuminate\Support\Facades\Hash;
2322
use Illuminate\Support\ServiceProvider;
2423
use Laravel\Fortify\Contracts\FailedTwoFactorLoginResponse as FailedTwoFactorLoginResponseContract;
2524
use Laravel\Fortify\Contracts\TwoFactorLoginResponse as TwoFactorLoginResponseContract;
@@ -80,8 +79,9 @@ public function registerPublishers(): void
8079
], 'config');
8180

8281
$this->publishes([
83-
__DIR__.'/../resources/views/auth' => resource_path('views/auth'),
84-
__DIR__.'/../resources/views/livewire' => resource_path('views/livewire'),
82+
__DIR__.'/../resources/views/auth' => resource_path('views/auth'),
83+
__DIR__.'/../resources/views/components' => resource_path('views/components'),
84+
__DIR__.'/../resources/views/profile' => resource_path('views/profile'),
8585
], 'views');
8686
}
8787

@@ -181,23 +181,9 @@ private function registerViews(): void
181181
private function registerAuthentication(): void
182182
{
183183
Fortify::authenticateUsing(function (Request $request) {
184-
try {
185-
$username = $request->get('email');
184+
$authenticator = new AuthenticateUser($request);
186185

187-
if (\filter_var($username, \FILTER_VALIDATE_EMAIL)) {
188-
$user = Models::user()::where('email', $username)->firstOrFail();
189-
} else {
190-
$user = Models::user()::where('username', $username)->firstOrFail();
191-
}
192-
193-
if ($user && Hash::check($request->password, $user->password)) {
194-
$user->update(['last_login_at' => Carbon::now()]);
195-
196-
return $user;
197-
}
198-
} catch (\Throwable $th) {
199-
return;
200-
}
186+
return (new AuthenticateUser($request))->handle($request);
201187
});
202188
}
203189

src/Models/User.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Laravel\Fortify\TwoFactorAuthenticatable;
1212
use Spatie\PersonalDataExport\ExportsPersonalData;
1313
use Spatie\PersonalDataExport\PersonalDataSelection;
14+
use Tests\Database\Factories\UserFactory;
1415

1516
class User extends Authenticatable implements ExportsPersonalData, MustVerifyEmail
1617
{
@@ -47,6 +48,16 @@ class User extends Authenticatable implements ExportsPersonalData, MustVerifyEma
4748
'email_verified_at' => 'datetime',
4849
];
4950

51+
/**
52+
* Create a new factory instance for the model.
53+
*
54+
* @return \Illuminate\Database\Eloquent\Factories\Factory
55+
*/
56+
protected static function newFactory()
57+
{
58+
return new UserFactory();
59+
}
60+
5061
/**
5162
* @codeCoverageIgnore
5263
*/
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
use ARKEcosystem\Fortify\Actions\AuthenticateUser;
4+
use ARKEcosystem\Fortify\Models\User;
5+
use Illuminate\Http\Request;
6+
use Illuminate\Support\Facades\Config;
7+
8+
it('login the user by default username (email)', function () {
9+
Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class);
10+
11+
$user = User::factory()->create();
12+
13+
$request = new Request();
14+
15+
$request->replace([
16+
'email' => $user->email,
17+
'password' => 'password',
18+
]);
19+
20+
$authenticator = new AuthenticateUser($request);
21+
$loggedUser = $authenticator->handle();
22+
23+
$this->assertNotNull($loggedUser);
24+
$this->assertTrue($user->is($loggedUser));
25+
});
26+
27+
it('login the user by the email when alt username is set', function () {
28+
Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class);
29+
Config::set('fortify.username_alt', 'username');
30+
31+
$user = User::factory()->withUsername()->create();
32+
33+
$request = new Request();
34+
35+
$request->replace([
36+
'email' => $user->email,
37+
'password' => 'password',
38+
]);
39+
40+
$authenticator = new AuthenticateUser($request);
41+
$loggedUser = $authenticator->handle();
42+
43+
$this->assertNotNull($loggedUser);
44+
$this->assertTrue($user->is($loggedUser));
45+
});
46+
47+
it('login the user by the alt username (username)', function () {
48+
Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class);
49+
Config::set('fortify.username_alt', 'username');
50+
51+
$user = User::factory()->withUsername()->create();
52+
53+
$request = new Request();
54+
55+
$request->replace([
56+
'email' => $user->username,
57+
'password' => 'password',
58+
]);
59+
60+
$authenticator = new AuthenticateUser($request);
61+
$loggedUser = $authenticator->handle();
62+
63+
$this->assertNotNull($loggedUser);
64+
$this->assertTrue($user->is($loggedUser));
65+
});
66+
67+
it('doesnt login the user by the alt username if not set (username)', function () {
68+
Config::set('fortify.models.user', \ARKEcosystem\Fortify\Models\User::class);
69+
Config::set('fortify.username_alt', null);
70+
71+
$user = User::factory()->withUsername()->create();
72+
73+
$request = new Request();
74+
75+
$request->replace([
76+
'email' => $user->username,
77+
'password' => 'password',
78+
]);
79+
80+
$authenticator = new AuthenticateUser($request);
81+
$loggedUser = $authenticator->handle();
82+
83+
$this->assertNull($loggedUser);
84+
});

0 commit comments

Comments
 (0)