Merge pull request #9069 from turbo124/v5-develop

Improvements
This commit is contained in:
David Bomba 2023-12-26 09:56:50 +11:00 committed by GitHub
commit 490639bd1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 135 additions and 17 deletions

View File

@ -0,0 +1,41 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com).
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2023. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/
namespace App\Helpers\Encrypt;
class Secure
{
public static function encrypt(string $hash): ?string
{
$data = null;
$public_key = openssl_pkey_get_public(config('ninja.encryption.public_key'));
if (openssl_public_encrypt($hash, $encrypted, $public_key)) {
$data = base64_encode($encrypted);
}
return $data;
}
public static function decrypt(string $hash): ?string
{
$data = null;
$private_key = openssl_pkey_get_private(config('ninja.encryption.private_key'));
if (openssl_private_decrypt(base64_decode($hash), $decrypted, $private_key)) {
$data = $decrypted;
}
return $data;
}
}

View File

@ -11,17 +11,18 @@
namespace App\Http\Controllers;
use App\Http\Requests\Account\CreateAccountRequest;
use App\Http\Requests\Account\UpdateAccountRequest;
use App\Jobs\Account\CreateAccount;
use App\Libraries\MultiDB;
use App\Models\Account;
use App\Libraries\MultiDB;
use App\Utils\TruthSource;
use App\Models\CompanyUser;
use Illuminate\Http\Response;
use App\Helpers\Encrypt\Secure;
use App\Jobs\Account\CreateAccount;
use App\Transformers\AccountTransformer;
use App\Transformers\CompanyUserTransformer;
use App\Utils\TruthSource;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Http\Response;
use App\Http\Requests\Account\CreateAccountRequest;
use App\Http\Requests\Account\UpdateAccountRequest;
class AccountController extends BaseController
{
@ -65,6 +66,33 @@ class AccountController extends BaseController
*/
public function store(CreateAccountRequest $request)
{
if($request->has('cf-turnstile-response') && config('ninja.cloudflare.turnstile.secret')) {
$r = \Illuminate\Support\Facades\Http::post('https://challenges.cloudflare.com/turnstile/v0/siteverify', [
'secret' => config('ninja.cloudflare.turnstile.secret'),
'response' => $request->input('cf-turnstile-response'),
'remoteip' => $request->getClientIp(),
]);
if($r->successful()){
if($r->json()['success'] === true) {
// Captcha passed
} else {
return response()->json(['message' => 'Captcha Failed'], 400);
}
}
}
if($request->has('hash') && config('ninja.cloudflare.turnstile.secret')) { //@todo once all platforms are implemented, we disable access to the rest of this route without a success response.
if(Secure::decrypt($request->input('hash')) !== $request->input('email')) {
return response()->json(['message' => 'Invalid Signup Payload'], 400);
}
}
$account = (new CreateAccount($request->all(), $request->getClientIp()))->handle();
if (! ($account instanceof Account)) {
return $account;

View File

@ -42,6 +42,10 @@ class TwilioController extends BaseController
/** @var \App\Models\User $user */
$user = auth()->user();
if(!$user->email_verified_at) {
return response()->json(['message' => 'Please verify your email address before verifying your phone number'], 400);
}
$account = $user->company()->account;
if(!$this->checkPhoneValidity($request->phone)) {
@ -140,12 +144,24 @@ class TwilioController extends BaseController
*/
public function generate2faResetCode(Generate2faRequest $request)
{
nlog($request->all());
nlog($request->headers());
$user = User::where('email', $request->email)->first();
if (!$user) {
return response()->json(['message' => 'Unable to retrieve user.'], 400);
}
if(!$user->email_verified_at) {
return response()->json(['message' => 'Please verify your email address before verifying your phone number'], 400);
}
if(!$user->first_name || !$user->last_name) {
return response()->json(['message' => 'Please update your first and/or last name in the User Details before verifying your number.'], 400);
}
if (!$user->phone || $user->phone == '') {
return response()->json(['message' => 'User found, but no valid phone number on file, please contact support.'], 400);
}

View File

@ -203,20 +203,20 @@ class PdfSlot extends Component
if($this->entity_type == 'invoice' || $this->entity_type == 'recurring_invoice') {
foreach($this->settings->pdf_variables->invoice_details as $variable) {
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
}
} elseif($this->entity_type == 'quote') {
foreach($this->settings->pdf_variables->quote_details ?? [] as $variable) {
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
}
} elseif($this->entity_type == 'credit') {
foreach($this->settings->pdf_variables->credit_details ?? [] as $variable) {
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
}
} elseif($this->entity_type == 'purchase_order') {
foreach($this->settings->pdf_variables->purchase_order_details ?? [] as $variable) {
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='pl-5 w-36 block entity-field'>{$variable}</p></div>";
$entity_details .= "<div class='flex px-5 block'><p class= w-36 block'>{$variable}_label</p><p class='ml-5 w-36 block entity-field'>{$variable}</p></div>";
}
}
@ -310,6 +310,7 @@ class PdfSlot extends Component
return 'purchase_order';
}
return '';
}
}

View File

@ -73,6 +73,18 @@ class UpdatePaymentMethods
$this->addOrUpdateCard($method, $customer->id, $client, GatewayType::SOFORT);
}
$sepa_methods = PaymentMethod::all(
[
'customer' => $customer->id,
'type' => 'sepa_debit',
],
$this->stripe->stripe_connect_auth
);
foreach ($sepa_methods as $method) {
$this->addOrUpdateCard($method, $customer->id, $client, GatewayType::SEPA);
}
$this->importBankAccounts($customer, $client);
$this->importPMBankAccounts($customer, $client);
@ -189,7 +201,7 @@ class UpdatePaymentMethods
}
/* Ignore Expired cards */
if ($method->card->exp_year <= date('Y') && $method->card->exp_month < date('m')) {
if ($method->card && $method->card->exp_year <= date('Y') && $method->card->exp_month < date('m')) {
return;
}
@ -231,6 +243,15 @@ class UpdatePaymentMethods
return new \stdClass;
case GatewayType::SEPA:
$payment_meta = new \stdClass;
$payment_meta->brand = (string) \sprintf('%s (%s)', $method->sepa_debit->bank_code, ctrans('texts.sepa'));
$payment_meta->last4 = (string) $method->sepa_debit->last4;
$payment_meta->state = 'authorized';
$payment_meta->type = GatewayType::SEPA;
return $payment_meta;
default:
break;

View File

@ -233,5 +233,14 @@ return [
'secret' => env('PAYPAL_SECRET', null),
'client_id' => env('PAYPAL_CLIENT_ID', null),
'webhook_id' => env('PAYPAL_WEBHOOK_ID', null),
]
],
'cloudflare' => [
'turnstile' => [
'secret' => env('CLOUDFLARE_SECRET', null),
]
],
'encryption' => [
'public_key' => env('NINJA_PUBLIC_KEY', false),
'private_key' => env('NINJA_PRIVATE_KEY', false),
],
];

View File

@ -77,6 +77,8 @@ span {
<td>
<div class="product-information">
<div class="item-details">
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{!! $product['notes'] !!}</p>
<p class="mt-2">
@if($show_quantity)
{{ $product['quantity'] }} x
@ -85,8 +87,8 @@ span {
@if($show_cost)
{{ $product['cost'] }}
@endif
</p>
<p class="overflow-ellipsis overflow-hidden px-1 mb-2">{!! $product['notes'] !!}</p>
</p>
</div>
</div>
</td>

View File

@ -361,7 +361,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('settings/enable_two_factor', [TwoFactorController::class, 'enableTwoFactor']);
Route::post('settings/disable_two_factor', [TwoFactorController::class, 'disableTwoFactor']);
Route::post('verify', [TwilioController::class, 'generate'])->name('verify.generate')->middleware('throttle:100,1');
Route::post('verify', [TwilioController::class, 'generate'])->name('verify.generate')->middleware('throttle:3,1');
Route::post('verify/confirm', [TwilioController::class, 'confirm'])->name('verify.confirm');
Route::resource('vendors', VendorController::class); // name = (vendors. index / create / show / update / destroy / edit
@ -406,8 +406,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::get('nordigen/institutions', [NordigenController::class, 'institutions'])->name('nordigen.institutions');
});
Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:10,1');
Route::post('api/v1/sms_reset/confirm', [TwilioController::class, 'confirm2faResetCode'])->name('sms_reset.confirm')->middleware('throttle:20,1');
Route::post('api/v1/sms_reset', [TwilioController::class, 'generate2faResetCode'])->name('sms_reset.generate')->middleware('throttle:3,1');
Route::post('api/v1/sms_reset/confirm', [TwilioController::class, 'confirm2faResetCode'])->name('sms_reset.confirm')->middleware('throttle:3,1');
Route::match(['get', 'post'], 'payment_webhook/{company_key}/{company_gateway_id}', PaymentWebhookController::class)
->middleware('throttle:1000,1')