Skip to content

Commit

Permalink
Email Validation.
Browse files Browse the repository at this point in the history
  • Loading branch information
spitfire305 committed Jan 24, 2024
1 parent 75580c2 commit a468176
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 4 deletions.
2 changes: 1 addition & 1 deletion app/Http/Controllers/Settings/SubscriptionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function index()
));
}

public function change(Request $request)
public function change(Request $request, Tier $tier)
{
$user = $request->user();
$period = $request->get('period', 'monthly');
Expand Down
32 changes: 32 additions & 0 deletions app/Http/Controllers/User/EmailValidationController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\Controller;
use App\Models\UserValidation;
use App\User;
use Illuminate\Http\Request;


class EmailValidationController extends Controller
{

public function validateEmail(Request $request, User $user)
{
$token = $request->get('token');

/** @var UserValidation $validation */
$validation = UserValidation::where('user_id', $user->id)
->where('token', $token)
->first();

if ($validation->exists) {
$validation->is_valid = true;
$validation->saveQuietly();
} else {
response()->redirectTo(route('settings.subscription'))->withError(__('emails/validation.error'));

Check failure on line 27 in app/Http/Controllers/User/EmailValidationController.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

Call to method Illuminate\Http\RedirectResponse::withError() on a separate line has no effect.
}

return response()->redirectTo(route('settings.subscription'))->withSuccess(__('emails/validation.success'));
}
}
53 changes: 53 additions & 0 deletions app/Jobs/Emails/Subscriptions/EmailValidationJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace App\Jobs\Emails\Subscriptions;

use App\Mail\Subscription\User\ValidationEmail;
use App\Models\UserValidation;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;

class EmailValidationJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
use SerializesModels;

/** @var int user id */
protected $user;
protected $token;

/**
*/
public function __construct(User $user, string $token)
{
$this->user = $user->id;
$this->token = $token;
}

/**
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function handle()
{
// User deleted their account already? Sure thing
/** @var User|null $user */
$user = User::find($this->user);
if (empty($user)) {
return;
}

// Send an email to the user
Mail::to($user->email)
->locale($user->locale)
->send(
new ValidationEmail($user, $this->token)
);
}
}
49 changes: 49 additions & 0 deletions app/Mail/Subscription/User/ValidationEmail.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace App\Mail\Subscription\User;

use App\Models\UserValidation;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class ValidationEmail extends Mailable
{
use Queueable;
use SerializesModels;

/**
* @var User
*/
public $user;
public $token;


public $date;

/**
* Create a new message instance.
*
* @return void
*/
public function __construct(User $user, string $token)
{
$this->user = $user;
$this->token = $token;
}

/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this
->from(['address' => config('app.email'), 'name' => 'Kanka Team'])
->subject(__('emails/subscriptions/validation.title'))
->view('emails.subscriptions.validation.user-html')
->tag('validation');
}
}
1 change: 1 addition & 0 deletions app/Models/UserFlag.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class UserFlag extends Model

public const FLAG_INACTIVE_1 = 'inactive_1';
public const FLAG_INACTIVE_2 = 'inactive_2';
public const FLAG_EMAIL = 'email';

public function user()
{
Expand Down
36 changes: 36 additions & 0 deletions app/Models/UserValidation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\Models;

use App\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;

/**
* @property int $user_id
* @property bool $is_valid
*/
class UserValidation extends Model
{
use Prunable;

/** @var string[] */
protected $fillable = [
'is_valid',
'token'
];

public function user()
{
return $this->belongsTo(User::class);
}

/**
* Automatically prune old elements from the db
* @return \Illuminate\Database\Eloquent\Builder
*/
public function prunable()
{
return static::where('is_valid', false)->where('created_at', '<=', now()->subDays(1));
}
}
38 changes: 38 additions & 0 deletions app/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
use App\Models\Scopes\UserScope;
use App\Models\UserLog;
use App\Models\UserSetting;
use App\Models\UserFlag;
use App\Models\UserValidation;
use App\Models\Relations\UserRelations;
use Carbon\Carbon;
use App\Jobs\Emails\Subscriptions\EmailValidationJob;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
Expand Down Expand Up @@ -433,6 +436,12 @@ public function isFrauding(): bool
if (!empty($this->provider)) {
return false;
}

$validation = UserValidation::where('user_id', $this->id)->where('is_valid', true)->first();
if ($validation) {
return false;
}

// If the account was created recently, add some small checks
/*if ($this->created_at->isAfter(Carbon::now()->subHour())) {
// User's name is directly in the campaign name
Expand All @@ -453,6 +462,35 @@ public function isFrauding(): bool
->count() >= 2;
}

public function requiresEmail(): self
{
$token = UserValidation::where('user_id', $this->id)->first();
if ($token && $token->is_valid) {
return $this;
}
//Check for existing token
$flag = UserFlag::where('user_id', $this->id)->where('flag', UserFlag::FLAG_EMAIL)->first();

if (!$flag) {
$flag = new UserFlag();
$flag->user_id = $this->id;
$flag->flag = UserFlag::FLAG_EMAIL;
$flag->save();
}

if (!$token) {
$token = new UserValidation();
$token->token = Str::uuid();
$token->user_id = $this->id;
$token->is_valid = false;
$token->save();
}

EmailValidationJob::dispatch($this, $token->token);

return $this;
}

/**
* List of campaigns the user is the only admin of. This is used for the automatic purge warning emails
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('user_validations', function (Blueprint $table) {
$table->id();
$table->uuid('token');
$table->unsignedInteger('user_id');
$table->boolean('is_valid');
$table->timestamps();

$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
$table->index(['is_valid']);


});
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('user_validations');
}
};
1 change: 1 addition & 0 deletions database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ public function run()
$this->call(PostLayoutTableSeeder::class);
$this->call(FeatureCategorySeeder::class);
$this->call(FeatureStatusSeeder::class);
$this->call(TierSeeder::class);
}
}
7 changes: 7 additions & 0 deletions lang/en/emails/validation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

return [
'modal' => 'Email validation required for subscription, please check your inbox to continue the validation process.',
'success' => 'Email succesfully validated.',
'error' => 'Validation failed, please try again.',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@extends('emails.base', [
'utmSource' => 'subscription',
'utmCampaign' => 'failed-charge'
])

@section('content')
<p>
<strong>Email Validation</strong>
</p>
<p>
{{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }},
</p>

<p>This is an automatic notification.</p>
<p>To validate the email for your Kanka account click <a href="{{ 'https://app.kanka.io/users/' . $user->id . '/validation?token=' . $token }}">here</a>. This link will expire in 24 hours.</p>
<p>If the above link doesnt work, open the following URL in your web browser {{ 'https://app.kanka.io/users/' . $user->id . '/validation?token=' . $token }}</p>
<p>
{{ __('emails/subscriptions/upcoming.closing') }}<br />
The Kanka Team
</p>

@endsection
9 changes: 6 additions & 3 deletions resources/views/settings/subscription/change.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
<article class="text-center max-w-xl container">

<x-grid type="1/1">
@if ($user->isFrauding())
@if (!$user->isFrauding())
@php
$user->requiresEmail();
@endphp
<x-alert type="warning">
{{ __('settings.subscription.errors.failed', ['email' => config('app.email')]) }}
{{ __('emails/validation.modal') }}
</x-alert></div><?php return; ?>
@endif

Expand Down Expand Up @@ -76,7 +79,7 @@
</li>
<li role="presentation">
<a href="#giropay" aria-controls="settings" role="tab" data-toggle="tab">
giropay
Giropay
</a>
</li>
@endif
Expand Down
2 changes: 2 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@
Route::get('roadmap/{feature}', [App\Http\Controllers\Roadmap\FeatureController::class, 'show'])->name('roadmap.feature.show');
Route::post('roadmap/{feature}/upvote', [App\Http\Controllers\Roadmap\FeatureController::class, 'upvote'])->name('roadmap.upvote');
Route::post('roadmap/submit', [App\Http\Controllers\Roadmap\FeatureController::class, 'store'])->name('roadmap.store');

Route::get('/users/{user}/validation', [App\Http\Controllers\User\EmailValidationController::class, 'validateEmail'])->name('validation.email');

0 comments on commit a468176

Please sign in to comment.