feat: add email verification for cloud
This commit is contained in:
parent
f14995200b
commit
165f0a3d4a
@ -58,6 +58,11 @@ public function create(array $input): User
|
||||
'password' => Hash::make($input['password']),
|
||||
]);
|
||||
$team = $user->teams()->first();
|
||||
if (isCloud()) {
|
||||
$user->sendVerificationEmail();
|
||||
} else {
|
||||
$user->markEmailAsVerified();
|
||||
}
|
||||
}
|
||||
// Set session variable
|
||||
session(['currentTeam' => $user->currentTeam = $team]);
|
||||
|
@ -38,8 +38,7 @@ class Kernel extends HttpKernel
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\CheckForcePasswordReset::class,
|
||||
\App\Http\Middleware\IsSubscriptionValid::class,
|
||||
\App\Http\Middleware\IsBoardingFlow::class,
|
||||
\App\Http\Middleware\DecideWhatToDoWithUser::class,
|
||||
|
||||
],
|
||||
|
||||
|
@ -5,10 +5,11 @@
|
||||
use App\Actions\Server\UpdateCoolify;
|
||||
use App\Models\InstanceSettings;
|
||||
use Livewire\Component;
|
||||
use Masmerise\Toaster\Toaster;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
|
||||
class Upgrade extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public bool $showProgress = false;
|
||||
public bool $isUpgradeAvailable = false;
|
||||
public string $latestVersion = '';
|
||||
@ -31,6 +32,7 @@ public function checkUpdate()
|
||||
public function upgrade()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(1, 30);
|
||||
if ($this->showProgress) {
|
||||
return;
|
||||
}
|
||||
|
26
app/Http/Livewire/VerifyEmail.php
Normal file
26
app/Http/Livewire/VerifyEmail.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use Livewire\Component;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
|
||||
class VerifyEmail extends Component
|
||||
{
|
||||
use WithRateLimiting;
|
||||
public function again() {
|
||||
try {
|
||||
$this->rateLimit(1, 300);
|
||||
auth()->user()->sendVerificationEmail();
|
||||
$this->emit('success', 'Email verification link sent!');
|
||||
|
||||
} catch(\Exception $e) {
|
||||
ray($e);
|
||||
return handleError($e,$this);
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.verify-email');
|
||||
}
|
||||
}
|
45
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
45
app/Http/Middleware/DecideWhatToDoWithUser.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class DecideWhatToDoWithUser
|
||||
{
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
if (!auth()->user() || !isCloud()) {
|
||||
return $next($request);
|
||||
}
|
||||
if (!auth()->user()->hasVerifiedEmail()) {
|
||||
if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('/verify');
|
||||
}
|
||||
if (!isSubscriptionActive() && !isSubscriptionOnGracePeriod()) {
|
||||
if (!in_array($request->path(), allowedPathsForUnsubscribedAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('subscription');
|
||||
}
|
||||
}
|
||||
if (showBoarding() && !in_array($request->path(), allowedPathsForBoardingAccounts())) {
|
||||
if (Str::startsWith($request->path(), 'invitations')) {
|
||||
return $next($request);
|
||||
}
|
||||
return redirect('boarding');
|
||||
}
|
||||
if (auth()->user()->hasVerifiedEmail() && $request->path() === 'verify') {
|
||||
return redirect('/');
|
||||
}
|
||||
if (isSubscriptionActive() && $request->path() === 'subscription') {
|
||||
return redirect('/');
|
||||
}
|
||||
return $next($request);
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ public function type()
|
||||
}
|
||||
if (isStripe()) {
|
||||
if (!$this->stripe_plan_id) {
|
||||
return 'zero';
|
||||
return 'zero';
|
||||
}
|
||||
$subscription = Subscription::where('id', $this->id)->first();
|
||||
if (!$subscription) {
|
||||
|
@ -6,8 +6,12 @@
|
||||
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
@ -54,6 +58,23 @@ public function getRecepients($notification)
|
||||
return $this->email;
|
||||
}
|
||||
|
||||
public function sendVerificationEmail()
|
||||
{
|
||||
$mail = new MailMessage();
|
||||
$url = Url::temporarySignedRoute(
|
||||
'verify.verify',
|
||||
Carbon::now()->addMinutes(Config::get('auth.verification.expire', 60)),
|
||||
[
|
||||
'id' => $this->getKey(),
|
||||
'hash' => sha1($this->getEmailForVerification()),
|
||||
]
|
||||
);
|
||||
$mail->view('emails.email-verification', [
|
||||
'url' => $url,
|
||||
]);
|
||||
$mail->subject('Coolify Cloud: Verify your email.');
|
||||
send_user_an_email($mail, $this->email);
|
||||
}
|
||||
public function sendPasswordResetNotification($token): void
|
||||
{
|
||||
$this->notify(new TransactionalEmailsResetPassword($token));
|
||||
@ -61,7 +82,7 @@ public function sendPasswordResetNotification($token): void
|
||||
|
||||
public function isAdmin()
|
||||
{
|
||||
return data_get($this->pivot,'role') === 'admin' || data_get($this->pivot,'role') === 'owner';
|
||||
return data_get($this->pivot, 'role') === 'admin' || data_get($this->pivot, 'role') === 'owner';
|
||||
}
|
||||
|
||||
public function isAdminFromSession()
|
||||
@ -79,7 +100,7 @@ public function isAdminFromSession()
|
||||
return true;
|
||||
}
|
||||
$team = $teams->where('id', session('currentTeam')->id)->first();
|
||||
$role = data_get($team,'pivot.role');
|
||||
$role = data_get($team, 'pivot.role');
|
||||
return $role === 'admin' || $role === 'owner';
|
||||
}
|
||||
|
||||
@ -96,7 +117,7 @@ public function isInstanceAdmin()
|
||||
|
||||
public function currentTeam()
|
||||
{
|
||||
return Cache::remember('team:' . auth()->user()->id, 3600, function() {
|
||||
return Cache::remember('team:' . auth()->user()->id, 3600, function () {
|
||||
return Team::find(session('currentTeam')->id);
|
||||
});
|
||||
}
|
||||
|
@ -122,14 +122,13 @@ function allowedPathsForUnsubscribedAccounts()
|
||||
return [
|
||||
'subscription',
|
||||
'login',
|
||||
'register',
|
||||
'logout',
|
||||
'waitlist',
|
||||
'force-password-reset',
|
||||
'logout',
|
||||
'livewire/message/force-password-reset',
|
||||
'livewire/message/check-license',
|
||||
'livewire/message/switch-team',
|
||||
'livewire/message/subscription.pricing-plans'
|
||||
'livewire/message/subscription.pricing-plans',
|
||||
];
|
||||
}
|
||||
function allowedPathsForBoardingAccounts()
|
||||
@ -141,3 +140,10 @@ function allowedPathsForBoardingAccounts()
|
||||
'livewire/message/activity-monitor'
|
||||
];
|
||||
}
|
||||
function allowedPathsForInvalidAccounts() {
|
||||
return [
|
||||
'logout',
|
||||
'verify',
|
||||
'livewire/message/verify-email',
|
||||
];
|
||||
}
|
||||
|
@ -9,12 +9,12 @@
|
||||
@if ($is_registration_enabled)
|
||||
@if (config('coolify.waitlist'))
|
||||
<a href="/waitlist"
|
||||
class="text-xs normal-case hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
Join the waitlist
|
||||
</a>
|
||||
@else
|
||||
<a href="/register"
|
||||
class="text-xs normal-case hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
{{ __('auth.register_now') }}
|
||||
</a>
|
||||
@endif
|
||||
|
12
resources/views/auth/verify-email.blade.php
Normal file
12
resources/views/auth/verify-email.blade.php
Normal file
@ -0,0 +1,12 @@
|
||||
<x-layout-subscription>
|
||||
<div class="min-h-screen hero">
|
||||
<div class="min-w-fit">
|
||||
<h1> Verification Email Sent </h1>
|
||||
<div class="flex justify-center gap-2 text-center">
|
||||
<br>To activate your account, please open the email and follow the
|
||||
instructions.
|
||||
</div>
|
||||
<livewire:verify-email />
|
||||
</div>
|
||||
</div>
|
||||
</x-layout-subscription>
|
3
resources/views/emails/email-verification.blade.php
Normal file
3
resources/views/emails/email-verification.blade.php
Normal file
@ -0,0 +1,3 @@
|
||||
<x-emails.layout>
|
||||
Verify your email [here]({{ $url }}).
|
||||
</x-emails.layout>
|
3
resources/views/livewire/verify-email.blade.php
Normal file
3
resources/views/livewire/verify-email.blade.php
Normal file
@ -0,0 +1,3 @@
|
||||
<div class="pt-4">
|
||||
<x-forms.button wire:click="again">Send Verification Email Again</x-forms.button>
|
||||
</div>
|
@ -26,6 +26,7 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Foundation\Auth\EmailVerificationRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -61,7 +62,19 @@
|
||||
}
|
||||
return response()->json(['message' => 'Transactional emails are not active'], 400);
|
||||
})->name('password.forgot');
|
||||
|
||||
|
||||
Route::get('/waitlist', WaitlistIndex::class)->name('waitlist.index');
|
||||
|
||||
Route::get('/verify', function () {
|
||||
return view('auth.verify-email');
|
||||
})->middleware('auth')->name('verify.email');
|
||||
|
||||
Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
|
||||
$request->fulfill();
|
||||
return redirect('/');
|
||||
})->middleware(['auth'])->name('verify.verify');
|
||||
|
||||
Route::middleware(['throttle:login'])->group(function () {
|
||||
Route::get('/auth/link', [Controller::class, 'link'])->name('auth.link');
|
||||
});
|
||||
@ -74,7 +87,7 @@
|
||||
Route::get('/environment/new', [MagicController::class, 'newEnvironment']);
|
||||
});
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('/projects', [ProjectController::class, 'all'])->name('projects');
|
||||
Route::get('/project/{project_uuid}/edit', [ProjectController::class, 'edit'])->name('project.edit');
|
||||
Route::get('/project/{project_uuid}', [ProjectController::class, 'show'])->name('project.show');
|
||||
@ -114,7 +127,7 @@
|
||||
});
|
||||
|
||||
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('/', Dashboard::class)->name('dashboard');
|
||||
Route::get('/boarding', BoardingIndex::class)->name('boarding');
|
||||
Route::middleware(['throttle:force-password-reset'])->group(function () {
|
||||
|
Loading…
Reference in New Issue
Block a user