diff --git a/.env.dev b/.env.dev
index 1f86fab5e..7e967bdc7 100644
--- a/.env.dev
+++ b/.env.dev
@@ -38,6 +38,8 @@ APP_DEFAULT_LOCALE=en
# Ability to disable signups on your instance.
# Can be true or false. Default to false.
APP_DISABLE_SIGNUP=false
+# Enable user email verification.
+APP_SIGNUP_DOUBLE_OPTIN=false
# Set trusted proxy IP addresses. Useful for ssl terminating loadbalancers.
# To trust all proxies that connect directly to your server, use a "*".
diff --git a/.env.example b/.env.example
index 5853ae1ec..c8e75fb58 100644
--- a/.env.example
+++ b/.env.example
@@ -60,6 +60,9 @@ APP_DEFAULT_LOCALE=en
# Can be true or false. Default to false.
APP_DISABLE_SIGNUP=true
+# Enable user email verification.
+APP_SIGNUP_DOUBLE_OPTIN=false
+
# Set trusted proxy IP addresses.
# To trust all proxies that connect directly to your server, use a "*".
# To trust one or more specific proxies that connect directly to your server, use a comma separated list of IP addresses.
diff --git a/CHANGELOG b/CHANGELOG
index e9fb4d401..3b64d5fbb 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,7 @@ UNRELEASED CHANGES:
* Fix duplication of tags when filtering contacts
* Add trusted proxies to run behind a ssl terminating loadbalancer
* Fix reminders for past events are visible on the dashboard
+* Add email address verification on register, and email change
RELEASED VERSIONS:
diff --git a/app/Http/Controllers/Auth/EmailChangeController.php b/app/Http/Controllers/Auth/EmailChangeController.php
new file mode 100644
index 000000000..e35cf929d
--- /dev/null
+++ b/app/Http/Controllers/Auth/EmailChangeController.php
@@ -0,0 +1,121 @@
+session()->has('user_id')) {
+ $user = User::findOrFail($request->session()->get('user_id'));
+
+ return view('auth.emailchange1')
+ ->with('email', $user->email);
+ }
+
+ return redirect('/');
+ }
+
+ /**
+ * Display a listing of the resource.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @return \Illuminate\Http\Response
+ */
+ public function index(Request $request)
+ {
+ $user = auth()->user();
+
+ return view('auth.emailchange2')
+ ->with('email', $user->email);
+ }
+
+ /**
+ * Change user email.
+ *
+ * @param EmailChangeRequest $request
+ * @return \Illuminate\Http\Response
+ */
+ public function save(EmailChangeRequest $request)
+ {
+ $response = $this->validateAndEmailChange($request);
+
+ return $response == 'auth.email_changed'
+ ? $this->sendChangedResponse($response)
+ : $this->sendChangedFailedResponse($response);
+ }
+
+ /**
+ * Validate a password change request and update password of the user.
+ *
+ * @param EmailChangeRequest $request
+ * @return mixed
+ */
+ protected function validateAndEmailChange(EmailChangeRequest $request)
+ {
+ $user = auth()->user();
+
+ // Change email of the user
+ $user->email = $request->get('newmail');
+
+ // Resend validation token
+ $user->confirmation_code = str_random(30);
+ $user->confirmed = false;
+ $user->save();
+
+ $user->notify(new ConfirmEmail);
+
+ // Logout the user
+ Auth::guard()->logout();
+ $request->session()->invalidate();
+
+ return 'auth.email_changed';
+ }
+
+ /**
+ * Get the response for a successful password changed.
+ *
+ * @param string $response
+ * @return \Illuminate\Http\Response
+ */
+ protected function sendChangedResponse($response)
+ {
+ return redirect('/')
+ ->with('status', trans($response));
+ }
+
+ /**
+ * Get the response for a failed password.
+ *
+ * @param string $response
+ * @return \Illuminate\Http\Response
+ */
+ protected function sendChangedFailedResponse($response)
+ {
+ return redirect('/')
+ ->withErrors(trans($response));
+ }
+}
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index b2e119962..5eac005d8 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -4,7 +4,7 @@ namespace App\Http\Controllers\Auth;
use App\Account;
use App\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\AuthenticatesUsers;
+use Bestmomo\LaravelEmailConfirmation\Traits\AuthenticatesUsers;
class LoginController extends Controller
{
diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php
index 2b479c770..b4e8394b2 100644
--- a/app/Http/Controllers/Auth/RegisterController.php
+++ b/app/Http/Controllers/Auth/RegisterController.php
@@ -9,7 +9,7 @@ use App\Jobs\SendNewUserAlert;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
-use Illuminate\Foundation\Auth\RegistersUsers;
+use Bestmomo\LaravelEmailConfirmation\Traits\RegistersUsers;
class RegisterController extends Controller
{
@@ -100,4 +100,26 @@ class RegisterController extends Controller
return $user;
}
+
+ /**
+ * The user has been registered.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param mixed $user
+ * @return mixed
+ */
+ protected function registered(Request $request, $user)
+ {
+ $first = Account::count() == 1;
+ if (! config('monica.signup_double_optin') || $first) {
+ // if signup_double_optin is disabled, skip the confirm email part
+ $user->confirmation_code = null;
+ $user->confirmed = true;
+ $user->save();
+
+ $this->guard()->login($user);
+
+ return redirect(route('login'));
+ }
+ }
}
diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php
index 637d3644d..5edb99229 100644
--- a/app/Http/Controllers/Auth/ResetPasswordController.php
+++ b/app/Http/Controllers/Auth/ResetPasswordController.php
@@ -3,7 +3,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\ResetsPasswords;
+use Bestmomo\LaravelEmailConfirmation\Traits\ResetsPasswords;
class ResetPasswordController extends Controller
{
diff --git a/app/Http/Controllers/Auth/Validate2faController.php b/app/Http/Controllers/Auth/Validate2faController.php
new file mode 100644
index 000000000..414edbcea
--- /dev/null
+++ b/app/Http/Controllers/Auth/Validate2faController.php
@@ -0,0 +1,24 @@
+has('url')) {
+ return redirect($request->get('url'));
+ }
+
+ return redirect('/');
+ }
+}
diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php
index 9123f6415..4d3efe9fe 100644
--- a/app/Http/Controllers/SettingsController.php
+++ b/app/Http/Controllers/SettingsController.php
@@ -12,6 +12,7 @@ use App\Jobs\ExportAccountAsSQL;
use App\Jobs\AddContactFromVCard;
use App\Jobs\SendInvitationEmail;
use Illuminate\Support\Facades\DB;
+use App\Notifications\ConfirmEmail;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\ImportsRequest;
use App\Http\Requests\SettingsRequest;
@@ -72,11 +73,12 @@ class SettingsController extends Controller
*/
public function save(SettingsRequest $request)
{
- $request->user()->update(
+ $user = $request->user();
+
+ $user->update(
$request->only([
'first_name',
'last_name',
- 'email',
'timezone',
'locale',
'currency_id',
@@ -86,8 +88,17 @@ class SettingsController extends Controller
]
);
- $request->user()->account->default_time_reminder_is_sent = $request->get('reminder_time');
- $request->user()->account->save();
+ if ($user->email != $request->get('email')) {
+ $user->email = $request->get('email');
+ $user->confirmation_code = str_random(30);
+ $user->confirmed = false;
+ $user->save();
+
+ $user->notify(new ConfirmEmail);
+ }
+
+ $user->account->default_time_reminder_is_sent = $request->get('reminder_time');
+ $user->account->save();
return redirect('settings')
->with('status', trans('settings.settings_success', [], $request['locale']));
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index fcd3e12a8..0f41e9f11 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -61,5 +61,6 @@ class Kernel extends HttpKernel
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'2fa' => \PragmaRX\Google2FALaravel\Middleware::class,
'locale' => \App\Http\Middleware\CheckLocale::class,
+ 'auth.confirm' => \App\Http\Middleware\AuthEmailConfirm::class,
];
}
diff --git a/app/Http/Middleware/AuthEmailConfirm.php b/app/Http/Middleware/AuthEmailConfirm.php
new file mode 100644
index 000000000..53374a567
--- /dev/null
+++ b/app/Http/Middleware/AuthEmailConfirm.php
@@ -0,0 +1,32 @@
+user()->confirmed) {
+ // Logout the user
+ Auth::guard()->logout();
+ $request->session()->invalidate();
+
+ return redirect('/')
+ ->with('confirmation-danger', trans('confirmation::confirmation.again'));
+ }
+ }
+
+ return $next($request);
+ }
+}
diff --git a/app/Http/Requests/EmailChangeRequest.php b/app/Http/Requests/EmailChangeRequest.php
new file mode 100644
index 000000000..65a3548c8
--- /dev/null
+++ b/app/Http/Requests/EmailChangeRequest.php
@@ -0,0 +1,28 @@
+ 'required|email|max:255|unique:users,email',
+ ];
+ }
+}
diff --git a/app/Jobs/SendNewUserAlert.php b/app/Jobs/SendNewUserAlert.php
index 38568f186..c50565820 100644
--- a/app/Jobs/SendNewUserAlert.php
+++ b/app/Jobs/SendNewUserAlert.php
@@ -33,6 +33,10 @@ class SendNewUserAlert implements ShouldQueue
*/
public function handle()
{
- Mail::to(config('monica.email_new_user_notification'))->send(new NewUserAlert($this->user));
+ $email = config('monica.email_new_user_notification');
+ if (! empty($email)) {
+ Mail::to($email)
+ ->send(new NewUserAlert($this->user));
+ }
}
}
diff --git a/app/Notifications/ConfirmEmail.php b/app/Notifications/ConfirmEmail.php
new file mode 100644
index 000000000..c3a445120
--- /dev/null
+++ b/app/Notifications/ConfirmEmail.php
@@ -0,0 +1,45 @@
+subject(trans('mail.confirmation_email_title'))
+ ->line(trans('mail.confirmation_email_title'))
+ ->line(trans('mail.confirmation_email_intro'))
+ ->action(trans('mail.confirmation_email_button'),
+ url("confirmation/$notifiable->id/$notifiable->confirmation_code"));
+ }
+}
diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php
index 5222dd4e8..ad87511a7 100644
--- a/app/Providers/RouteServiceProvider.php
+++ b/app/Providers/RouteServiceProvider.php
@@ -173,7 +173,8 @@ class RouteServiceProvider extends ServiceProvider
$this->mapWebRoutes($router);
$this->mapOAuthRoutes($router);
- //
+
+ $this->mapSpecialRoutes($router);
}
/**
@@ -227,4 +228,21 @@ class RouteServiceProvider extends ServiceProvider
require base_path('routes/api.php');
});
}
+
+ /**
+ * Define the "special" routes for the application.
+ *
+ * These routes are typically stateless.
+ *
+ * @return void
+ */
+ protected function mapSpecialRoutes(Router $router)
+ {
+ $router->group([
+ 'middleware' => 'web',
+ 'namespace' => $this->namespace,
+ ], function ($router) {
+ require base_path('routes/special.php');
+ });
+ }
}
diff --git a/composer.json b/composer.json
index c71d2ce3a..fff13a401 100644
--- a/composer.json
+++ b/composer.json
@@ -9,6 +9,7 @@
"ext-bcmath": "*",
"ext-intl": "*",
"bacon/bacon-qr-code": "^1.0",
+ "bestmomo/laravel-email-confirmation": "^1.1",
"creativeorange/gravatar": "~1.0",
"doctrine/dbal": "^2.5",
"erusev/parsedown": "~1.7",
diff --git a/composer.lock b/composer.lock
index 56d05667a..b7bfd00e9 100644
--- a/composer.lock
+++ b/composer.lock
@@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "654c35e2a59e4ad320a8eff5c8fc57c4",
+ "content-hash": "77646740ed930baa5e0db5adc6386704",
"packages": [
{
"name": "aws/aws-sdk-php",
@@ -176,6 +176,52 @@
],
"time": "2017-04-04T11:38:05+00:00"
},
+ {
+ "name": "bestmomo/laravel-email-confirmation",
+ "version": "V1.1.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bestmomo/laravel-email-confirmation.git",
+ "reference": "9fff1946e8a3e89231ae363f653a2178627054f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bestmomo/laravel-email-confirmation/zipball/9fff1946e8a3e89231ae363f653a2178627054f9",
+ "reference": "9fff1946e8a3e89231ae363f653a2178627054f9",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/support": "~5.4",
+ "php": ">=5.6.4"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Bestmomo\\LaravelEmailConfirmation\\ServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Bestmomo\\LaravelEmailConfirmation\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bestmomo",
+ "email": "grandheretique@free.fr",
+ "homepage": "http://laravel.sillo.org"
+ }
+ ],
+ "description": "Email address confirmation with Laravel",
+ "homepage": "http://github.com/laravel-email-confirmation",
+ "time": "2018-03-28T08:45:47+00:00"
+ },
{
"name": "colinodell/json5",
"version": "v1.0.4",
@@ -2066,7 +2112,7 @@
{
"name": "Luís Otávio Cobucci Oblonczyk",
"email": "lcobucci@gmail.com",
- "role": "developer"
+ "role": "Developer"
}
],
"description": "A simple library to work with JSON Web Token and JSON Web Signature",
diff --git a/config/app.php b/config/app.php
index 6736be0d8..ea91c2a4f 100644
--- a/config/app.php
+++ b/config/app.php
@@ -2,6 +2,19 @@
return [
+ /*
+ |--------------------------------------------------------------------------
+ | Application Name
+ |--------------------------------------------------------------------------
+ |
+ | This value is the name of your application. This value is used when the
+ | framework needs to place the application's name in a notification or
+ | any other location as required by the application or its packages.
+ |
+ */
+
+ 'name' => env('APP_NAME', 'Monica'),
+
/*
|--------------------------------------------------------------------------
| Application Environment
diff --git a/config/monica.php b/config/monica.php
index 307fd2929..06106d1a0 100644
--- a/config/monica.php
+++ b/config/monica.php
@@ -12,6 +12,16 @@ return [
*/
'disable_signup' => env('APP_DISABLE_SIGNUP', false),
+ /*
+ |--------------------------------------------------------------------------
+ | Activate double optin on signup
+ |--------------------------------------------------------------------------
+ |
+ | Activates double optin on signup
+ |
+ */
+ 'signup_double_optin' => env('APP_SIGNUP_DOUBLE_OPTIN', true),
+
/*
|--------------------------------------------------------------------------
| New User Email Notification
diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php
index fffaa51ac..710d29213 100644
--- a/database/factories/ModelFactory.php
+++ b/database/factories/ModelFactory.php
@@ -20,6 +20,7 @@ $factory->define(App\User::class, function (Faker\Generator $faker) {
'remember_token' => str_random(10),
'timezone' => config('app.timezone'),
'name_order' => 'firstname_first',
+ 'confirmed' => true,
'account_id' => factory(App\Account::class)->create()->id,
];
});
diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php
index 2f5b0d673..85a4c11cb 100644
--- a/resources/lang/en/auth.php
+++ b/resources/lang/en/auth.php
@@ -24,6 +24,7 @@ return [
'2fa_recuperation_code' => 'Enter a two factor recovery code',
'login_to_account' => 'Login to your account',
+ 'login_again' => 'Please login again to your account',
'email' => 'Email',
'password' => 'Password',
'login' => 'Login',
@@ -59,4 +60,9 @@ return [
'register_policy' => 'Signing up signifies you’ve read and agree to our Privacy Policy and Terms of use.',
'register_invitation_email' => 'For security purposes, please indicate the email of the person who’ve invited you to join this account. This information is provided in the invitation email.',
+ 'confirmation_again' => 'If you want to change your email address you can click here.',
+ 'email_change_current_email' => 'Current email address:',
+ 'email_change_title' => 'Change your email address',
+ 'email_change_new' => 'New email address',
+ 'email_changed' => 'Your email address has been changed. Check your mailbox to validate it.',
];
diff --git a/resources/lang/en/mail.php b/resources/lang/en/mail.php
index efb8520c3..345ca3a2a 100644
--- a/resources/lang/en/mail.php
+++ b/resources/lang/en/mail.php
@@ -13,4 +13,13 @@ return [
'stay_in_touch_subject_line' => 'Stay in touch with :name',
'stay_in_touch_subject_description' => 'You asked to be reminded to stay in touch with :name every :frequency days.',
+
+ 'notifications_whoops' => 'Whoops!',
+ 'notifications_hello' => 'Hello!',
+ 'notifications_regards' => 'Regards',
+ 'notifications_footer' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser: [:actionURL](:actionURL)',
+
+ 'confirmation_email_title' => 'Monica – Email verification',
+ 'confirmation_email_intro'=> 'To validate your email click on the button below',
+ 'confirmation_email_button' => 'Verify email address',
];
diff --git a/resources/lang/vendor/confirmation/en/confirmation.php b/resources/lang/vendor/confirmation/en/confirmation.php
new file mode 100644
index 000000000..b6721dbc4
--- /dev/null
+++ b/resources/lang/vendor/confirmation/en/confirmation.php
@@ -0,0 +1,10 @@
+ 'Thanks for signing up! Please check your emails to confirm your email address.',
+ 'success' => 'You have successfully verified your account! You can now login.',
+ 'again' => 'You must verify your email before you can access the site.' .
+ '
If you have not received the confirmation email check your spam folder.'.
+ '
To get a new confirmation email please click here.',
+ 'resend' => 'A confirmation message has been sent. Please check your mailbox.'
+];
diff --git a/resources/views/auth/emailchange1.blade.php b/resources/views/auth/emailchange1.blade.php
new file mode 100644
index 000000000..af90c87a2
--- /dev/null
+++ b/resources/views/auth/emailchange1.blade.php
@@ -0,0 +1,49 @@
+@extends('marketing.skeleton')
+
+@section('content')
+