Merge branch 'develop' into ts-1556
This commit is contained in:
commit
416f778de7
14 changed files with 297 additions and 67 deletions
|
|
@ -36,6 +36,11 @@ MAIL_LIST_CONTACTFORM=
|
|||
|
||||
ANALYTICS_GOOGLE_ID=
|
||||
|
||||
# Google Recaptcha
|
||||
GOOGLE_RECAPTCHA_PUBLIC=
|
||||
GOOGLE_RECAPTCHA_PRIVATE=
|
||||
GOOGLE_RECAPTCHA_VERSION=
|
||||
|
||||
LDC_WEB_API_KEY="MY_KEY"
|
||||
LDC_STATUS_CHECKER_API_KEY="MY_KEY"
|
||||
LDC_STATUS_CHECKER_UUID="UUID_PROJECT"
|
||||
LDC_STATUS_CHECKER_UUID="UUID_PROJECT"
|
||||
|
|
|
|||
|
|
@ -6,19 +6,10 @@ use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
|||
use Illuminate\Foundation\Bus\DispatchesJobs;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use App\Enums\Country;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
use DispatchesJobs;
|
||||
use ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Perform any actions required when creating the object.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
view()->share('availableCountries', Country::toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ class Kernel extends HttpKernel
|
|||
'lang-check',
|
||||
\App\Http\Middleware\VerifyCsrfToken::class,
|
||||
\Illuminate\Routing\Middleware\SubstituteBindings::class,
|
||||
\App\Http\Middleware\SecureHeaders::class
|
||||
],
|
||||
|
||||
'api' => [
|
||||
|
|
|
|||
88
app/Http/Middleware/SecureHeaders.php
Normal file
88
app/Http/Middleware/SecureHeaders.php
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class SecureHeaders
|
||||
{
|
||||
/**
|
||||
* Enumerate headers which you do not want in your application's responses.
|
||||
* Great starting point would be to go check out @Scott_Helme's:
|
||||
* https://securityheaders.com/
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $unwantedHeaderList = [
|
||||
'X-Powered-By',
|
||||
'Server',
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
View::share('nonce', $nonce = \uniqid());
|
||||
// * For VueJS dev tool to execute
|
||||
$jsDev = config('app.debug') ? "'unsafe-eval' 'unsafe-inline'" : "'nonce-$nonce'";
|
||||
$reportUri = Route::has('cspReportUri') ?
|
||||
\sprintf('report-uri %s', \parse_url(route('cspReportUri'), \PHP_URL_PATH)) : '';
|
||||
$this->removeUnwantedHeaders($this->unwantedHeaderList);
|
||||
$response = $next($request);
|
||||
$response->headers->set('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
$response->headers->set('X-Content-Type-Options', 'nosniff');
|
||||
$response->headers->set('X-XSS-Protection', '1; mode=block');
|
||||
$response->headers->set('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains;');
|
||||
$cspPolicies = [
|
||||
// "default-src 'none';",
|
||||
"manifest-src 'self';",
|
||||
"connect-src 'self' https://*.googletagmanager.com blob:;",
|
||||
"img-src * https://*.googletagmanager.com data: blob:;",
|
||||
'frame-src ' . implode(' ', [
|
||||
'https://www.google.com/recaptcha/',
|
||||
'https://recaptcha.google.com/recaptcha/',
|
||||
]),
|
||||
// "font-src 'self' data:;",
|
||||
// ! Using CKEditor will must have unsafe-inline style
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
"style-src 'self' 'unsafe-inline';",
|
||||
// ! If you dont use ckeditor use this
|
||||
// "style-src 'self' 'nonce-$nonce';",
|
||||
// phpcs:ignore Generic.Files.LineLength.TooLong
|
||||
'script-src ' . implode(" ", [
|
||||
'\'self\'',
|
||||
'https://www.google.com/recaptcha/',
|
||||
'https://www.gstatic.com/recaptcha/',
|
||||
'https://*.googletagmanager.com',
|
||||
]) . ' ' . $jsDev . ';'
|
||||
];
|
||||
$response->headers->set(
|
||||
'Content-Security-Policy',
|
||||
implode(' ', $cspPolicies) . $reportUri
|
||||
);
|
||||
$response->headers->set('Feature-Policy', 'fullscreen \'self\';');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unwanted headers
|
||||
*
|
||||
* @param array $headerList
|
||||
* @return void
|
||||
*/
|
||||
private function removeUnwantedHeaders(array $headerList)
|
||||
{
|
||||
foreach ($headerList as $header) {
|
||||
header_remove($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Lib\Helpers\ToolboxHelper;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ContactFormRequest extends FormRequest
|
||||
|
|
@ -23,6 +24,8 @@ class ContactFormRequest extends FormRequest
|
|||
*/
|
||||
public function rules()
|
||||
{
|
||||
ToolboxHelper::validateRecaptcha($this, \config('services.google.recaptcha.version'));
|
||||
|
||||
return [
|
||||
'civilite' => 'required|in:monsieur,madame',
|
||||
'name' => 'required|string|min:2',
|
||||
|
|
|
|||
65
app/Lib/Helpers/ToolboxHelper.php
Normal file
65
app/Lib/Helpers/ToolboxHelper.php
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Helpers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class ToolboxHelper
|
||||
{
|
||||
/**
|
||||
* Validate recaptcha
|
||||
*
|
||||
* @param Request $request
|
||||
* @param boolean $isV3
|
||||
* @return void
|
||||
*/
|
||||
public static function validateRecaptcha(Request $request, bool $isV3 = true)
|
||||
{
|
||||
Validator::validate($request->all(), [
|
||||
'g-recaptcha-response' => [
|
||||
'required', 'string',
|
||||
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed, Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
|
||||
function ($attribute, $value, $fail) use ($isV3) {
|
||||
try {
|
||||
$response = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', [
|
||||
'secret' => config('services.google.recaptcha.private'),
|
||||
'response' => \strval($value),
|
||||
'remoteip' => app('clientIp')
|
||||
]);
|
||||
if ($e = $response->toException()) {
|
||||
throw $e;
|
||||
}
|
||||
if ($isV3) {
|
||||
if (
|
||||
!$response->json('success') or
|
||||
!$response->json('challenge_ts') or
|
||||
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
|
||||
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST) or
|
||||
!$response->json('score')
|
||||
) {
|
||||
throw new \RuntimeException();
|
||||
}
|
||||
if (\floatval($response->json('score')) < 0.8) {
|
||||
$fail("Vous n'avez pas obtenu un score assez élevé lors de la validation du recaptcha");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!$response->json('success') or
|
||||
!$response->json('challenge_ts') or
|
||||
Carbon::parse($response->json('challenge_ts'))->lessThan(\now('utc')->subMinutes(5)) or
|
||||
$response->json('hostname') !== \parse_url(config('app.url'), \PHP_URL_HOST)
|
||||
) {
|
||||
throw new \RuntimeException();
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$fail('Erreur lors de la validation du formulaire veuillez réssayer');
|
||||
} //end try
|
||||
}
|
||||
]
|
||||
], [], ['g-recaptcha-response' => 'reCaptcha']);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ namespace App\Providers;
|
|||
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use App\Enums\Country;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
|
@ -24,5 +26,18 @@ class AppServiceProvider extends ServiceProvider
|
|||
public function boot()
|
||||
{
|
||||
URL::forceScheme('https');
|
||||
|
||||
// * HA PROXY FIX
|
||||
app()->singleton('clientIp', function () {
|
||||
if (config('app.env') === 'local') {
|
||||
return request()->getClientIp();
|
||||
}
|
||||
return request()->server('HTTP_X_FORWARDED_FOR') ?? request()->getClientIp();
|
||||
});
|
||||
|
||||
$listCountries = Cache::remember('listCountries', 60, function () {
|
||||
return Country::toArray();
|
||||
});
|
||||
view()->share('availableCountries', $listCountries ?? Country::toArray());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,4 +30,12 @@ return [
|
|||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'google' => [
|
||||
'recaptcha' => [
|
||||
'public' => env('GOOGLE_RECAPTCHA_PUBLIC'),
|
||||
'private' => env('GOOGLE_RECAPTCHA_PRIVATE'),
|
||||
'version' => env('GOOGLE_RECAPTCHA_VERSION', "3") === "3" ? true : false
|
||||
]
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -16770,4 +16770,4 @@
|
|||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
resources/js/frontend/custom.js
vendored
27
resources/js/frontend/custom.js
vendored
|
|
@ -30,6 +30,33 @@ docReady(function () {
|
|||
}
|
||||
};
|
||||
|
||||
// PAGE CONTACT
|
||||
let eltCountry = document.getElementById("country");
|
||||
if (eltCountry) {
|
||||
eltCountry.addEventListener("click", function () {
|
||||
showDromField(this);
|
||||
});
|
||||
}
|
||||
|
||||
let eltsConsoRecla = document.getElementsByClassName("consoRecla");
|
||||
if (eltsConsoRecla) {
|
||||
for (let ii = 0; ii < eltsConsoRecla.length; ii++) {
|
||||
eltsConsoRecla[ii].addEventListener("click", function () {
|
||||
showReclaFields();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let eltsOtherRecla = document.getElementsByClassName("otherRecla");
|
||||
if (eltsOtherRecla) {
|
||||
for (let ii = 0; ii < eltsOtherRecla.length; ii++) {
|
||||
eltsOtherRecla[ii].addEventListener("click", function () {
|
||||
hideReclaFields();
|
||||
});
|
||||
}
|
||||
}
|
||||
// FIN PAGE CONTACT
|
||||
|
||||
document
|
||||
.getElementById("backtotop")
|
||||
.addEventListener("click", function () {
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@
|
|||
<div class="header-title sectiontitle align-center p-rel">
|
||||
<h2 class="punchline-module__title align-center animate__animated fadeInUp animate__slow">{!! __('Une question ? Une idée ? Un conseil ?<br>Des informations sur nos produits ?') !!}</h2>
|
||||
</div>
|
||||
<p class="align-center">{!! __('N’hésitez-pas à nous contacter en renseignant le formulaire ci-dessous.<br>Nous traiterons votre demande avec la plus grande attention dans les meilleurs délais.') !!}</p>
|
||||
<p class="align-center">{!! __('N\'hésitez-pas à nous contacter en renseignant le formulaire ci-dessous.<br>Nous traiterons votre demande avec la plus grande attention dans les meilleurs délais.') !!}</p>
|
||||
@if(session()->has('status'))
|
||||
<div class="form-result form-result_confirm">
|
||||
<div class="form-result__title title title_small">{{ __('Votre message a bien été envoyé !') }}</div>
|
||||
<div class="form-result__text">{{ __('Nos services mettent tout en oeuvre pour y répondre au plus vite.') }}</div>
|
||||
</div>
|
||||
@endif
|
||||
<form action="{{route($routeprefixer . 'contact.submit')}}" method="post" class="form contact__form" enctype="multipart/form-data">
|
||||
<form id="contact-form" action="{{route($routeprefixer . 'contact.submit')}}" method="post" class="form contact__form" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@if ($errors->any())
|
||||
@foreach ($errors->all() as $error)
|
||||
|
|
@ -110,10 +110,10 @@
|
|||
</div>
|
||||
<div class="form-row">
|
||||
<div class="country">
|
||||
<select id="country" class="changeLength" maxlength="255" name="country" required onclick="javascript:showDromField(this);">
|
||||
<select id="country" class="changeLength" maxlength="255" name="country" required>
|
||||
<option value="" @if(!old('country')) selected @endif>{{ __('Sélectionnez un pays') }} *</option>
|
||||
@foreach($availableCountries as $country)
|
||||
<option value="{{ $country->label }}" @if(old('country') == $country->label) selected @endif>{{ __($country->label) }}</option>
|
||||
<option value="{{ $country->label }}" @if(old('country') == $country->label) selected @endif>{{ $country->label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
|
@ -141,16 +141,16 @@
|
|||
<legend>{{ __('Préciser votre demande') }} :</legend>
|
||||
<div class="ck-button">
|
||||
<label>
|
||||
<input type="radio" value="Réclamation" name="topic" onclick="javascript:showReclaFields();" @if (old('topic')=="Réclamation") checked @endif><span>{{ __('Réclamation produit') }}</span>
|
||||
<input class="consoRecla" type="radio" value="Réclamation" name="topic" @if (old('topic')=="Réclamation") checked @endif><span>{{ __('Réclamation produit') }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" value="La marque" name="topic" onclick="javascript:hideReclaFields();" @if (old('topic')=="La marque") checked @endif><span>{{ __('La marque') }}</span>
|
||||
<input class="otherRecla" type="radio" value="La marque" name="topic" @if (old('topic')=="La marque") checked @endif><span>{{ __('La marque') }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" value="Contact Commercial" onclick="javascript:hideReclaFields();" name="topic" @if (old('topic')=="Contact Commercial") checked @endif><span>{{ __('Contact commercial') }}</span>
|
||||
<input class="otherRecla" type="radio" value="Contact Commercial" name="topic" @if (old('topic')=="Contact Commercial") checked @endif><span>{{ __('Contact commercial') }}</span>
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" value="Autre" name="topic" onclick="javascript:hideReclaFields();" @if (old('topic')=="Autre") checked @endif><span>{{ __('Autre demande') }}</span>
|
||||
<input class="otherRecla" type="radio" value="Autre" name="topic" @if (old('topic')=="Autre") checked @endif><span>{{ __('Autre demande') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row recla_field">
|
||||
|
|
@ -198,6 +198,7 @@
|
|||
<label for="rgpd">{{ __('rgpd.contact.acceptcgu') }}</label>
|
||||
</div>
|
||||
<div class="align-center">
|
||||
<div class="g-recaptcha" data-sitekey="{{ config('services.google.recaptcha.public') }}"></div>
|
||||
<input type="submit" value="Envoyer">
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -207,53 +208,67 @@
|
|||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function showReclaFields() {
|
||||
console.log('show');
|
||||
var collection = document.getElementsByClassName('recla_field');
|
||||
for(let elem of collection) {
|
||||
elem.style.display = 'flex';
|
||||
}
|
||||
document.getElementById('lblFile').innerHTML = 'Photo de l\'emballage*';
|
||||
document.getElementById('file').required = true;
|
||||
}
|
||||
|
||||
function hideReclaFields() {
|
||||
console.log('hide');
|
||||
var collection = document.getElementsByClassName('recla_field');
|
||||
for(let elem of collection) {
|
||||
elem.style.display = 'none';
|
||||
<script nonce="{{ $nonce }}">
|
||||
function showReclaFields() {
|
||||
var collection = document.getElementsByClassName('recla_field');
|
||||
for(let elem of collection) {
|
||||
elem.style.display = 'flex';
|
||||
}
|
||||
document.getElementById('lblFile').innerHTML = 'Photo de l\'emballage*';
|
||||
document.getElementById('file').required = true;
|
||||
}
|
||||
document.getElementById('lblFile').innerHTML = 'Insérer une pièce jointe';
|
||||
document.getElementById('file').required = false;
|
||||
}
|
||||
|
||||
function showDromField(sel) {
|
||||
if (document.getElementById('country').options[sel.selectedIndex].text=='France') {
|
||||
function hideReclaFields() {
|
||||
var collection = document.getElementsByClassName('recla_field');
|
||||
for(let elem of collection) {
|
||||
elem.style.display = 'none';
|
||||
}
|
||||
document.getElementById('lblFile').innerHTML = 'Insérer une pièce jointe';
|
||||
document.getElementById('file').required = false;
|
||||
}
|
||||
|
||||
function showDromField(sel) {
|
||||
if (document.getElementById('country').options[sel.selectedIndex].text=='France') {
|
||||
var dromList = document.getElementById('departement');
|
||||
dromList.style.display = 'initial';
|
||||
dromList.required = true;
|
||||
} else {
|
||||
hideDromField();
|
||||
}
|
||||
}
|
||||
|
||||
function hideDromField() {
|
||||
var dromList = document.getElementById('departement');
|
||||
dromList.style.display = 'initial';
|
||||
dromList.required = true;
|
||||
} else {
|
||||
hideDromField();
|
||||
dromList.style.display = 'none';
|
||||
dromList.required = false;
|
||||
}
|
||||
}
|
||||
|
||||
function hideDromField() {
|
||||
var dromList = document.getElementById('departement');
|
||||
dromList.style.display = 'none';
|
||||
dromList.required = false;
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
hideReclaFields();
|
||||
hideDromField();
|
||||
@if (old('topic')=="Réclamation")
|
||||
showReclaFields();
|
||||
@endif
|
||||
@if (old('country')=="France")
|
||||
showDromField();
|
||||
@endif
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
hideReclaFields();
|
||||
hideDromField();
|
||||
@if (old('topic')=="Réclamation")
|
||||
showReclaFields();
|
||||
@endif
|
||||
@if (old('country')=="France")
|
||||
showDromField();
|
||||
@endif
|
||||
});
|
||||
</script>
|
||||
|
||||
<script nonce="{{ $nonce }}">
|
||||
window.__LDC_CN_Recaptcha = function() {
|
||||
(function(d, t) {
|
||||
let g = d.createElement(t),
|
||||
s = d.getElementsByTagName(t)[0];
|
||||
g.src = 'https://www.google.com/recaptcha/api.js';
|
||||
s.parentNode.insertBefore(g, s)
|
||||
}(document, 'script'));
|
||||
};
|
||||
|
||||
function onSubmit(token) {
|
||||
document.getElementById("contact-form").submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="{{ mix('js/preload-front.js') }}" type="text/javascript"></script>
|
||||
<script src="{{ mix('js/preload-front.js') }}" type="text/javascript" nonce="{{ $nonce }}"></script>
|
||||
@stack('scripts')
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -124,11 +124,11 @@
|
|||
</div>
|
||||
</header>
|
||||
@push('scripts')
|
||||
<script>
|
||||
<script nonce="{{ $nonce }}">
|
||||
var loupe = document.querySelector("div#search");
|
||||
|
||||
loupe.addEventListener("click", function() {
|
||||
document.querySelector("div.search-form").classList.toggle('is-visible');
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endpush
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
<script nonce="{{ $nonce }}">
|
||||
window.LDCWebCN = new LdcwebCn({
|
||||
'analytics': {
|
||||
'name': "{!! __('rgpd.analytics.title') !!}",
|
||||
|
|
@ -112,7 +112,19 @@
|
|||
gtag('js', new Date());
|
||||
gtag('config', '{{ config('google.analyticsKey') }}');
|
||||
}
|
||||
}
|
||||
},
|
||||
'recaptcha': {
|
||||
'name': "API - reCAPTACHA",
|
||||
'description': 'Les cookies qui sont déposés via le recaptcha sur les formulaires ont pour finalité d\'empêcher que ceux-ci ne soient soumis par des robots.',
|
||||
'link': 'https://policies.google.com/privacy',
|
||||
'labelLink': 'En savoir plus',
|
||||
'cookies': ['NID', '__Secure-3PAPISID', '__Secure-3PSID', '__Secure-3PSIDCC'],
|
||||
'script': function() {
|
||||
if (window.__LDC_CN_Recaptcha) {
|
||||
window.__LDC_CN_Recaptcha();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue