Skip to content

Commit 4cd8e5d

Browse files
authored
Merge pull request #5301 from Laravel-Backpack/add-email-confirmation
Add email confirmation
2 parents a8b5d7d + cbb430a commit 4cd8e5d

File tree

11 files changed

+228
-4
lines changed

11 files changed

+228
-4
lines changed

src/BackpackServiceProvider.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Backpack\CRUD;
44

55
use Backpack\Basset\Facades\Basset;
6+
use Backpack\CRUD\app\Http\Middleware\EnsureEmailVerification;
67
use Backpack\CRUD\app\Http\Middleware\ThrottlePasswordRecovery;
78
use Backpack\CRUD\app\Library\CrudPanel\CrudPanel;
89
use Backpack\CRUD\app\Library\Database\DatabaseSchema;
@@ -129,6 +130,11 @@ public function registerMiddlewareGroup(Router $router)
129130
if (config('backpack.base.setup_password_recovery_routes')) {
130131
$router->aliasMiddleware('backpack.throttle.password.recovery', ThrottlePasswordRecovery::class);
131132
}
133+
134+
// register the email verification middleware, if the developer enabled it in the config.
135+
if (config('backpack.base.setup_email_verification_routes', false) && config('backpack.base.setup_email_verification_middleware', true)) {
136+
$router->pushMiddlewareToGroup($middleware_key, EnsureEmailVerification::class);
137+
}
132138
}
133139

134140
public function publishFiles()

src/app/Http/Controllers/Auth/RegisterController.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
use Illuminate\Auth\Events\Registered;
77
use Illuminate\Http\Request;
88
use Illuminate\Routing\Controller;
9-
use Validator;
9+
use Illuminate\Support\Facades\Cookie;
10+
use Illuminate\Support\Facades\Validator;
1011

1112
class RegisterController extends Controller
1213
{
@@ -64,7 +65,7 @@ protected function validator(array $data)
6465
* Create a new user instance after a valid registration.
6566
*
6667
* @param array $data
67-
* @return User
68+
* @return \Illuminate\Contracts\Auth\Authenticatable
6869
*/
6970
protected function create(array $data)
7071
{
@@ -99,7 +100,7 @@ public function showRegistrationForm()
99100
* Handle a registration request for the application.
100101
*
101102
* @param \Illuminate\Http\Request $request
102-
* @return \Illuminate\Http\Response
103+
* @return \Illuminate\Http\Response|\Illuminate\Contracts\View\View
103104
*/
104105
public function register(Request $request)
105106
{
@@ -113,6 +114,12 @@ public function register(Request $request)
113114
$user = $this->create($request->all());
114115

115116
event(new Registered($user));
117+
if (config('backpack.base.setup_email_verification_routes')) {
118+
Cookie::queue('backpack_email_verification', $user->{config('backpack.base.email_column')}, 30);
119+
120+
return redirect(route('verification.notice'));
121+
}
122+
116123
$this->guard()->login($user);
117124

118125
return redirect($this->redirectPath());
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\Http\Controllers\Auth;
4+
5+
use Backpack\CRUD\app\Http\Requests\EmailVerificationRequest;
6+
use Backpack\CRUD\app\Library\Auth\UserFromCookie;
7+
use Exception;
8+
use Illuminate\Http\Request;
9+
use Illuminate\Routing\Controller;
10+
use Prologue\Alerts\Facades\Alert;
11+
12+
class VerifyEmailController extends Controller
13+
{
14+
public null|string $redirectTo = null;
15+
16+
/**
17+
* Create a new controller instance.
18+
*
19+
* @return void
20+
*/
21+
public function __construct()
22+
{
23+
if (! app('router')->getMiddleware()['signed'] ?? null) {
24+
throw new Exception('Missing "signed" alias middleware in App/Http/Kernel.php. More info: https://backpackforlaravel.com/docs/6.x/base-how-to#enable-email-verification-in-backpack-routes');
25+
}
26+
27+
$this->middleware('signed')->only('verifyEmail');
28+
$this->middleware('throttle:'.config('backpack.base.email_verification_throttle_access'))->only('resendVerificationEmail');
29+
30+
if (! backpack_users_have_email()) {
31+
abort(500, trans('backpack::base.no_email_column'));
32+
}
33+
// where to redirect after the email is verified
34+
$this->redirectTo = $this->redirectTo ?? backpack_url('dashboard');
35+
}
36+
37+
public function emailVerificationRequired(Request $request): \Illuminate\Contracts\View\View|\Illuminate\Http\RedirectResponse
38+
{
39+
$this->getUserOrRedirect($request);
40+
41+
return view(backpack_view('auth.verify-email'));
42+
}
43+
44+
/**
45+
* Verify the user's email address.
46+
*
47+
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
48+
*/
49+
public function verifyEmail(EmailVerificationRequest $request)
50+
{
51+
$this->getUserOrRedirect($request);
52+
53+
$request->fulfill();
54+
55+
return redirect($this->redirectTo);
56+
}
57+
58+
/**
59+
* Resend the email verification notification.
60+
*/
61+
public function resendVerificationEmail(Request $request): \Illuminate\Http\RedirectResponse
62+
{
63+
$user = $this->getUserOrRedirect($request);
64+
65+
if (is_a($user, \Illuminate\Http\RedirectResponse::class)) {
66+
return $user;
67+
}
68+
69+
$user->sendEmailVerificationNotification();
70+
Alert::success('Email verification link sent successfully.')->flash();
71+
72+
return back()->with('status', 'verification-link-sent');
73+
}
74+
75+
private function getUser(Request $request): ?\Illuminate\Contracts\Auth\MustVerifyEmail
76+
{
77+
return $request->user(backpack_guard_name()) ?? (new UserFromCookie())();
78+
}
79+
80+
private function getUserOrRedirect(Request $request): \Illuminate\Contracts\Auth\MustVerifyEmail|\Illuminate\Http\RedirectResponse
81+
{
82+
if ($user = $this->getUser($request)) {
83+
return $user;
84+
}
85+
86+
return redirect()->route('backpack.auth.login');
87+
}
88+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\Http\Middleware;
4+
5+
use Closure;
6+
use Exception;
7+
use Throwable;
8+
9+
class EnsureEmailVerification
10+
{
11+
/**
12+
* Handle an incoming request.
13+
*
14+
* @param \Illuminate\Http\Request $request
15+
* @param \Closure $next
16+
* @return mixed
17+
*/
18+
public function handle($request, Closure $next)
19+
{
20+
// if the route is one in the verification process, do nothing
21+
if (in_array($request->route()->getName(), ['verification.notice', 'verification.verify', 'verification.send'])) {
22+
return $next($request);
23+
}
24+
25+
// the Laravel middleware needs the user resolver to be set with the backpack guard
26+
$userResolver = $request->getUserResolver();
27+
$request->setUserResolver(function () use ($userResolver) {
28+
return $userResolver(backpack_guard_name());
29+
});
30+
31+
try {
32+
$verifiedMiddleware = new (app('router')->getMiddleware()['verified'])();
33+
} catch(Throwable) {
34+
throw new Exception('Missing "verified" alias middleware in App/Http/Kernel.php. More info: https://backpackforlaravel.com/docs/6.x/base-how-to#enable-email-verification-in-backpack-routes');
35+
}
36+
37+
return $verifiedMiddleware->handle($request, $next);
38+
}
39+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\Http\Requests;
4+
5+
use Backpack\CRUD\app\Library\Auth\UserFromCookie;
6+
use Illuminate\Foundation\Auth\EmailVerificationRequest as OriginalEmailVerificationRequest;
7+
8+
class EmailVerificationRequest extends OriginalEmailVerificationRequest
9+
{
10+
public function user($guard = null)
11+
{
12+
return parent::user(backpack_guard_name()) ?? (new UserFromCookie())();
13+
}
14+
}

src/app/Library/Auth/AuthenticatesUsers.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Illuminate\Http\Request;
66
use Illuminate\Http\Response;
77
use Illuminate\Support\Facades\Auth;
8+
use Illuminate\Support\Facades\Cookie;
89
use Illuminate\Validation\ValidationException;
910

1011
trait AuthenticatesUsers
@@ -47,6 +48,10 @@ public function login(Request $request)
4748
}
4849

4950
if ($this->attemptLogin($request)) {
51+
if (config('backpack.base.setup_email_verification_routes', false)) {
52+
return $this->logoutIfEmailNotVerified($request);
53+
}
54+
5055
return $this->sendLoginResponse($request);
5156
}
5257

@@ -199,4 +204,25 @@ protected function guard()
199204
{
200205
return Auth::guard();
201206
}
207+
208+
private function logoutIfEmailNotVerified(Request $request): Response|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
209+
{
210+
$user = $this->guard()->user();
211+
212+
// if the user is already verified, do nothing
213+
if ($user->email_verified_at) {
214+
return $this->sendLoginResponse($request);
215+
}
216+
// user is not yet verified, log him out
217+
$this->guard()->logout();
218+
219+
// add a cookie for 30m to remember the email address that needs to be verified
220+
Cookie::queue('backpack_email_verification', $user->{config('backpack.base.email_column')}, 30);
221+
222+
if ($request->wantsJson()) {
223+
return new Response('Email verification required', 403);
224+
}
225+
226+
return redirect(route('verification.notice'));
227+
}
202228
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Backpack\CRUD\app\Library\Auth;
4+
5+
use Illuminate\Support\Facades\Cookie;
6+
7+
class UserFromCookie
8+
{
9+
public function __invoke(): ?\Illuminate\Contracts\Auth\MustVerifyEmail
10+
{
11+
if (Cookie::has('backpack_email_verification')) {
12+
return config('backpack.base.user_model_fqn')::where(config('backpack.base.email_column'), Cookie::get('backpack_email_verification'))->first();
13+
}
14+
15+
return null;
16+
}
17+
}

src/config/backpack/base.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@
5656
// (you then need to manually define the routes in your web.php)
5757
'setup_password_recovery_routes' => true,
5858

59+
// Set this to true if you would like to enable email verification for your user model.
60+
// Make sure your user model implements the MustVerifyEmail contract and your database
61+
// table contains the `email_verified_at` column. Read the following before enabling:
62+
// https://backpackforlaravel.com/docs/6.x/base-how-to#enable-email-verification-in-backpack-routes
63+
'setup_email_verification_routes' => false,
64+
65+
// When email verification is enabled, automatically add the Verified middleware to Backpack routes?
66+
// Set false if you want to use your own Verified middleware in `middleware_class`.
67+
'setup_email_verification_middleware' => true,
68+
69+
// How many times in any given time period should the user be allowed to
70+
// request a new verification email?
71+
// Defaults to 1,10 - 1 time in 10 minutes.
72+
'email_verification_throttle_access' => '3,15',
73+
5974
/*
6075
|--------------------------------------------------------------------------
6176
| Security

src/resources/lang/en/base.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,10 @@
8585
'throttled' => 'You have already requested a password reset recently. Please check your email. If you do not receive our email, please retry later.',
8686
'throttled_request' => 'You have exceeded the limit of tries. Please wait a few minutes and try again.',
8787

88+
'verify_email' => [
89+
'email_verification' => 'Email Verification',
90+
'verification_link_sent' => 'A verification link has been sent to your email address.',
91+
'email_verification_required' => 'Please verify your email address, by clicking on the link we\'ve sent you.',
92+
'resend_verification_link' => 'Resend link',
93+
],
8894
];

src/resources/views/ui/errors/layout.blade.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
{{-- show error using sidebar layout if logged in AND on an admin page; otherwise use a blank page --}}
12
@extends(backpack_view(backpack_user() && backpack_theme_config('layout') ? 'layouts.'.backpack_theme_config('layout') : 'errors.blank'))
2-
{{-- show error using sidebar layout if looged in AND on an admin page; otherwise use a blank page --}}
33

44
@section('content')
55
<div class="row">

0 commit comments

Comments
 (0)