Browse Source

Add Email Verification

Daniel Supernault 7 years ago
parent
commit
a415b421cb

+ 15 - 0
app/EmailVerification.php

@@ -0,0 +1,15 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+class EmailVerification extends Model
+{
+    public function url()
+    {
+      $base = config('app.url');
+      $path = '/i/confirm-email/' . $this->user_token . '/' . $this->random_token;
+      return "{$base}{$path}";
+    }
+}

+ 43 - 2
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";

+ 1 - 0
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,
     ];
 }

+ 28 - 0
app/Http/Middleware/EmailVerificationCheck.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace App\Http\Middleware;
+
+use Auth, Closure;
+
+class EmailVerificationCheck
+{
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        if($request->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);
+    }
+}

+ 34 - 0
app/Mail/ConfirmEmail.php

@@ -0,0 +1,34 @@
+<?php
+
+namespace App\Mail;
+
+use App\EmailVerification;
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+class ConfirmEmail extends Mailable
+{
+    use Queueable, SerializesModels;
+
+    /**
+     * Create a new message instance.
+     *
+     * @return void
+     */
+    public function __construct(EmailVerification $verify)
+    {
+        $this->verify = $verify;
+    }
+
+    /**
+     * Build the message.
+     *
+     * @return $this
+     */
+    public function build()
+    {
+        return $this->markdown('emails.confirm_email')->with(['verify'=>$this->verify]);
+    }
+}

+ 10 - 0
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),
     
 ];

+ 35 - 0
database/migrations/2018_06_14_041422_create_email_verifications_table.php

@@ -0,0 +1,35 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class CreateEmailVerificationsTable extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('email_verifications', function (Blueprint $table) {
+            $table->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');
+    }
+}

+ 24 - 0
resources/views/account/verify_email.blade.php

@@ -0,0 +1,24 @@
+@extends('layouts.app')
+
+@section('content')
+<div class="container mt-4">
+  <div class="col-12 col-md-8 offset-md-2">
+    @if (session('status'))
+        <div class="alert alert-success">
+            {{ session('status') }}
+        </div>
+    @endif
+    <div class="card">
+      <div class="card-header font-weight-bold bg-white">Confirm Email Address</div>
+      <div class="card-body">
+        <p class="lead">You need to confirm your email address (<span class="font-weight-bold">{{Auth::user()->email}}</span>) before you can proceed.</p>
+        <hr>
+        <form method="post">
+          @csrf
+          <button type="submit" class="btn btn-primary btn-block py-1 font-weight-bold">Send Confirmation Email</button>
+        </form>
+      </div>
+    </div>
+  </div>
+</div>
+@endsection

+ 12 - 0
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,<br>
+{{ config('app.name') }}
+@endcomponent

+ 4 - 1
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');