Fix SonarQube issues, store sessions in Redis, use direct analytics URLs
- ApiSandboxController: reduce scan() returns from 4 to 3 via ternary - ApiDocController: add MIME_JSON constant, extract buildInsomniaRequest() and buildInsomniaBody() to reduce cognitive complexity - Store sessions in Redis to fix SSO disconnect with 2 PHP replicas (round-robin load balancing caused session loss on filesystem storage) - Configure session cookie: 24h lifetime, secure auto, samesite lax - Replace Caddy analytics proxies (/stats/*, /assets/perf.js, /sperf) with direct URLs to tools-security.esy-web.dev and cloudflareinsights.com - Update JS tests for new direct analytics URLs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
4
.env
4
.env
@@ -32,6 +32,10 @@ DATABASE_URL="postgresql://app:secret@database:5432/ecosplay?serverVersion=16&ch
|
||||
MESSENGER_TRANSPORT_DSN=redis://redis:6379/messages
|
||||
###< symfony/messenger ###
|
||||
|
||||
###> session ###
|
||||
SESSION_HANDLER_DSN=redis://redis:6379/sessions
|
||||
###< session ###
|
||||
|
||||
###> symfony/mailer ###
|
||||
MAILER_DSN=smtp://mailpit:1025
|
||||
###< symfony/mailer ###
|
||||
|
||||
@@ -12,5 +12,6 @@ STRIPE_WEBHOOK_SECRET=whsec_test
|
||||
STRIPE_WEBHOOK_SECRET_CONNECT=whsec_test_connect
|
||||
OUTSIDE_URL=https://test.example.com
|
||||
MESSENGER_TRANSPORT_DSN=redis://:e_ticket@redis:6379/messages
|
||||
SESSION_HANDLER_DSN=redis://:e_ticket@redis:6379/sessions
|
||||
SMIME_PASSPHRASE=test
|
||||
ADMIN_EMAIL=contact@test.com
|
||||
|
||||
@@ -9,27 +9,6 @@ ticket.e-cosplay.fr {
|
||||
file_server
|
||||
}
|
||||
|
||||
handle_path /stats/* {
|
||||
rewrite * {uri}
|
||||
reverse_proxy https://tools-security.esy-web.dev {
|
||||
header_up Host tools-security.esy-web.dev
|
||||
}
|
||||
}
|
||||
|
||||
handle /assets/perf.js {
|
||||
rewrite * /beacon.min.js
|
||||
reverse_proxy https://static.cloudflareinsights.com {
|
||||
header_up Host static.cloudflareinsights.com
|
||||
}
|
||||
}
|
||||
|
||||
handle_path /sperf {
|
||||
rewrite * /cdn-cgi/rum
|
||||
reverse_proxy https://cloudflareinsights.com {
|
||||
header_up Host cloudflareinsights.com
|
||||
}
|
||||
}
|
||||
|
||||
@maintenance file /var/www/e-ticket/public/.update
|
||||
handle @maintenance {
|
||||
root * /var/www/e-ticket/public
|
||||
|
||||
@@ -24,9 +24,9 @@ function loadAnalytics() {
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.defer = true
|
||||
script.src = '/stats/script.js'
|
||||
script.src = 'https://tools-security.esy-web.dev/script.js'
|
||||
script.dataset.websiteId = 'a1f85dd5-741f-4df7-840a-7ef0931ed0cc'
|
||||
script.dataset.hostUrl = '/stats'
|
||||
script.dataset.hostUrl = 'https://tools-security.esy-web.dev'
|
||||
script.dataset.analytics = '1'
|
||||
document.head.appendChild(script)
|
||||
|
||||
@@ -40,7 +40,7 @@ function loadCloudflareTunnel() {
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.defer = true
|
||||
script.src = '/assets/perf.js'
|
||||
script.src = 'https://static.cloudflareinsights.com/beacon.min.js'
|
||||
script.dataset.cfBeacon = '{"token":"5f2f3b8e1f824be6984a348fe31d2f04","spa":true}'
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,11 @@ framework:
|
||||
secret: '%env(APP_SECRET)%'
|
||||
|
||||
# Note that the session will be started ONLY if you read or write from it.
|
||||
session: true
|
||||
session:
|
||||
handler_id: '%env(SESSION_HANDLER_DSN)%'
|
||||
cookie_lifetime: 86400
|
||||
cookie_secure: auto
|
||||
cookie_samesite: lax
|
||||
|
||||
#esi: true
|
||||
#fragments: true
|
||||
|
||||
@@ -1271,9 +1271,9 @@ use Symfony\Component\Config\Loader\ParamConfigurator as Param;
|
||||
* lifetime?: int|Param, // Default: 31536000
|
||||
* path?: scalar|Param|null, // Default: "/"
|
||||
* domain?: scalar|Param|null, // Default: null
|
||||
* secure?: true|false|"auto"|Param, // Default: false
|
||||
* secure?: true|false|"auto"|Param, // Default: null
|
||||
* httponly?: bool|Param, // Default: true
|
||||
* samesite?: null|"lax"|"strict"|"none"|Param, // Default: null
|
||||
* samesite?: null|"lax"|"strict"|"none"|Param, // Default: "lax"
|
||||
* always_remember_me?: bool|Param, // Default: false
|
||||
* remember_me_parameter?: scalar|Param|null, // Default: "_remember_me"
|
||||
* },
|
||||
|
||||
@@ -119,13 +119,9 @@ class ApiSandboxController extends AbstractController
|
||||
$fixtures = $this->loadFixtures();
|
||||
$result = $fixtures['scan'][$reference] ?? null;
|
||||
|
||||
if (!$result) {
|
||||
return $this->error('Billet introuvable.', 404);
|
||||
}
|
||||
|
||||
unset($result['_comment']);
|
||||
|
||||
return $this->success($result);
|
||||
return $result
|
||||
? $this->success(array_diff_key($result, ['_comment' => true]))
|
||||
: $this->error('Billet introuvable.', 404);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class ApiDocController extends AbstractController
|
||||
{
|
||||
private const MIME_JSON = 'application/json';
|
||||
private const STATUS_401 = 'Non authentifie';
|
||||
private const STATUS_403_EVENT = 'Evenement non accessible';
|
||||
private const STATUS_403_BILLET = 'Billet non accessible';
|
||||
@@ -74,38 +75,48 @@ class ApiDocController extends AbstractController
|
||||
];
|
||||
|
||||
foreach ($section['endpoints'] as $reqIndex => $endpoint) {
|
||||
$resources[] = $this->buildInsomniaRequest($endpoint, $folderIndex, $reqIndex, $folderId);
|
||||
}
|
||||
}
|
||||
|
||||
$export = [
|
||||
'_type' => 'export',
|
||||
'__export_format' => 4,
|
||||
'__export_date' => date('Y-m-d\TH:i:s\Z'),
|
||||
'__export_source' => 'eticket.api.doc',
|
||||
'resources' => $resources,
|
||||
];
|
||||
|
||||
return new Response(
|
||||
json_encode($export, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES),
|
||||
200,
|
||||
[
|
||||
'Content-Type' => self::MIME_JSON,
|
||||
'Content-Disposition' => 'attachment; filename="eticket-api-insomnia.json"',
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $endpoint
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildInsomniaRequest(array $endpoint, int $folderIndex, int $reqIndex, string $folderId): array
|
||||
{
|
||||
$isAuthRoute = str_starts_with($endpoint['path'], '/api/auth');
|
||||
$isLogin = '/api/auth/login' === $endpoint['path'];
|
||||
$url = $isAuthRoute
|
||||
? '{{ _.base_url }}'.$endpoint['path']
|
||||
: '{{ _.base_url }}/api/{{ _.env }}'.str_replace('/api', '', $endpoint['path']);
|
||||
|
||||
$headers = [];
|
||||
$headers[] = ['name' => 'Content-Type', 'value' => 'application/json'];
|
||||
$headers = [['name' => 'Content-Type', 'value' => self::MIME_JSON]];
|
||||
if (!$isLogin) {
|
||||
$headers[] = ['name' => 'ETicket-Email', 'value' => '{{ _.email }}'];
|
||||
$headers[] = ['name' => 'ETicket-JWT', 'value' => '{{ _.jwt_token }}'];
|
||||
}
|
||||
|
||||
$body = null;
|
||||
if ($isLogin) {
|
||||
$body = [
|
||||
'mimeType' => 'application/json',
|
||||
'text' => json_encode([
|
||||
'email' => '{{ _.email }}',
|
||||
'password' => '{{ _.password }}',
|
||||
], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
} elseif ($endpoint['request']) {
|
||||
$example = [];
|
||||
foreach ($endpoint['request'] as $name => $field) {
|
||||
$example[$name] = $field['example'] ?? '';
|
||||
}
|
||||
$body = [
|
||||
'mimeType' => 'application/json',
|
||||
'text' => json_encode($example, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
}
|
||||
$body = $this->buildInsomniaBody($endpoint, $isLogin);
|
||||
|
||||
$req = [
|
||||
'_type' => 'request',
|
||||
@@ -122,26 +133,39 @@ class ApiDocController extends AbstractController
|
||||
$req['afterResponseScript'] = "const res = insomnia.response.json();\nif (res && res.success && res.data && res.data.token) {\n insomnia.environment.set('jwt_token', res.data.token);\n}";
|
||||
}
|
||||
|
||||
$resources[] = $req;
|
||||
}
|
||||
return $req;
|
||||
}
|
||||
|
||||
$export = [
|
||||
'_type' => 'export',
|
||||
'__export_format' => 4,
|
||||
'__export_date' => date('Y-m-d\TH:i:s\Z'),
|
||||
'__export_source' => 'eticket.api.doc',
|
||||
'resources' => $resources,
|
||||
/**
|
||||
* @param array<string, mixed> $endpoint
|
||||
*
|
||||
* @return array{mimeType: string, text: string}|null
|
||||
*/
|
||||
private function buildInsomniaBody(array $endpoint, bool $isLogin): ?array
|
||||
{
|
||||
if ($isLogin) {
|
||||
return [
|
||||
'mimeType' => self::MIME_JSON,
|
||||
'text' => json_encode([
|
||||
'email' => '{{ _.email }}',
|
||||
'password' => '{{ _.password }}',
|
||||
], \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
}
|
||||
|
||||
return new Response(
|
||||
json_encode($export, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE | \JSON_UNESCAPED_SLASHES),
|
||||
200,
|
||||
[
|
||||
'Content-Type' => 'application/json',
|
||||
'Content-Disposition' => 'attachment; filename="eticket-api-insomnia.json"',
|
||||
]
|
||||
);
|
||||
if ($endpoint['request']) {
|
||||
$example = [];
|
||||
foreach ($endpoint['request'] as $name => $field) {
|
||||
$example[$name] = $field['example'] ?? '';
|
||||
}
|
||||
|
||||
return [
|
||||
'mimeType' => self::MIME_JSON,
|
||||
'text' => json_encode($example, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE),
|
||||
];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('initCookieConsent', () => {
|
||||
document.getElementById('cookie-accept').click()
|
||||
const script = document.querySelector('script[data-analytics]')
|
||||
expect(script).not.toBeNull()
|
||||
expect(script.src).toContain('/stats/script.js')
|
||||
expect(script.src).toContain('tools-security.esy-web.dev/script.js')
|
||||
expect(script.dataset.websiteId).toBe('a1f85dd5-741f-4df7-840a-7ef0931ed0cc')
|
||||
})
|
||||
|
||||
@@ -105,7 +105,7 @@ describe('initCookieConsent', () => {
|
||||
document.getElementById('cookie-accept').click()
|
||||
const script = document.querySelector('script[data-cf-beacon]')
|
||||
expect(script).not.toBeNull()
|
||||
expect(script.src).toContain('/assets/perf.js')
|
||||
expect(script.src).toContain('static.cloudflareinsights.com/beacon.min.js')
|
||||
})
|
||||
|
||||
it('does not duplicate cloudflare script', () => {
|
||||
|
||||
Reference in New Issue
Block a user