mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 20:22:42 +01:00
Add Billing Portal Login functionality
This commit is contained in:
parent
70fdc73b2d
commit
cfd33bd4fc
162
app/Livewire/BillingPortal/Authentication/Login.php
Normal file
162
app/Livewire/BillingPortal/Authentication/Login.php
Normal file
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Livewire\BillingPortal\Authentication;
|
||||
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Mail\Subscription\OtpCode;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Subscription;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
|
||||
class Login extends Component
|
||||
{
|
||||
public Subscription $subscription;
|
||||
|
||||
public array $context;
|
||||
|
||||
public ?string $email;
|
||||
|
||||
public ?string $password;
|
||||
|
||||
public ?int $otp;
|
||||
|
||||
public array $state = [
|
||||
'otp' => false, // Use as preference. E-mail/password or OTP.
|
||||
'login_form' => false,
|
||||
'otp_form' => false,
|
||||
'initial_completed' => false,
|
||||
];
|
||||
|
||||
public function initial()
|
||||
{
|
||||
$this->validateOnly('email', ['email' => 'required|bail|email:rfc|exists:client_contacts,email']);
|
||||
|
||||
$this->state['initial_completed'] = true;
|
||||
|
||||
if ($this->state['otp']) {
|
||||
return $this->withOtp();
|
||||
}
|
||||
|
||||
return $this->withPassword();
|
||||
}
|
||||
|
||||
public function withPassword()
|
||||
{
|
||||
$contact = ClientContact::where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
if ($contact) {
|
||||
return $this->state['login_form'] = true;
|
||||
}
|
||||
|
||||
$this->state['login_form'] = false;
|
||||
|
||||
$contact = $this->createClientContact();
|
||||
|
||||
auth()->guard('contact')->loginUsingId($contact->id, true);
|
||||
|
||||
$this->dispatch('purchase.context', property: 'contact', value: $contact);
|
||||
$this->dispatch('purchase.next');
|
||||
}
|
||||
|
||||
public function withOtp()
|
||||
{
|
||||
$code = rand(100000, 999999);
|
||||
$email_hash = "subscriptions:otp:{$this->email}";
|
||||
|
||||
Cache::put($email_hash, $code, 600);
|
||||
|
||||
$cc = new ClientContact();
|
||||
$cc->email = $this->email;
|
||||
|
||||
$nmo = new NinjaMailerObject();
|
||||
$nmo->mailable = new OtpCode($this->subscription->company, $this->context['contact'] ?? null, $code);
|
||||
$nmo->company = $this->subscription->company;
|
||||
$nmo->settings = $this->subscription->company->settings;
|
||||
$nmo->to_user = $cc;
|
||||
|
||||
NinjaMailerJob::dispatch($nmo);
|
||||
|
||||
if (app()->environment('local')) {
|
||||
session()->flash('message', "[dev]: Your OTP is: {$code}");
|
||||
}
|
||||
|
||||
$this->state['otp_form'] = true;
|
||||
}
|
||||
|
||||
public function handleOtp()
|
||||
{
|
||||
$this->validate([
|
||||
'otp' => 'required|numeric|digits:6',
|
||||
'email' => 'required|bail|email:rfc|exists:client_contacts,email',
|
||||
]);
|
||||
|
||||
$code = Cache::get("subscriptions:otp:{$this->email}");
|
||||
|
||||
if ($this->otp != $code) { //loose comparison prevents edge cases
|
||||
$errors = $this->getErrorBag();
|
||||
$errors->add('otp', ctrans('texts.invalid_code'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$contact = ClientContact::where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
if ($contact) {
|
||||
auth()->guard('contact')->loginUsingId($contact->id, true);
|
||||
|
||||
$this->dispatch('purchase.context', property: 'contact', value: $contact);
|
||||
$this->dispatch('purchase.next');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public function handlePassword()
|
||||
{
|
||||
$this->validate([
|
||||
'email' => 'required|bail|email:rfc',
|
||||
'password' => 'required',
|
||||
]);
|
||||
|
||||
$attempt = auth()->guard('contact')->attempt([
|
||||
'email' => $this->email,
|
||||
'password' => $this->password,
|
||||
'company_id' => $this->subscription->company_id,
|
||||
]);
|
||||
|
||||
if ($attempt) {
|
||||
$this->dispatch('purchase.next');
|
||||
}
|
||||
|
||||
session()->flash('message', 'These credentials do not match our records.');
|
||||
}
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (auth()->guard('contact')->check()) {
|
||||
$this->dispatch('purchase.context', property: 'contact', value: auth()->guard('contact')->user());
|
||||
$this->dispatch('purchase.next');
|
||||
}
|
||||
}
|
||||
|
||||
public function render(): \Illuminate\View\View
|
||||
{
|
||||
return view('billing-portal.v3.authentication.login');
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
<div>
|
||||
@if (session()->has('message'))
|
||||
@component('portal.ninja2020.components.message')
|
||||
{{ session('message') }}
|
||||
@endcomponent
|
||||
@endif
|
||||
|
||||
<div class="my-4">
|
||||
<h1 class="text-3xl font-medium">{{ ctrans('texts.contact') }}</h1>
|
||||
</div>
|
||||
|
||||
@if($state['initial_completed'] === false)
|
||||
<form wire:submit="initial">
|
||||
@csrf
|
||||
|
||||
<label for="email_address">
|
||||
<span class="input-label">{{ ctrans('texts.email_address') }}</span>
|
||||
<input wire:model="email" type="email" class="input w-full" />
|
||||
|
||||
@error('email')
|
||||
<p class="validation validation-fail block w-full" role="alert">
|
||||
{{ $message }}
|
||||
</p>
|
||||
@enderror
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="button button-block bg-primary text-white mt-4">
|
||||
{{ ctrans('texts.next') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($state['login_form'])
|
||||
<form wire:submit="handlePassword" class="space-y-3">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<span class="input-label">{{ ctrans('texts.email_address') }}</span>
|
||||
<input wire:model="email" type="email" class="input w-full" />
|
||||
|
||||
@error('email')
|
||||
<p class="validation validation-fail block w-full" role="alert">
|
||||
{{ $message }}
|
||||
</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="input-label">{{ ctrans('texts.password') }}</span>
|
||||
<input wire:model="password" type="password" class="input w-full" />
|
||||
|
||||
@error('password')
|
||||
<p class="validation validation-fail block w-full" role="alert">
|
||||
{{ $message }}
|
||||
</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="button button-block bg-primary text-white mt-4">
|
||||
{{ ctrans('texts.next') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if($state['otp_form'])
|
||||
<form wire:submit="handleOtp" class="space-y-3">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<span class="input-label">{{ ctrans('texts.code') }}</span>
|
||||
<input wire:model="otp" type="text" class="input w-full" />
|
||||
|
||||
@error('otp')
|
||||
<p class="validation validation-fail block w-full" role="alert">
|
||||
{{ $message }}
|
||||
</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="button button-block bg-primary text-white mt-4">
|
||||
{{ ctrans('texts.next') }}
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user