diff --git a/app/EmailVerification.php b/app/EmailVerification.php new file mode 100644 index 000000000..cdc9b8bb1 --- /dev/null +++ b/app/EmailVerification.php @@ -0,0 +1,15 @@ +user_token . '/' . $this->random_token; + return "{$base}{$path}"; + } +} diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php index 6f5eeea43..999f6b711 100644 --- a/app/Http/Controllers/AccountController.php +++ b/app/Http/Controllers/AccountController.php @@ -4,8 +4,9 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use Carbon\Carbon; -use Auth, Cache, Redis; -use App\{Notification, Profile, User}; +use App\Mail\ConfirmEmail; +use Auth, DB, Cache, Mail, Redis; +use App\{EmailVerification, Notification, Profile, User}; class AccountController extends Controller { @@ -30,6 +31,46 @@ class AccountController extends Controller return view('account.activity', compact('profile', 'notifications')); } + public function verifyEmail(Request $request) + { + return view('account.verify_email'); + } + + public function sendVerifyEmail(Request $request) + { + if(EmailVerification::whereUserId(Auth::id())->count() !== 0) { + return redirect()->back()->with('status', 'A verification email has already been sent! Please check your email.'); + } + + $user = User::whereNull('email_verified_at')->find(Auth::id()); + $utoken = hash('sha512', $user->id); + $rtoken = str_random(40); + + $verify = new EmailVerification; + $verify->user_id = $user->id; + $verify->email = $user->email; + $verify->user_token = $utoken; + $verify->random_token = $rtoken; + $verify->save(); + + Mail::to($user->email)->send(new ConfirmEmail($verify)); + + return redirect()->back()->with('status', 'Email verification email sent!'); + } + + public function confirmVerifyEmail(Request $request, $userToken, $randomToken) + { + $verify = EmailVerification::where(DB::raw('BINARY `user_token`'), $userToken) + ->where(DB::raw('BINARY `random_token`'), $randomToken) + ->firstOrFail(); + if(Auth::id() === $verify->user_id) { + $user = User::find(Auth::id()); + $user->email_verified_at = Carbon::now(); + $user->save(); + return redirect('/timeline'); + } + } + public function fetchNotifications($id) { $key = config('cache.prefix') . ":user.{$id}.notifications"; diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 52a072844..61e44822f 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -60,5 +60,6 @@ class Kernel extends HttpKernel 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'validemail' => \App\Http\Middleware\EmailVerificationCheck::class, ]; } diff --git a/app/Http/Middleware/EmailVerificationCheck.php b/app/Http/Middleware/EmailVerificationCheck.php new file mode 100644 index 000000000..04ee1fb1b --- /dev/null +++ b/app/Http/Middleware/EmailVerificationCheck.php @@ -0,0 +1,28 @@ +user() && + config('pixelfed.enforce_email_verification') && + is_null($request->user()->email_verified_at) && + !$request->is('i/verify-email') && !$request->is('login') && + !$request->is('i/confirm-email/*') + ) { + return redirect('/i/verify-email'); + } + return $next($request); + } +} diff --git a/app/Mail/ConfirmEmail.php b/app/Mail/ConfirmEmail.php new file mode 100644 index 000000000..83191f5fe --- /dev/null +++ b/app/Mail/ConfirmEmail.php @@ -0,0 +1,34 @@ +verify = $verify; + } + + /** + * Build the message. + * + * @return $this + */ + public function build() + { + return $this->markdown('emails.confirm_email')->with(['verify'=>$this->verify]); + } +} diff --git a/config/pixelfed.php b/config/pixelfed.php index a3ec46783..c825643a7 100644 --- a/config/pixelfed.php +++ b/config/pixelfed.php @@ -106,5 +106,15 @@ return [ | */ 'max_album_length' => env('MAX_ALBUM_LENGTH', 4), + + /* + |-------------------------------------------------------------------------- + | Email Verification + |-------------------------------------------------------------------------- + | + | Require email verification before a new user can do anything. + | + */ + 'enforce_email_verification' => env('ENFORCE_EMAIL_VERIFICATION', true), ]; \ No newline at end of file diff --git a/database/migrations/2018_06_14_041422_create_email_verifications_table.php b/database/migrations/2018_06_14_041422_create_email_verifications_table.php new file mode 100644 index 000000000..cbcc6ae3a --- /dev/null +++ b/database/migrations/2018_06_14_041422_create_email_verifications_table.php @@ -0,0 +1,35 @@ +bigIncrements('id'); + $table->bigInteger('user_id')->unsigned(); + $table->string('email')->nullable(); + $table->string('user_token')->index(); + $table->string('random_token')->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('email_verifications'); + } +} diff --git a/resources/views/account/verify_email.blade.php b/resources/views/account/verify_email.blade.php new file mode 100644 index 000000000..99ae26f3c --- /dev/null +++ b/resources/views/account/verify_email.blade.php @@ -0,0 +1,24 @@ +@extends('layouts.app') + +@section('content') +
+
+ @if (session('status')) +
+ {{ session('status') }} +
+ @endif +
+
Confirm Email Address
+
+

You need to confirm your email address ({{Auth::user()->email}}) before you can proceed.

+
+
+ @csrf + +
+
+
+
+
+@endsection \ No newline at end of file diff --git a/resources/views/emails/confirm_email.blade.php b/resources/views/emails/confirm_email.blade.php new file mode 100644 index 000000000..20d388b96 --- /dev/null +++ b/resources/views/emails/confirm_email.blade.php @@ -0,0 +1,12 @@ +@component('mail::message') +# Email Confirmation + +Please confirm your email address. + +@component('mail::button', ['url' => $verify->url()]) +Confirm Email +@endcomponent + +Thanks,
+{{ config('app.name') }} +@endcomponent diff --git a/routes/web.php b/routes/web.php index 160d9bd6b..eb9fe2ad1 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,7 +25,7 @@ Route::domain(config('pixelfed.domain.admin'))->group(function() { Route::get('media/list', 'AdminController@media')->name('admin.media'); }); -Route::domain(config('pixelfed.domain.app'))->group(function() { +Route::domain(config('pixelfed.domain.app'))->middleware('validemail')->group(function() { Route::view('/', 'welcome'); @@ -62,6 +62,9 @@ Route::domain(config('pixelfed.domain.app'))->group(function() { Route::post('follow', 'FollowerController@store'); Route::post('bookmark', 'BookmarkController@store'); Route::get('lang/{locale}', 'SiteController@changeLocale'); + Route::get('verify-email', 'AccountController@verifyEmail'); + Route::post('verify-email', 'AccountController@sendVerifyEmail'); + Route::get('confirm-email/{userToken}/{randomToken}', 'AccountController@confirmVerifyEmail'); Route::group(['prefix' => 'report'], function() { Route::get('/', 'ReportController@showForm')->name('report.form');