feat: force password reset + waitlist

This commit is contained in:
Andras Bacsai
2023-08-15 14:11:38 +02:00
parent 952d335789
commit 88b3005589
35 changed files with 482 additions and 44 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Console;
use App\Jobs\CheckResaleLicenseJob;
use App\Jobs\CheckResaleLicenseKeys;
use App\Jobs\CleanupInstanceStuffsJob;
use App\Jobs\DatabaseBackupJob;
use App\Jobs\DockerCleanupJob;
use App\Jobs\InstanceApplicationsStatusJob;
@@ -22,12 +23,14 @@ class Kernel extends ConsoleKernel
$schedule->command('horizon:snapshot')->everyMinute();
$schedule->job(new InstanceApplicationsStatusJob)->everyMinute();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
// $schedule->job(new CheckResaleLicenseJob)->hourly();
// $schedule->job(new DockerCleanupJob)->everyOddHour();
// $schedule->job(new InstanceAutoUpdateJob(true))->everyMinute();
} else {
$schedule->command('horizon:snapshot')->everyFiveMinutes();
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute();
$schedule->job(new InstanceApplicationsStatusJob)->everyMinute();
$schedule->job(new CheckResaleLicenseJob)->hourly();
$schedule->job(new ProxyCheckJob)->everyFiveMinutes();

View File

@@ -9,6 +9,7 @@ use App\Models\Server;
use App\Models\StandalonePostgresql;
use App\Models\TeamInvitation;
use App\Models\User;
use App\Models\Waitlist;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
@@ -18,6 +19,12 @@ class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
public function waitlist() {
$waiting_in_line = Waitlist::whereVerified(true)->count();
return view('auth.waitlist', [
'waiting_in_line' => $waiting_in_line,
]);
}
public function subscription()
{
if (!is_cloud()) {
@@ -38,6 +45,9 @@ class Controller extends BaseController
]);
}
public function force_passoword_reset() {
return view('auth.force-password-reset');
}
public function dashboard()
{
$projects = Project::ownedByCurrentTeam()->get();

View File

@@ -37,6 +37,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\CheckForcePasswordReset::class,
\App\Http\Middleware\SubscriptionValid::class,
],

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Livewire;
use Illuminate\Support\Facades\Hash;
use Livewire\Component;
class ForcePasswordReset extends Component
{
public string $email;
public string $password;
public string $password_confirmation;
protected $rules = [
'email' => 'required|email',
'password' => 'required|min:8',
'password_confirmation' => 'required|same:password',
];
public function mount() {
$this->email = auth()->user()->email;
}
public function submit() {
try {
$this->validate();
auth()->user()->forceFill([
'password' => Hash::make($this->password),
'force_password_reset' => false,
])->save();
auth()->logout();
return redirect()->route('login')->with('status', 'Your initial password has been set.');
} catch(\Exception $e) {
return general_error_handler(err:$e, that:$this);
}
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace App\Http\Livewire;
use App\Jobs\SendConfirmationForWaitlistJob;
use App\Models\Waitlist as ModelsWaitlist;
use Livewire\Component;
class Waitlist extends Component
{
public string $email;
public int $waiting_in_line = 0;
protected $rules = [
'email' => 'required|email',
];
public function mount()
{
if (is_dev()) {
$this->email = 'test@example.com';
}
}
public function submit()
{
$this->validate();
try {
$found = ModelsWaitlist::where('email', $this->email)->first();
ray($found);
if ($found) {
if (!$found->verified) {
$this->emit('error', 'You are already on the waitlist. <br>Please check your email to verify your email address.');
return;
}
$this->emit('error', 'You are already on the waitlist.');
return;
}
$waitlist = ModelsWaitlist::create([
'email' => $this->email,
'type' => 'registration',
]);
$this->emit('success', 'You have been added to the waitlist.');
dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid));
} catch (\Exception $e) {
return general_error_handler(err: $e, that: $this);
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckForcePasswordReset
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->user()) {
$force_password_reset = auth()->user()->force_password_reset;
if ($force_password_reset) {
if ($request->routeIs('auth.force-password-reset') || $request->path() === 'livewire/message/force-password-reset') {
return $next($request);
}
return redirect()->route('auth.force-password-reset');
}
}
return $next($request);
}
}

View File

@@ -24,7 +24,6 @@ class RedirectIfAuthenticated
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}

View File

@@ -10,7 +10,6 @@ class SubscriptionValid
{
public function handle(Request $request, Closure $next): Response
{
if (!auth()->user() || !is_cloud()) {
if ($request->path() === 'subscription') {
return redirect('/');
@@ -36,7 +35,10 @@ class SubscriptionValid
'subscription',
'login',
'register',
'waitlist',
'force-password-reset',
'logout',
'livewire/message/force-password-reset',
'livewire/message/check-license',
'livewire/message/switch-team',
];

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Jobs;
use App\Models\Waitlist;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class CleanupInstanceStuffsJob implements ShouldQueue, ShouldBeUnique
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct()
{
}
// public function uniqueId(): string
// {
// return $this->container_name;
// }
public function handle(): void
{
try {
$this->cleanup_waitlist();
} catch (\Exception $e) {
ray($e->getMessage());
}
}
private function cleanup_waitlist()
{
$waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.confirmation_valid_for_minutes')))->get();
foreach ($waitlist as $item) {
$item->delete();
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace App\Jobs;
use App\Models\InstanceSettings;
use App\Models\Waitlist;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Mail;
class SendConfirmationForWaitlistJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public string $email, public string $uuid)
{
}
public function handle()
{
try {
$settings = InstanceSettings::get();
set_transanctional_email_settings($settings);
$mail = new MailMessage();
$confirmation_url = base_url() . '/webhooks/waitlist/confirm?email=' . $this->email . '&confirmation_code=' . $this->uuid;
$cancel_url = base_url() . '/webhooks/waitlist/cancel?email=' . $this->email . '&confirmation_code=' . $this->uuid;
$mail->view('emails.waitlist-confirmation',
[
'confirmation_url' => $confirmation_url,
'cancel_url' => $cancel_url,
]);
$mail->subject('You are on the waitlist!');
Mail::send(
[],
[],
fn(Message $message) => $message
->from(
data_get($settings, 'smtp_from_address'),
data_get($settings, 'smtp_from_name')
)
->to($this->email)
->subject($mail->subject)
->html((string) $mail->render())
);
} catch (\Throwable $th) {
ray($th->getMessage());
throw $th;
}
}
}

View File

@@ -3,6 +3,7 @@
namespace App\Models;
use App\Notifications\Channels\SendsEmail;
use App\Notifications\TransactionalEmails\ResetPassword as TransactionalEmailsResetPassword;
use App\Notifications\TrnsactionalEmails\ResetPassword;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -14,18 +15,14 @@ class User extends Authenticatable implements SendsEmail
{
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable;
protected $fillable = [
'id',
'name',
'email',
'password',
];
protected $guarded = [];
protected $hidden = [
'password',
'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
'force_password_reset' => 'boolean',
];
protected static function boot()
@@ -57,7 +54,7 @@ class User extends Authenticatable implements SendsEmail
public function sendPasswordResetNotification($token): void
{
$this->notify(new ResetPassword($token));
$this->notify(new TransactionalEmailsResetPassword($token));
}
public function isAdmin()

11
app/Models/Waitlist.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class Waitlist extends BaseModel
{
use HasFactory;
protected $guarded = [];
}

View File

@@ -14,6 +14,7 @@ use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\ServiceProvider;
use Laravel\Fortify\Contracts\RegisterResponse;
use Laravel\Fortify\Features;
use Laravel\Fortify\Fortify;
class FortifyServiceProvider extends ServiceProvider
@@ -41,13 +42,19 @@ class FortifyServiceProvider extends ServiceProvider
*/
public function boot(): void
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::registerView(function () {
ray('asd');
$settings = InstanceSettings::get();
if (!$settings->is_registration_enabled) {
return redirect()->route('login');
}
return view('auth.register');
if (config('coolify.waitlist')) {
return view('auth.waitlist');
} else {
return view('auth.register');
}
});
Fortify::loginView(function () {