├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── config └── emailverification.php ├── database └── migrations │ └── 2017_02_27_170020_add_verified_to_user_table.php ├── phpunit.xml ├── resources ├── lang │ ├── de │ │ └── messages.php │ ├── en │ │ └── messages.php │ └── nb │ │ └── messages.php └── views │ └── resend.blade.php ├── src ├── Contracts │ └── CanVerifyEmail.php ├── EmailVerification.php ├── Events │ ├── EmailVerificationSent.php │ └── UserVerified.php ├── Exceptions │ └── UserNotVerifiedException.php ├── Http │ └── routes.php ├── Listeners │ └── SendUserVerificationMail.php ├── Middleware │ └── IsEmailVerified.php ├── Notifications │ └── EmailVerification.php ├── Providers │ ├── EmailVerificationEventServiceProvider.php │ └── EmailVerificationServiceProvider.php └── Traits │ ├── CanVerifyEmail.php │ └── VerifiesEmail.php └── tests ├── Feature ├── MiddlewareTest.php ├── RegisterController.php ├── RegistrationTest.php ├── ResendVerificationMailTest.php ├── TestCase.php └── User.php └── Unit └── EmailVerificationTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .DS_Store 4 | .idea 5 | .project -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.1 5 | - 7.2 6 | - 7.3 7 | 8 | sudo: false 9 | 10 | install: travis_retry composer install --no-interaction --prefer-dist --no-suggest 11 | 12 | script: vendor/bin/phpunit --verbose 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Josias Montag 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Build Status 3 | Total Downloads 4 | Latest Stable Version 5 | License 6 |

7 | 8 | > ⚠️ **Deprecation Warning**: This package is deprecated. I recommend using Laravel's [built in email verification](https://laravel.com/docs/5.8/verification). 9 | 10 | ## Introduction 11 | 12 | The Laravel Email Verification package is built for Laravel 5.4 and later to easily handle a user verification and validate the e-mail. It is inspired by [crypto-based password resets](https://github.com/laravel/framework/pull/17499) and the [email verification package by jrean](https://github.com/jrean/laravel-user-verification). 13 | 14 | - [x] Crypto-based email verification. No need to store a temporary token in the database! 15 | - [x] Event based: No need to override your `register()` method. 16 | - [x] Using the Laravel 5.3 notification system. 17 | - [x] Allow certain routes for verified users only using the `IsEmailVerified` middleware. 18 | - [x] Let the users resend the verification email at anytime. 19 | - [x] Ready for Localization. 20 | 21 | 22 | ## Configuration 23 | 24 | 25 | To get started, use Composer to add the package to your project's dependencies: 26 | 27 | composer require josiasmontag/laravel-email-verification 28 | 29 | 30 | In Laravel 5.5 the service provider will automatically get registered. In older versions of the framework just register the `Lunaweb\EmailVerification\Providers\EmailVerificationServiceProvider` in your `config/app.php` configuration file: 31 | 32 | ```php 33 | 'providers' => [ 34 | // Other service providers... 35 | 36 | Lunaweb\EmailVerification\Providers\EmailVerificationServiceProvider::class, 37 | ], 38 | ``` 39 | 40 | ### Migration 41 | 42 | The table representing the user must be updated with a `verified` column. 43 | This update will be performed by the migrations included with this package. 44 | 45 | To run the migrations from this package use the following command: 46 | 47 | ``` 48 | php artisan migrate --path="/vendor/josiasmontag/laravel-email-verification/database/migrations" 49 | ``` 50 | 51 | The package tries to guess your `user` table by checking what is set in the auth providers users settings. 52 | If this key is not found, the default `App\User` will be used to get the table name. 53 | 54 | To customize the migration, publish it with the following command: 55 | 56 | ``` 57 | php artisan vendor:publish --provider="Lunaweb\EmailVerification\Providers\EmailVerificationServiceProvider" --tag="migrations" 58 | ``` 59 | 60 | ### User Model 61 | 62 | The model representing the `User` must implement the `CanVerifyEmail` interface. The package comes with a `CanVerifyEmail` trait for a quick implementation. You can customize this trait in order to change the activation email. 63 | 64 | 65 | ```php 66 | use Illuminate\Foundation\Auth\User as Authenticatable; 67 | use Lunaweb\EmailVerification\Traits\CanVerifyEmail; 68 | use Lunaweb\EmailVerification\Contracts\CanVerifyEmail as CanVerifyEmailContract; 69 | 70 | class User extends Authenticatable implements CanVerifyEmailContract 71 | { 72 | 73 | use CanVerifyEmail; 74 | 75 | // ... 76 | } 77 | ``` 78 | 79 | ### Register Controller 80 | 81 | The package offers a `VerifiesEmail` trait for your `RegisterController`. You must update the middleware exception to allow `verify` routes to be access by authenticated users. 82 | 83 | ```php 84 | 85 | use Lunaweb\EmailVerification\Traits\VerifiesEmail; 86 | 87 | class RegisterController extends Controller 88 | { 89 | 90 | use RegistersUsers, VerifiesEmail; 91 | 92 | 93 | public function __construct() 94 | { 95 | $this->middleware('guest', ['except' => ['verify', 'showResendVerificationEmailForm', 'resendVerificationEmail']]); 96 | $this->middleware('auth', ['only' => ['showResendVerificationEmailForm', 'resendVerificationEmail']]); 97 | } 98 | 99 | // ... 100 | 101 | } 102 | 103 | ``` 104 | 105 | There is no need to override `register()`. As default, the package listens for the `Illuminate\Auth\Events\Registered` event and sends the verification mail. You can disable this behavior using the `listen_registered_event` setting. 106 | 107 | ### Routes 108 | 109 | The package adds the following routes. 110 | 111 | ```php 112 | Route::get('register/verify', 'App\Http\Controllers\Auth\RegisterController@verify')->name('verifyEmailLink'); 113 | Route::get('register/verify/resend', 'App\Http\Controllers\Auth\RegisterController@showResendVerificationEmailForm')->name('showResendVerificationEmailForm'); 114 | Route::post('register/verify/resend', 'App\Http\Controllers\Auth\RegisterController@resendVerificationEmail')->name('resendVerificationEmail'); 115 | 116 | ``` 117 | 118 | ### Middleware 119 | 120 | 121 | To register the IsEmailVerified middleware add the following to the `$routeMiddleware` array within the `app/Http/Kernel.php` file: 122 | 123 | ```php 124 | protected $routeMiddleware = [ 125 | // … 126 | 'isEmailVerified' => \Lunaweb\EmailVerification\Middleware\IsEmailVerified::class, 127 | ``` 128 | 129 | Apply the middleware on your routes: 130 | 131 | ```php 132 | Route::group(['middleware' => ['web', 'auth', 'isEmailVerified']], function () { 133 | … 134 | ``` 135 | 136 | ### Events 137 | 138 | The package emits 2 events: 139 | 140 | * ``Lunaweb\EmailVerification\Events\EmailVerificationSent`` 141 | * ``Lunaweb\EmailVerification\Events\UserVerified`` 142 | 143 | 144 | 145 | ### Resend the verification mail 146 | 147 | Using the `isEmailVerified` Middleware, the following form is shown to the user. It allows the user to correct his email address and resend the verification mail. 148 | 149 | ![Screenshot](https://user-images.githubusercontent.com/1945577/27735164-7b316630-5d9e-11e7-86f6-8922a2488cfb.png) 150 | 151 | You can manually point the user to this form using the `showResendVerificationEmailForm` route (Default: `register/verify/resend`). 152 | 153 | To programmatically resend the verification mail: 154 | ```php 155 | $this->app->make('Lunaweb\EmailVerification\EmailVerification')->sendVerifyLink($user); 156 | ``` 157 | 158 | 159 | ### Customize the verification mail 160 | 161 | Therefore, override `sendEmailVerificationNotification()` of your User model. Example: 162 | 163 | ```php 164 | class User implements CanVerifyEmailContract 165 | { 166 | 167 | use CanVerifyEmail; 168 | 169 | /** 170 | * Send the email verification notification. 171 | * 172 | * @param string $token The verification mail reset token. 173 | * @param int $expiration The verification mail expiration date. 174 | * @return void 175 | */ 176 | public function sendEmailVerificationNotification($token, $expiration) 177 | { 178 | $this->notify(new MyEmailVerificationNotification($token, $expiration)); 179 | } 180 | } 181 | ``` 182 | 183 | ### Customize the resend form 184 | ``` 185 | php artisan vendor:publish --provider="Lunaweb\EmailVerification\Providers\EmailVerificationServiceProvider" --tag="views" 186 | ``` 187 | The template can be found in `resources/views/vendor/emailverification/resend.blade.php` 188 | 189 | ### Customize the messages / localization 190 | ``` 191 | php artisan vendor:publish --provider="Lunaweb\EmailVerification\Providers\EmailVerificationServiceProvider" --tag="translations" 192 | ``` 193 | The localization files can be found in `resources/lang/vendor/emailverification` 194 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "josiasmontag/laravel-email-verification", 3 | "description": "Laravel Email Verification", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Josias Montag", 8 | "email": "josias@montag.info" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=7.1.0", 13 | "illuminate/support": "5.4.*|5.5.*|5.6.*|5.7.*|5.8.*", 14 | "illuminate/queue": "5.4.*|5.5.*|5.6.*|5.7.*|5.8.*", 15 | "illuminate/auth": "5.4.*|5.5.*|5.6.*|5.7.*|5.8.*", 16 | "nesbot/carbon": "^1.20|2.0" 17 | }, 18 | "require-dev": { 19 | "mockery/mockery": "1.2", 20 | "phpunit/phpunit": "^5.7|6.2|^7.0", 21 | "orchestra/testbench": "~3.4.0|^3.5.0|^3.6.0|^3.7.0|^3.8.0", 22 | "laravel/framework": "5.4.*|5.5.*|5.6.*|5.7.*|5.8.*" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Lunaweb\\EmailVerification\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Lunaweb\\EmailVerification\\Tests\\": "tests/" 32 | } 33 | }, 34 | "config": { 35 | "sort-packages": true 36 | }, 37 | "prefer-stable": true, 38 | "minimum-stability": "dev", 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "Lunaweb\\EmailVerification\\Providers\\EmailVerificationServiceProvider" 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config/emailverification.php: -------------------------------------------------------------------------------- 1 | 1440, 11 | 12 | /** 13 | * Whether to listen to the \Illuminate\Auth\Events\Registered event to automatically 14 | * send a verification email. 15 | * Disable it to trigger the verification manually. 16 | */ 17 | 18 | "listen_registered_event" => true 19 | 20 | ]; -------------------------------------------------------------------------------- /database/migrations/2017_02_27_170020_add_verified_to_user_table.php: -------------------------------------------------------------------------------- 1 | getTable(); 24 | } 25 | 26 | /** 27 | * Run the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function up() 32 | { 33 | Schema::table($this->getUserTableName(), function (Blueprint $table) { 34 | $table->boolean('verified')->default(false); 35 | }); 36 | 37 | } 38 | 39 | /** 40 | * Reverse the migrations. 41 | * 42 | * @return void 43 | */ 44 | public function down() 45 | { 46 | Schema::table($this->getUserTableName(), function (Blueprint $table) { 47 | $table->dropColumn('verified'); 48 | }); 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests/Feature 18 | 19 | 20 | 21 | ./tests/Unit 22 | 23 | 24 | 25 | 26 | ./app 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /resources/lang/de/messages.php: -------------------------------------------------------------------------------- 1 | 'Ihr Account wurde erfolgreich aktiviert!', 10 | 'sent' => 'Wir haben Ihnen eine Email mit Ihrem Aktivierungslink gesendet!', 11 | 'token' => 'Dieser Aktivierungslink ist ungültig!', 12 | 'user' => 'Der zugehörige Account zu dieser Email Adresse wurde nicht gefunden!', 13 | 'resend' => [ 14 | 'title' => 'Email Verifikation', 15 | 'warning' => 'Wir haben Ihnen einen Aktivierungslink an :email gesendet. Bitte nutzen Sie diesen Link um Ihren Account zu aktivieren. Wenn Sie die Email nicht finden können, übeprüfen Sie bitte auch Ihren Junk/Spam Ordner!', 16 | 'instructions' => 'Falls Sie keine Email erhalten haben oder Sie sich bei Ihrer Email Adresse vertippt haben, können wir ihnen die Email erneut senden.', 17 | 'email' => 'Email', 18 | 'submit' => 'Aktivierungslink senden' 19 | ], 20 | 'email' => [ 21 | 'welcome' => 'Vielen Dank für Ihre Registrierung!', 22 | 'instructions' => 'Um die Registrierung abzuschließen und Ihren Account zu aktivieren, nutzen Sie bitte den folgenden Link:', 23 | 'action' => 'Registrierung abschließen', 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /resources/lang/en/messages.php: -------------------------------------------------------------------------------- 1 | 'Your account was activated!', 10 | 'sent' => 'We have e-mailed your account activation link!', 11 | 'token' => 'This email verification token is invalid.', 12 | 'user' => 'We can\'t find a user with that e-mail address.', 13 | 'resend' => [ 14 | 'title' => 'Email Verification', 15 | 'warning' => 'We have sent a verification mail to :email. Please activate your account with the link in this mail. If you cannot find the mail, please also check the Junk/Spam folder!', 16 | 'instructions' => 'If you have not received a verification email or if you have mistyped your email address, you can resend the verification mail.', 17 | 'email' => 'Email', 18 | 'submit' => 'Resend' 19 | ], 20 | 'email' => [ 21 | 'welcome' => 'Thank you for signing up with us!', 22 | 'instructions' => 'You\'re almost done! Please click here to complete your registration:', 23 | 'action' => 'Complete Registration', 24 | ], 25 | ]; -------------------------------------------------------------------------------- /resources/lang/nb/messages.php: -------------------------------------------------------------------------------- 1 | 'Din brukerkonto er nå aktiv!', 10 | 'sent' => 'Vi har sendt deg en e-post med lenke til å aktivere din brukerkonto!', 11 | 'token' => 'Denne aktiveringskoden er ikke gyldig.', 12 | 'user' => 'Vi finner ingen brukerkonto med denne e-postadressen', 13 | 'resend' => [ 14 | 'title' => 'E-post aktivering', 15 | 'warning' => 'Vi har sendt en mail med aktiveringslink til :email. Aktiver din brukerkonto med lenken i eposten. Hvis du ikke finner e-posten, se i spamfilteret/søppelpost!', 16 | 'instructions' => 'Hvis du ikke har mottatt aktiveringsepost eller ikke skrev inn riktig e-postadresse, kan du sende aktiveringslinken på nytt.', 17 | 'email' => 'E-post', 18 | 'submit' => 'Send på nytt' 19 | ], 20 | 'email' => [ 21 | 'welcome' => 'Takk for at du registrerte deg som bruker!', 22 | 'instructions' => 'Du er nesten i mål! Klikk her for å fullføre registrering:', 23 | 'action' => 'Fullfør registrering', 24 | ], 25 | ]; 26 | -------------------------------------------------------------------------------- /resources/views/resend.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
@lang('emailverification::messages.resend.title')
9 |
10 | 11 | @if($verified) 12 |
13 | @lang('emailverification::messages.done') 14 |
15 | @else 16 | 17 |
19 | {!! csrf_field() !!} 20 | 21 | 22 |
23 | @lang('emailverification::messages.resend.warning', ['email' => $email]) 24 |
25 | 26 |

27 | @lang('emailverification::messages.resend.instructions') 28 |

29 | 30 |
31 | 32 | 33 |
34 | 35 | 37 | 38 | @if ($errors->has('email')) 39 | 40 | {{ $errors->first('email') }} 41 | 42 | @endif 43 |
44 |
45 | 46 | 47 |
48 | 49 | 50 |
51 | 54 |
55 |
56 | 57 | 58 |
59 | 60 | 61 | @endif 62 |
63 |
64 |
65 |
66 |
67 | @endsection -------------------------------------------------------------------------------- /src/Contracts/CanVerifyEmail.php: -------------------------------------------------------------------------------- 1 | key = $key; 102 | $this->users = $users; 103 | $this->expiration = $expiration; 104 | $this->events = $events; 105 | } 106 | 107 | /** 108 | * Send a email verification link to a user. 109 | * 110 | * @param $user 111 | * @return string 112 | * @internal param array $credentials 113 | */ 114 | public function sendVerifyLink($user) 115 | { 116 | 117 | if (is_null($user)) { 118 | return static::INVALID_USER; 119 | } 120 | 121 | $expiration = Carbon::now()->addMinutes($this->expiration)->timestamp; 122 | 123 | // Once we have the reset token, we are ready to send the message out to this 124 | // user with a link to reset their password. We will then redirect back to 125 | // the current URI having nothing set in the session to indicate errors. 126 | $user->sendEmailVerificationNotification( 127 | $this->createToken($user, $expiration), 128 | $expiration 129 | ); 130 | $this->events->dispatch(new EmailVerificationSent($user)); 131 | 132 | return static::VERIFY_LINK_SENT; 133 | } 134 | 135 | /** 136 | * Verify the user for the given token. 137 | * 138 | * @param array $credentials 139 | * @param \Closure $callback 140 | * @return mixed 141 | */ 142 | public function verify(array $credentials, Closure $callback) 143 | { 144 | // If the responses from the validate method is not a user instance, we will 145 | // assume that it is a redirect and simply return it from this method and 146 | // the user is properly redirected having an error message on the post. 147 | $user = $this->validateEmailVerification($credentials); 148 | 149 | if (! $user instanceof CanVerifyEmailContract) { 150 | return $user; 151 | } 152 | 153 | if($callback($user)) { 154 | 155 | $this->events->dispatch(new UserVerified($user)); 156 | 157 | } 158 | 159 | return static::VERIFIED; 160 | } 161 | 162 | /** 163 | * Validate a email verification for the given credentials. 164 | * 165 | * @param array $credentials 166 | * @return \Lunaweb\User\Auth\CanVerifyEmailContract 167 | */ 168 | protected function validateEmailVerification(array $credentials) 169 | { 170 | if (is_null($user = $this->getUser($credentials))) { 171 | return static::INVALID_USER; 172 | } 173 | 174 | if (! $this->validateToken($user, $credentials)) { 175 | return static::INVALID_TOKEN; 176 | } 177 | 178 | if (! $this->validateTimestamp($credentials['expiration'])) { 179 | return static::EXPIRED_TOKEN; 180 | } 181 | 182 | return $user; 183 | } 184 | 185 | 186 | 187 | /** 188 | * Get the user for the given credentials. 189 | * 190 | * @param array $credentials 191 | * @return \Lunaweb\User\Auth\CanVerifyEmailContract 192 | * 193 | * @throws \UnexpectedValueException 194 | */ 195 | public function getUser(array $credentials) 196 | { 197 | $user = $this->users->retrieveByCredentials(Arr::only($credentials, ['email'])); 198 | 199 | if ($user && ! $user instanceof CanVerifyEmailContract) { 200 | throw new UnexpectedValueException('User must implement CanVerifyEmailContract interface.'); 201 | } 202 | 203 | return $user; 204 | } 205 | 206 | /** 207 | * Create a new password reset token for the given user. 208 | * 209 | * @param CanVerifyEmailContract $user 210 | * @param int $expiration 211 | * @return string 212 | */ 213 | public function createToken(CanVerifyEmailContract $user, $expiration) 214 | { 215 | $payload = $this->buildPayload($user, $user->getEmailForEmailVerification(), $expiration); 216 | 217 | return hash_hmac('sha256', $payload, $this->getKey()); 218 | } 219 | 220 | /** 221 | * Validate the given password reset token. 222 | * 223 | * @param CanVerifyEmailContract $user 224 | * @param array $credentials 225 | * @return bool 226 | */ 227 | public function validateToken(CanVerifyEmailContract $user, array $credentials) 228 | { 229 | $payload = $this->buildPayload($user, $credentials['email'], $credentials['expiration']); 230 | 231 | return hash_equals($credentials['token'], hash_hmac('sha256', $payload, $this->getKey())); 232 | } 233 | 234 | /** 235 | * Validate the given expiration timestamp. 236 | * 237 | * @param int $expiration 238 | * @return bool 239 | */ 240 | public function validateTimestamp($expiration) 241 | { 242 | return Carbon::createFromTimestamp($expiration)->isFuture(); 243 | } 244 | 245 | /** 246 | * Return the application key. 247 | * 248 | * @return string 249 | */ 250 | public function getKey() 251 | { 252 | if (Str::startsWith($this->key, 'base64:')) { 253 | return base64_decode(substr($this->key, 7)); 254 | } 255 | 256 | return $this->key; 257 | } 258 | 259 | /** 260 | * Returns the payload string containing. 261 | * 262 | * @param CanVerifyEmailContract $user 263 | * @param string $email 264 | * @param int $expiration 265 | * @return string 266 | */ 267 | protected function buildPayload(CanVerifyEmailContract $user, $email, $expiration) 268 | { 269 | return implode(';', [ 270 | $email, 271 | $expiration, 272 | $user->password, 273 | ]); 274 | } 275 | } -------------------------------------------------------------------------------- /src/Events/EmailVerificationSent.php: -------------------------------------------------------------------------------- 1 | user = $user; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/Events/UserVerified.php: -------------------------------------------------------------------------------- 1 | user = $user; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/Exceptions/UserNotVerifiedException.php: -------------------------------------------------------------------------------- 1 | ['web']], function () { 4 | 5 | // Verification 6 | Route::get('register/verify', 'App\Http\Controllers\Auth\RegisterController@verify')->name('verifyEmailLink'); 7 | Route::get('register/verify/resend', 'App\Http\Controllers\Auth\RegisterController@showResendVerificationEmailForm')->name('showResendVerificationEmailForm'); 8 | Route::post('register/verify/resend', 'App\Http\Controllers\Auth\RegisterController@resendVerificationEmail')->name('resendVerificationEmail')->middleware('throttle:2,1'); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /src/Listeners/SendUserVerificationMail.php: -------------------------------------------------------------------------------- 1 | sendVerifyLink($event->user); 44 | Session::flash($sent == EmailVerification::VERIFY_LINK_SENT ? 'success' : 'error', trans($sent)); 45 | 46 | } 47 | 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/Middleware/IsEmailVerified.php: -------------------------------------------------------------------------------- 1 | user()) && !$request->user()->verified){ 29 | return redirect(route('showResendVerificationEmailForm')); 30 | } 31 | 32 | return $next($request); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/Notifications/EmailVerification.php: -------------------------------------------------------------------------------- 1 | token = $token; 39 | $this->expiration = $expiration; 40 | } 41 | 42 | /** 43 | * Get the notification's channels. 44 | * 45 | * @param mixed $notifiable 46 | * @return array|string 47 | */ 48 | public function via($notifiable) 49 | { 50 | return ['mail']; 51 | } 52 | 53 | /** 54 | * Build the mail representation of the notification. 55 | * 56 | * @param mixed $notifiable 57 | * @return \Illuminate\Notifications\Messages\MailMessage 58 | */ 59 | public function toMail($notifiable) 60 | { 61 | $email = $notifiable->getEmailForEmailVerification(); 62 | $link = route('verifyEmailLink', ['email' => $email, 'expiration' => $this->expiration, 'token' => $this->token]); 63 | return (new MailMessage) 64 | ->subject(trans('emailverification::messages.resend.title')) 65 | ->line(trans('emailverification::messages.email.welcome')) 66 | ->line(trans('emailverification::messages.email.instructions')) 67 | ->action(trans('emailverification::messages.email.action'), $link); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/Providers/EmailVerificationEventServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 24 | 'Lunaweb\EmailVerification\Listeners\SendUserVerificationMail', 25 | ], 26 | 27 | ]; 28 | 29 | /** 30 | * Register any other events for your application. 31 | * 32 | * @return void 33 | */ 34 | public function boot() 35 | { 36 | parent::boot(); 37 | 38 | // 39 | } 40 | 41 | 42 | } -------------------------------------------------------------------------------- /src/Providers/EmailVerificationServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadRoutesFrom(__DIR__ . '/../Http/routes.php'); 28 | 29 | 30 | /* 31 | * Views 32 | */ 33 | $this->loadViewsFrom(__DIR__ . '/../../resources/views', 'emailverification'); 34 | $this->publishes([ 35 | __DIR__ . '/../../resources/views' => resource_path('views/vendor/emailverification'), 36 | ], 'views'); 37 | 38 | 39 | /* 40 | * Migrations 41 | */ 42 | $this->loadMigrationsFrom(__DIR__ . '/../../database/migrations'); 43 | $this->publishes([ 44 | __DIR__ . '/../../database/migrations' => database_path('migrations') 45 | ], 'migrations'); 46 | 47 | 48 | /* 49 | * Translations 50 | */ 51 | $this->loadTranslationsFrom(__DIR__ . '/../../resources/lang', 'emailverification'); 52 | $this->publishes([ 53 | __DIR__ . '/../../resources/lang' => resource_path('lang/vendor/emailverification'), 54 | ], 'translations'); 55 | 56 | /* 57 | * Config 58 | */ 59 | $this->publishes([ 60 | __DIR__ . '/../../config/emailverification.php' => config_path('emailverification.php'), 61 | ]); 62 | 63 | } 64 | 65 | /** 66 | * Register the application services. 67 | * 68 | * @return void 69 | */ 70 | public function register() 71 | { 72 | 73 | $this->app->register(\Lunaweb\EmailVerification\Providers\EmailVerificationEventServiceProvider::class); 74 | $this->app->singleton(\Lunaweb\EmailVerification\EmailVerification::class, function ($app) { 75 | return new EmailVerification( 76 | Auth::getProvider(), 77 | Auth::getDispatcher(), 78 | config('app.key'), 79 | config('emailverification.expire', 1440) 80 | ); 81 | }); 82 | 83 | } 84 | 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/Traits/CanVerifyEmail.php: -------------------------------------------------------------------------------- 1 | email; 24 | } 25 | /** 26 | * Send the email verification notification. 27 | * 28 | * @param string $token The verification mail reset token. 29 | * @param int $expiration The verification mail expiration date. 30 | * @return void 31 | */ 32 | public function sendEmailVerificationNotification($token, $expiration) 33 | { 34 | $this->notify(new EmailVerificationNotification($token, $expiration)); 35 | } 36 | 37 | /** 38 | * Get the verified attribute 39 | * 40 | * @return bool 41 | */ 42 | public function getVerifiedAttribute($verified) 43 | { 44 | return (bool) $verified; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/Traits/VerifiesEmail.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 29 | 'token' => 'required', 30 | 'email' => 'required|email', 31 | 'expiration' => 'required|date_format:U' 32 | ], []); 33 | 34 | } 35 | 36 | /** 37 | * Verifies the given user's email. 38 | * 39 | * @param \Illuminate\Http\Request|Request $request 40 | * @param EmailVerification $emailVerification 41 | * @return \Illuminate\Http\RedirectResponse 42 | */ 43 | public function verify(Request $request, EmailVerification $emailVerification) 44 | { 45 | $this->validateVerificationRequest($request); 46 | 47 | // Here we will attempt to verify the user. If it is successful we 48 | // will update the verified on an actual user model and persist it to the 49 | // database. Otherwise we will parse the error and return the response. 50 | $response = $emailVerification->verify( 51 | $request->only( 52 | 'email', 'expiration', 'token' 53 | ), function ($user) { 54 | return $this->verifiedEmail($user); 55 | } 56 | ); 57 | 58 | 59 | // If the user was successfully verified, we will redirect the user back to 60 | // the application's home authenticated view. If there is an error we can 61 | // redirect them back to where they came from with their error message. 62 | return $response == EmailVerification::VERIFIED 63 | ? $this->sendVerificationResponse($response) 64 | : $this->sendVerificationFailedResponse($request, $response); 65 | } 66 | 67 | 68 | /** 69 | * Show form to the user which allows resending the verification mail 70 | * 71 | * @return \Illuminate\Http\RedirectResponse 72 | */ 73 | public function showResendVerificationEmailForm() 74 | { 75 | $user = Auth::user(); 76 | return view('emailverification::resend', ['verified' => $user->verified, 'email' => $user->email]); 77 | } 78 | 79 | /** 80 | * Resend the verification mail 81 | * 82 | * @param Request $request 83 | * @return \Illuminate\Http\RedirectResponse 84 | */ 85 | public function resendVerificationEmail(Request $request) 86 | { 87 | $user = Auth::user(); 88 | 89 | $this->validate($request, [ 90 | 'email' => 'required|email|max:255|unique:users,email,' . $user->id 91 | ]); 92 | $user->email = $request->email; 93 | $user->save(); 94 | 95 | $sent = resolve('Lunaweb\EmailVerification\EmailVerification')->sendVerifyLink($user); 96 | Session::flash($sent == EmailVerification::VERIFY_LINK_SENT ? 'success' : 'error', trans($sent)); 97 | 98 | return redirect($this->redirectPath()); 99 | } 100 | 101 | /** 102 | * Store the user's verification 103 | * 104 | * @param \Illuminate\Contracts\Auth\CanResetPassword $user 105 | * @return boolean 106 | */ 107 | protected function verifiedEmail($user) 108 | { 109 | $this->guard()->login($user); 110 | 111 | if(!$user->verified) { 112 | $user->forceFill([ 113 | 'verified' => true 114 | ])->save(); 115 | return true; 116 | } 117 | 118 | return false; 119 | } 120 | 121 | /** 122 | * Get the response for a successful user verification. 123 | * 124 | * @param string $response 125 | * @return \Illuminate\Http\RedirectResponse 126 | */ 127 | protected function sendVerificationResponse($response) 128 | { 129 | return redirect($this->redirectPath())->with('success', trans($response)); 130 | } 131 | 132 | /** 133 | * Get the response for a failed user verification. 134 | * 135 | * @param \Illuminate\Http\Request 136 | * @param string $response 137 | * @return \Illuminate\Http\RedirectResponse 138 | */ 139 | protected function sendVerificationFailedResponse(Request $request, $response) 140 | { 141 | return redirect($this->redirectPath())->with('error', trans($response)); 142 | } 143 | 144 | 145 | } 146 | -------------------------------------------------------------------------------- /tests/Feature/MiddlewareTest.php: -------------------------------------------------------------------------------- 1 | 'test@user.com', 'verified' => true]); 20 | 21 | $response = $this->actingAs($user)->get('/verified'); 22 | $response->assertStatus(200); 23 | $response->assertSee('ok'); 24 | 25 | } 26 | 27 | 28 | public function testUnverifiedUser() 29 | { 30 | 31 | $user = User::create(['email' => 'test@user.com', 'verified' => false]); 32 | 33 | $response = $this->actingAs($user)->get('/verified'); 34 | $response->assertRedirect(route('showResendVerificationEmailForm')); 35 | 36 | 37 | } 38 | 39 | 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tests/Feature/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest', ['except' => ['verify', 'showResendVerificationEmailForm', 'resendVerificationEmail']]); 46 | $this->middleware('auth', ['only' => ['showResendVerificationEmailForm', 'resendVerificationEmail']]); 47 | } 48 | /** 49 | * Get a validator for an incoming registration request. 50 | * 51 | * @param array $data 52 | * @return \Illuminate\Contracts\Validation\Validator 53 | */ 54 | protected function validator(array $data) 55 | { 56 | return Validator::make($data, [ 57 | 'email' => 'required|string|email|max:255|unique:users', 58 | 'password' => 'required|string|min:6|confirmed', 59 | ]); 60 | } 61 | /** 62 | * Create a new user instance after a valid registration. 63 | * 64 | * @param array $data 65 | * @return \User 66 | */ 67 | protected function create(array $data) 68 | { 69 | return User::create([ 70 | 'email' => $data['email'], 71 | 'password' => bcrypt($data['password']), 72 | ]); 73 | } 74 | } -------------------------------------------------------------------------------- /tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | json('POST', '/register', [ 36 | 'email' => 'josias@montag.info', 37 | 'password' => 'secret', 38 | 'password_confirmation' => 'secret' 39 | ]); 40 | $response->assertRedirect('/home'); 41 | 42 | $user = User::where('email', 'josias@montag.info')->firstOrFail(); 43 | 44 | Notification::assertSentTo( 45 | [$user], EmailVerification::class 46 | ); 47 | 48 | $this->assertFalse($user->verified); 49 | 50 | $notification = Notification::sent($user, EmailVerification::class)->first(); 51 | 52 | 53 | $activationUrl = $notification->toMail($user)->actionUrl; 54 | 55 | 56 | $response = $this->get($activationUrl); 57 | 58 | $response->assertRedirect('/home'); 59 | $response->assertSessionHas('success'); 60 | 61 | $user->refresh(); 62 | $this->assertTrue($user->verified); 63 | 64 | 65 | } 66 | 67 | 68 | public function testEmitsUserVerifedEventOnce() 69 | { 70 | 71 | 72 | Event::fake(); 73 | 74 | $user = User::create(['email' => 'test@user.com', 'verified' => false]); 75 | 76 | app(\Lunaweb\EmailVerification\EmailVerification::class)->sendVerifyLink($user); 77 | 78 | $notification = Notification::sent($user, EmailVerification::class)->first(); 79 | $activationUrl = $notification->toMail($user)->actionUrl; 80 | 81 | // Open activation URL first time 82 | 83 | $this->get($activationUrl); 84 | 85 | Event::assertDispatched(UserVerified::class, function ($e) use ($user) { 86 | return $e->user->is($user); 87 | }); 88 | 89 | $this->assertTrue($user->fresh()->verified); 90 | 91 | 92 | // Open activation URL second time 93 | 94 | $this->get($activationUrl); 95 | 96 | $this->assertCount(1, Event::dispatched(UserVerified::class)); 97 | 98 | $this->assertTrue($user->fresh()->verified); 99 | 100 | 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /tests/Feature/ResendVerificationMailTest.php: -------------------------------------------------------------------------------- 1 | 'test@user.com', 'verified' => false]); 36 | 37 | $response = $this->actingAs($user)->json('POST', '/register/verify/resend', [ 38 | 'email' => 'new@email.info', 39 | ]); 40 | $response->assertRedirect('/home'); 41 | 42 | 43 | Notification::assertSentTo( 44 | [$user], EmailVerification::class 45 | ); 46 | 47 | $this->assertFalse($user->verified); 48 | 49 | $notification = Notification::sent($user, EmailVerification::class)->first(); 50 | 51 | $activationUrl = $notification->toMail($user)->actionUrl; 52 | 53 | $response = $this->get($activationUrl); 54 | 55 | $response->assertRedirect('/home'); 56 | $response->assertSessionHas('success'); 57 | 58 | $user->refresh(); 59 | $this->assertTrue($user->verified); 60 | $this->assertEquals('new@email.info', $user->email); 61 | 62 | 63 | } 64 | 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tests/Feature/TestCase.php: -------------------------------------------------------------------------------- 1 | setUpDatabase(); 28 | $this->createRoutes(); 29 | 30 | } 31 | 32 | 33 | protected function getEnvironmentSetUp($app) 34 | { 35 | 36 | $app['config']->set('database.default', 'sqlite'); 37 | $app['config']->set('database.connections.sqlite', [ 38 | 'driver' => 'sqlite', 39 | 'database' => ':memory:', 40 | 'prefix' => '', 41 | ]); 42 | 43 | 44 | // Use test User model for users provider 45 | $app['config']->set('auth.providers.users.model', User::class); 46 | 47 | } 48 | 49 | 50 | /** 51 | * @param \Illuminate\Foundation\Application $app 52 | * 53 | * @return array 54 | */ 55 | protected function getPackageProviders($app) 56 | { 57 | return [ 58 | EmailVerificationServiceProvider::class 59 | ]; 60 | } 61 | 62 | 63 | protected function setUpDatabase() 64 | { 65 | app('db')->connection()->getSchemaBuilder()->create('users', function (Blueprint $table) { 66 | $table->increments('id'); 67 | $table->string('email'); 68 | $table->boolean('verified')->default(false); 69 | $table->softDeletes(); 70 | }); 71 | } 72 | 73 | 74 | protected function createRoutes() 75 | { 76 | 77 | Route::post('register', 'Lunaweb\EmailVerification\Tests\Feature\RegisterController@register')->name('register'); 78 | Route::get('register/verify', 'Lunaweb\EmailVerification\Tests\Feature\RegisterController@verify')->name('verifyEmailLink'); 79 | Route::get('register/verify/resend', 'Lunaweb\EmailVerification\Tests\Feature\RegisterController@showResendVerificationEmailForm')->name('showResendVerificationEmailForm'); 80 | Route::post('register/verify/resend', 'Lunaweb\EmailVerification\Tests\Feature\RegisterController@resendVerificationEmail')->name('resendVerificationEmail'); 81 | 82 | 83 | Route::get('/verified', function () { 84 | return 'ok!'; 85 | })->middleware(IsEmailVerified::class); 86 | 87 | 88 | app('router')->getRoutes()->refreshNameLookups(); 89 | app('router')->getRoutes()->refreshActionLookups(); 90 | 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /tests/Feature/User.php: -------------------------------------------------------------------------------- 1 | getMocks(); 27 | $emailVerification = $this->getMockBuilder('Lunaweb\EmailVerification\EmailVerification')->setMethods(['getUser', 'makeErrorRedirect'])->setConstructorArgs(array_values($mocks))->getMock(); 28 | 29 | $this->assertEquals(EmailVerification::INVALID_USER, $emailVerification->sendVerifyLink(null)); 30 | } 31 | 32 | /** 33 | * @expectedException UnexpectedValueException 34 | */ 35 | public function testGetUserThrowsExceptionIfUserDoesntImplementCanVerifyEmail() 36 | { 37 | $creds = ['email' => 'josias@montag.info']; 38 | $emailVerification = $this->getEmailVerification($mocks = $this->getMocks()); 39 | $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn('foo'); 40 | 41 | $emailVerification->getUser($creds); 42 | } 43 | 44 | public function testUserIsRetrievedByCredentials() 45 | { 46 | $creds = ['email' => 'josias@montag.info']; 47 | $emailVerification = $this->getEmailVerification($mocks = $this->getMocks()); 48 | $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with($creds)->andReturn($user = m::mock('Lunaweb\EmailVerification\Contracts\CanVerifyEmail')); 49 | 50 | $this->assertEquals($user, $emailVerification->getUser($creds)); 51 | } 52 | 53 | public function testEmailVerificationCreatesTokenAndRedirectsWithoutError() 54 | { 55 | $creds = ['email' => 'josias@montag.info']; 56 | $mocks = $this->getMocks(); 57 | $emailVerification = $this->getMockBuilder('Lunaweb\EmailVerification\EmailVerification')->setMethods(['getKey'])->setConstructorArgs(array_values($mocks))->getMock(); 58 | $user = m::mock('Lunaweb\EmailVerification\Contracts\CanVerifyEmail'); 59 | $user->password = 'foo'; 60 | $user->updated_at = Carbon::now(); 61 | $user->shouldReceive('getEmailForEmailVerification')->once(); 62 | 63 | $user->shouldReceive('sendEmailVerificationNotification'); 64 | $this->assertEquals(EmailVerification::VERIFY_LINK_SENT, $emailVerification->sendVerifyLink($user)); 65 | } 66 | 67 | 68 | 69 | public function testVerifiedIsReturnedByVerifyWhenCredsAreValid() 70 | { 71 | $creds = ['email' => 'josias@montag.info', 'expiration' => time() + 1000]; 72 | $user = m::mock('Lunaweb\EmailVerification\Contracts\CanVerifyEmail'); 73 | $user->shouldReceive('getEmailForEmailVerification')->andReturn("josias@montag.info"); 74 | $user->password = 'foo'; 75 | $user->updated_at = Carbon::now(); 76 | 77 | $emailVerification = $this->getEmailVerification($mocks = $this->getMocks()); 78 | $creds['token'] = $emailVerification->createToken($user, $creds['expiration']); 79 | $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'josias@montag.info'])->andReturn($user); 80 | 81 | $this->assertEquals(EmailVerification::VERIFIED, $emailVerification->verify($creds, function () { 82 | // 83 | })); 84 | } 85 | 86 | 87 | public function testInvalidTokenIsReturnedByVerifyWhenTokenInvalid() 88 | { 89 | $creds = ['email' => 'josias@montag.info', 'expiration' => time() + 1000]; 90 | $user = m::mock('Lunaweb\EmailVerification\Contracts\CanVerifyEmail'); 91 | $user->shouldReceive('getEmailForEmailVerification')->andReturn("josias@montag.info"); 92 | $user->password = 'foo'; 93 | $user->updated_at = Carbon::now(); 94 | 95 | $emailVerification = $this->getEmailVerification($mocks = $this->getMocks()); 96 | $creds['token'] = "invalid"; 97 | $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'josias@montag.info'])->andReturn($user); 98 | 99 | $this->assertEquals(EmailVerification::INVALID_TOKEN, $emailVerification->verify($creds, function () { 100 | // 101 | })); 102 | } 103 | 104 | 105 | public function testInvalidUserIsReturnedByVerifyWhenEmailInvalid() 106 | { 107 | $creds = ['email' => 'invalid@email.de', 'expiration' => time() + 1000]; 108 | $user = m::mock('Lunaweb\EmailVerification\Contracts\CanVerifyEmail'); 109 | $user->shouldReceive('getEmailForEmailVerification')->andReturn("josias@montag.info"); 110 | $user->password = 'foo'; 111 | $user->updated_at = Carbon::now(); 112 | 113 | $emailVerification = $this->getEmailVerification($mocks = $this->getMocks()); 114 | $creds['token'] = $emailVerification->createToken($user, $creds['expiration']); 115 | $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'invalid@email.de'])->andReturn(null); 116 | 117 | $this->assertEquals(EmailVerification::INVALID_USER, $emailVerification->verify($creds, function () { 118 | // 119 | })); 120 | } 121 | 122 | 123 | public function testInvalidTokenIsReturnedByVerifyWhenTokenExpired() 124 | { 125 | $creds = ['email' => 'josias@montag.info', 'expiration' => time() - 1000]; 126 | $user = m::mock('Lunaweb\EmailVerification\Contracts\CanVerifyEmail'); 127 | $user->shouldReceive('getEmailForEmailVerification')->andReturn("josias@montag.info"); 128 | $user->password = 'foo'; 129 | $user->updated_at = Carbon::now(); 130 | 131 | $emailVerification = $this->getEmailVerification($mocks = $this->getMocks()); 132 | $creds['token'] = $emailVerification->createToken($user, $creds['expiration']); 133 | $mocks['users']->shouldReceive('retrieveByCredentials')->once()->with(['email' => 'josias@montag.info'])->andReturn($user); 134 | 135 | $this->assertEquals(EmailVerification::EXPIRED_TOKEN, $emailVerification->verify($creds, function () { 136 | // 137 | })); 138 | } 139 | 140 | 141 | protected function getEmailVerification($mocks) 142 | { 143 | return new EmailVerification($mocks['users'], $mocks['events'], $mocks['key'], $mocks['expiration']); 144 | } 145 | 146 | protected function getMocks() 147 | { 148 | $eventsMock = m::mock('Illuminate\Contracts\Events\Dispatcher'); 149 | $eventsMock->shouldReceive('dispatch'); 150 | $mocks = [ 151 | 'users' => m::mock('Illuminate\Contracts\Auth\UserProvider'), 152 | 'events' => $eventsMock, 153 | 'key' => 'secret', 154 | 'expiration' => 10, 155 | ]; 156 | return $mocks; 157 | } 158 | 159 | } --------------------------------------------------------------------------------