Skip to content

Commit 4f5e9a8

Browse files
pushpak1300driesvints
authored andcommitted
Implement Laravel email verification (#421)
1 parent c00b86a commit 4f5e9a8

File tree

11 files changed

+142
-37
lines changed

11 files changed

+142
-37
lines changed

app/Http/Controllers/Auth/RegisterController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
use App\Http\Middleware\RedirectIfAuthenticated;
77
use App\Http\Requests\RegisterRequest;
88
use App\Jobs\RegisterUser;
9-
use App\Jobs\SendEmailConfirmation;
109
use App\User;
10+
use Illuminate\Auth\Events\Registered;
1111
use Illuminate\Contracts\Validation\Validator as ValidatorContract;
1212
use Illuminate\Foundation\Auth\RegistersUsers;
1313
use Illuminate\Support\Facades\Validator;
@@ -59,7 +59,7 @@ protected function create(array $data): User
5959
{
6060
$user = $this->dispatchNow(RegisterUser::fromRequest(app(RegisterRequest::class)));
6161

62-
$this->dispatch(new SendEmailConfirmation($user));
62+
event(new Registered($user));
6363

6464
return $user;
6565
}

app/Http/Controllers/Auth/VerificationController.php

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@
33
namespace App\Http\Controllers\Auth;
44

55
use App\Http\Controllers\Controller;
6+
use Illuminate\Auth\Access\AuthorizationException;
7+
use Illuminate\Auth\Events\Verified;
68
use Illuminate\Foundation\Auth\VerifiesEmails;
9+
use Illuminate\Http\Request;
10+
use Illuminate\Http\Response;
11+
use Illuminate\Support\Facades\Auth;
712

813
class VerificationController extends Controller
914
{
@@ -25,7 +30,68 @@ class VerificationController extends Controller
2530
*
2631
* @var string
2732
*/
28-
protected $redirectTo = '/home';
33+
protected $redirectTo = '/dashboard';
34+
35+
/**
36+
* Mark the authenticated user's email address as verified.
37+
*
38+
* @param \Illuminate\Http\Request $request
39+
* @return \Illuminate\Http\Response
40+
*
41+
* @throws \Illuminate\Auth\Access\AuthorizationException
42+
*/
43+
public function verify(Request $request)
44+
{
45+
if (! hash_equals((string) $request->route('id'), (string) $request->user()->getKey())) {
46+
throw new AuthorizationException();
47+
}
48+
49+
if (! hash_equals((string) $request->hash, sha1($request->user()->emailAddress()))) {
50+
throw new AuthorizationException();
51+
}
52+
53+
if ($request->user()->hasVerifiedEmail()) {
54+
$this->error('auth.confirmation.no_match');
55+
return $request->wantsJson()
56+
? new Response('', 204)
57+
: redirect($this->redirectPath());
58+
}
59+
60+
if ($request->user()->markEmailAsVerified()) {
61+
event(new Verified($request->user()));
62+
}
63+
64+
$this->success('auth.confirmation.success');
65+
66+
return $request->wantsJson()
67+
? new Response('', 204)
68+
: redirect($this->redirectPath())->with('verified', true);
69+
}
70+
71+
/**
72+
* Resend the email verification notification.
73+
*
74+
* @param \Illuminate\Http\Request $request
75+
* @return \Illuminate\Http\Response
76+
*/
77+
public function resend(Request $request)
78+
{
79+
if ($request->user()->hasVerifiedEmail()) {
80+
$this->error('auth.confirmation.already_confirmed');
81+
82+
return $request->wantsJson()
83+
? new Response('', 204)
84+
: redirect($this->redirectPath());
85+
}
86+
87+
$request->user()->sendEmailVerificationNotification();
88+
89+
$this->success('auth.confirmation.sent', Auth::user()->emailAddress());
90+
91+
return $request->wantsJson()
92+
? new Response('', 202)
93+
: redirect()->route('dashboard')->with('resent', true);
94+
}
2995

3096
/**
3197
* Create a new controller instance.

app/User.php

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
use App\Models\Reply;
99
use App\Models\Series;
1010
use App\Models\Thread;
11+
use Illuminate\Contracts\Auth\MustVerifyEmail;
1112
use Illuminate\Database\Eloquent\Relations\HasMany;
1213
use Illuminate\Foundation\Auth\User as Authenticatable;
1314
use Illuminate\Notifications\Notifiable;
1415
use Illuminate\Support\Facades\Auth;
1516

16-
final class User extends Authenticatable
17+
final class User extends Authenticatable implements MustVerifyEmail
1718
{
1819
use HasTimestamps;
1920
use ModelHelpers;
@@ -83,6 +84,11 @@ public function githubUsername(): string
8384
return $this->github_username;
8485
}
8586

87+
public function emailVerifiedAt(): ?string
88+
{
89+
return $this->email_verified_at;
90+
}
91+
8692
public function gravatarUrl($size = 100): string
8793
{
8894
$hash = md5(strtolower(trim($this->email)));
@@ -93,12 +99,12 @@ public function gravatarUrl($size = 100): string
9399

94100
public function isConfirmed(): bool
95101
{
96-
return (bool) $this->confirmed;
102+
return ! $this->isUnconfirmed();
97103
}
98104

99105
public function isUnconfirmed(): bool
100106
{
101-
return ! $this->isConfirmed();
107+
return is_null($this->emailVerifiedAt());
102108
}
103109

104110
public function confirmationCode(): string

database/factories/UserFactory.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
use App\User;
44
use Illuminate\Support\Str;
55

6-
/** @var \Illuminate\Database\Eloquent\Factory $factory */
6+
/* @var \Illuminate\Database\Eloquent\Factory $factory */
7+
78
$factory->define(User::class, function (Faker\Generator $faker) {
89
static $password;
910

@@ -20,6 +21,7 @@
2021
'banned_at' => null,
2122
'type' => User::DEFAULT,
2223
'bio' => $faker->sentence,
24+
'email_verified_at' => null,
2325
];
2426
});
2527

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
class AddEmailVerifiedAtColumnToUsers extends Migration
8+
{
9+
public function up()
10+
{
11+
Schema::table('users', function (Blueprint $table) {
12+
$table->timestamp('email_verified_at')->nullable();
13+
});
14+
}
15+
}

resources/views/forum/threads/show.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class="forum-content"
179179
@else
180180
<div class="bg-gray-400 rounded p-4 text-gray-700 my-8">
181181
<p>You'll need to verify your account before participating in this thread.</p>
182-
<p><a href="{{ route('email.send_confirmation') }}" class="text-green-dark">Click here to resend the verification link.</p>
182+
<p><a href="{{ route('verification.resend') }}" class="text-green-dark">Click here to resend the verification link.</p>
183183
</div>
184184
@endif
185185
@endcan
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
@component('mail::message')
2+
# Welcome to Laravel.io!
3+
4+
Thanks for joining up with the [Laravel.io](https://laravel.io) community!
5+
6+
We just need to confirm your email address so please click the button below to confirm it:
7+
8+
@component('mail::button', ['url' => $actionUrl])
9+
Confirm Email Address
10+
@endcomponent
11+
12+
We hope to see you soon on the portal.
13+
14+
Regards,<br>
15+
{{ config('app.name') }}
16+
@endcomponent

routes/web.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,8 @@
3131
Route::post('password/reset', 'ResetPasswordController@reset')->name('password.reset.post');
3232

3333
// Email address confirmation
34-
Route::get('email-confirmation', 'EmailConfirmationController@send')->name('email.send_confirmation');
35-
Route::get('email-confirmation/{email_address}/{code}', 'EmailConfirmationController@confirm')
36-
->name('email.confirm');
34+
Route::get('email/verify/{id}', 'VerificationController@verify')->name('verification.verify');
35+
Route::get('email/resend', 'VerificationController@resend')->name('verification.resend');
3736

3837
// Social authentication
3938
Route::get('login/github', 'GithubController@redirectToProvider')->name('login.github');

tests/CreatesUsers.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ protected function createUser(array $attributes = []): User
3838
'email' => 'john@example.com',
3939
'password' => bcrypt('password'),
4040
'github_username' => 'johndoe',
41+
'email_verified_at' => now()
4142
], $attributes));
4243
}
4344
}

tests/Feature/AuthTest.php

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
namespace Tests\Feature;
44

5-
use App\Mail\EmailConfirmationEmail;
65
use Carbon\Carbon;
6+
use Illuminate\Auth\Events\Registered;
77
use Illuminate\Contracts\Auth\PasswordBroker;
88
use Illuminate\Foundation\Testing\DatabaseMigrations;
99
use Illuminate\Support\Facades\Auth;
10-
use Illuminate\Support\Facades\Mail;
10+
use Illuminate\Support\Facades\Event;
1111

1212
class AuthTest extends BrowserKitTestCase
1313
{
@@ -16,7 +16,7 @@ class AuthTest extends BrowserKitTestCase
1616
/** @test */
1717
public function users_can_register()
1818
{
19-
Mail::fake();
19+
Event::fake();
2020

2121
session(['githubData' => ['id' => 123, 'username' => 'johndoe']]);
2222

@@ -34,7 +34,7 @@ public function users_can_register()
3434

3535
$this->assertLoggedIn();
3636

37-
Mail::assertSent(EmailConfirmationEmail::class);
37+
Event::assertDispatched(Registered::class);
3838
}
3939

4040
/** @test */
@@ -72,9 +72,9 @@ public function registration_fails_with_non_alpha_dash_username()
7272
/** @test */
7373
public function users_can_resend_the_email_confirmation()
7474
{
75-
$this->login(['confirmed' => false]);
75+
$this->login(['email_verified_at' => null]);
7676

77-
$this->visit('/email-confirmation')
77+
$this->visit('/email/resend')
7878
->seePageIs('/dashboard')
7979
->see('Email confirmation sent to john@example.com');
8080
}
@@ -84,32 +84,32 @@ public function users_do_not_need_to_confirm_their_email_address_twice()
8484
{
8585
$this->login();
8686

87-
$this->visit('/email-confirmation')
87+
$this->visit('/email/resend')
8888
->seePageIs('/dashboard')
8989
->see('Your email address is already confirmed.');
9090
}
9191

92-
/** @test */
93-
public function users_can_confirm_their_email_address()
94-
{
95-
$user = $this->createUser(['confirmed' => false, 'confirmation_code' => 'testcode']);
92+
// /** @test */
93+
// public function users_can_confirm_their_email_address()
94+
// {
95+
// $user = $this->createUser(['confirmed' => false, 'confirmation_code' => 'testcode']);
9696

97-
$this->visit('/email-confirmation/john@example.com/testcode')
98-
->seePageIs('/')
99-
->see('Your email address was successfully confirmed.');
97+
// $this->visit('/email-confirmation/john@example.com/testcode')
98+
// ->seePageIs('/')
99+
// ->see('Your email address was successfully confirmed.');
100100

101-
$this->seeInDatabase('users', ['id' => $user->id(), 'confirmed' => true]);
102-
}
101+
// $this->seeInDatabase('users', ['id' => $user->id(), 'confirmed' => true]);
102+
// }
103103

104-
/** @test */
105-
public function users_get_a_message_when_a_confirmation_code_was_not_found()
106-
{
107-
$this->createUser(['confirmed' => false]);
104+
// /** @test */
105+
// public function users_get_a_message_when_a_confirmation_code_was_not_found()
106+
// {
107+
// $this->createUser(['confirmed' => false]);
108108

109-
$this->visit('/email-confirmation/john@example.com/testcode')
110-
->seePageIs('/')
111-
->see('We could not confirm your email address. The given email address and code did not match.');
112-
}
109+
// $this->visit('/email-confirmation/john@example.com/testcode')
110+
// ->seePageIs('/')
111+
// ->see('We could not confirm your email address. The given email address and code did not match.');
112+
// }
113113

114114
/** @test */
115115
public function users_can_login()
@@ -211,7 +211,7 @@ public function users_can_reset_their_password()
211211
/** @test */
212212
public function unconfirmed_users_cannot_create_threads()
213213
{
214-
$this->login(['confirmed' => false]);
214+
$this->login(['email_verified_at' => null]);
215215

216216
$this->visit('/forum/create-thread')
217217
->see('Please confirm your email address first.');

0 commit comments

Comments
 (0)