Init
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Extensions;
|
||||
|
||||
use Filament\Forms\Form;
|
||||
use Lunar\Admin\Support\Extending\ResourceExtension;
|
||||
|
||||
class StaffResourceExtension extends ResourceExtension
|
||||
{
|
||||
public function extendForm(Form $form): Form
|
||||
{
|
||||
$schema = collect($form->getComponents())
|
||||
->reject(fn ($component) => method_exists($component, 'getName') && $component->getName() == 'password')
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return $form->schema($schema);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Filament\Pages;
|
||||
|
||||
use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Models\Contracts\FilamentUser;
|
||||
use Filament\Pages\SimplePage;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Modules\Core\Auth\Services\OtpService;
|
||||
|
||||
class Login extends SimplePage
|
||||
{
|
||||
use WithRateLimiting;
|
||||
|
||||
protected static string $view = 'core::auth.filament.pages.login';
|
||||
|
||||
public ?string $email = '';
|
||||
public ?string $otp = '';
|
||||
public bool $otpSent = false;
|
||||
|
||||
private OtpService $otpService;
|
||||
|
||||
public function boot(OtpService $otpService): void
|
||||
{
|
||||
$this->otpService = $otpService;
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
if (Filament::auth()->check()) {
|
||||
redirect()->intended(Filament::getUrl());
|
||||
}
|
||||
}
|
||||
|
||||
public function requestOtp(): void
|
||||
{
|
||||
$this->validate(['email' => 'required|email']);
|
||||
|
||||
try {
|
||||
$this->rateLimit(5);
|
||||
} catch (TooManyRequestsException) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => 'Too many attempts. Please wait before trying again.',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->otpService->generateAndSend($this->email);
|
||||
|
||||
$this->otpSent = true;
|
||||
}
|
||||
|
||||
public function authenticate(): void
|
||||
{
|
||||
$this->validate(['otp' => 'required']);
|
||||
|
||||
try {
|
||||
$this->rateLimit(5);
|
||||
} catch (TooManyRequestsException) {
|
||||
throw ValidationException::withMessages([
|
||||
'otp' => 'Too many attempts. Please wait before trying again.',
|
||||
]);
|
||||
}
|
||||
|
||||
$staff = $this->otpService->validate($this->email, $this->otp);
|
||||
|
||||
if (!$staff) {
|
||||
throw ValidationException::withMessages([
|
||||
'otp' => 'Invalid or expired code.',
|
||||
]);
|
||||
}
|
||||
|
||||
if ($staff instanceof FilamentUser && !$staff->canAccessPanel(Filament::getCurrentPanel())) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => 'You do not have access to this panel.',
|
||||
]);
|
||||
}
|
||||
|
||||
Filament::auth()->login($staff);
|
||||
|
||||
session()->regenerate();
|
||||
|
||||
redirect()->intended(Filament::getUrl());
|
||||
}
|
||||
|
||||
public function getTitle(): string | Htmlable
|
||||
{
|
||||
return 'Sign in';
|
||||
}
|
||||
|
||||
public function getHeading(): string | Htmlable
|
||||
{
|
||||
return 'Sign in to your account';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Mail;
|
||||
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
|
||||
class CustomerOtpMail extends Mailable
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly string $code,
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(subject: 'Ο κωδικός σύνδεσης σου');
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(view: 'core::auth.mail.customer-otp');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Mail;
|
||||
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
|
||||
class InviteMail extends Mailable
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(subject: 'You have been invited');
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(view: 'core::auth.mail.invite');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Mail;
|
||||
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
|
||||
class OtpMail extends Mailable
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $name,
|
||||
public readonly string $code,
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(subject: 'Your login code');
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(view: 'core::auth.mail.otp');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Models;
|
||||
|
||||
use Lunar\Admin\Models\Staff as ModelsStaff;
|
||||
|
||||
class Staff extends ModelsStaff
|
||||
{
|
||||
protected $fillable = [
|
||||
'first_name',
|
||||
'last_name',
|
||||
'admin',
|
||||
'email',
|
||||
'otp_code',
|
||||
'otp_expires_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'admin' => 'bool',
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'otp_expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Lunar\Models\Contracts\Customer;
|
||||
use Lunar\Facades\ModelManifest;
|
||||
use Modules\Core\Auth\Mail\CustomerOtpMail;
|
||||
|
||||
class CustomerOtpService
|
||||
{
|
||||
private const EXPIRY_MINUTES = 10;
|
||||
private const CODE_LENGTH = 6;
|
||||
|
||||
public function generateAndSend(string $email): bool
|
||||
{
|
||||
$customer = $this->findByEmail($email);
|
||||
|
||||
if (! $customer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = str_pad((string) random_int(0, 999999), self::CODE_LENGTH, '0', STR_PAD_LEFT);
|
||||
|
||||
$customer->otp_code = $code;
|
||||
$customer->otp_expires_at = now()->addMinutes(self::EXPIRY_MINUTES);
|
||||
$customer->save();
|
||||
|
||||
Mail::to($email)->send(new CustomerOtpMail($customer->full_name, $code));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validate(string $email, string $code): ?Customer
|
||||
{
|
||||
$customer = $this->findByEmail($email);
|
||||
|
||||
if (! $customer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $customer->otp_expires_at || $customer->otp_code != $code || now()->isAfter($customer->otp_expires_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$customer->otp_code = null;
|
||||
$customer->otp_expires_at = null;
|
||||
$customer->save();
|
||||
|
||||
return $customer;
|
||||
}
|
||||
|
||||
private function findByEmail(string $email): ?Customer
|
||||
{
|
||||
$model = ModelManifest::get(Customer::class);
|
||||
|
||||
return $model::query()
|
||||
->whereHas('emails', fn ($query) => $query->where('email', $email))
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\Core\Auth\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Modules\Core\Auth\Mail\OtpMail;
|
||||
use Modules\Core\Auth\Models\Staff;
|
||||
|
||||
class OtpService
|
||||
{
|
||||
private const EXPIRY_MINUTES = 10;
|
||||
private const CODE_LENGTH = 6;
|
||||
|
||||
public function generateAndSend(string $email): bool
|
||||
{
|
||||
$staff = Staff::where('email', $email)->first();
|
||||
|
||||
if (! $staff) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$code = str_pad((string) random_int(0, 999999), self::CODE_LENGTH, '0', STR_PAD_LEFT);
|
||||
|
||||
$staff->otp_code = $code;
|
||||
$staff->otp_expires_at = now()->addMinutes(self::EXPIRY_MINUTES);
|
||||
$staff->save();
|
||||
|
||||
Mail::to($staff->email)->send(new OtpMail($staff->first_name, $code));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validate(string $email, string $code): ?Staff
|
||||
{
|
||||
$staff = Staff::where('email', $email)->first();
|
||||
|
||||
if (! $staff) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $staff->otp_expires_at || $staff->otp_code != $code || now()->isAfter($staff->otp_expires_at)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$staff->otp_code = null;
|
||||
$staff->otp_expires_at = null;
|
||||
$staff->save();
|
||||
|
||||
return $staff;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user