Sécurité et qualité : headers HTTP, permissions .env, lint PHPStan + PHP-CS-Fixer, réorganisation dossiers, scripts de déploiement

This commit is contained in:
Cedric Abonnel
2026-05-08 13:18:00 +02:00
parent 700329f156
commit 70304d3b31
44 changed files with 776 additions and 670 deletions
+28 -10
View File
@@ -8,18 +8,27 @@ use App\Http\Csrf;
// --- Helpers AVANT tout usage ---
if (!function_exists('env')) {
function env(string $key, ?string $default = null): ?string {
if (array_key_exists($key, $_ENV) && $_ENV[$key] !== '') return (string)$_ENV[$key];
function env(string $key, ?string $default = null): ?string
{
if (array_key_exists($key, $_ENV) && $_ENV[$key] !== '') {
return (string)$_ENV[$key];
}
$v = getenv($key);
if ($v !== false && $v !== '') return (string)$v;
if ($v !== false && $v !== '') {
return (string)$v;
}
return $default;
}
}
if (!function_exists('db')) {
function db(): \PDO { return \App\Infrastructure\Database::get(); }
function db(): \PDO
{
return \App\Infrastructure\Database::get();
}
}
if (!function_exists('url')) {
function url(string $path = '/'): string {
function url(string $path = '/'): string
{
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
return $scheme . '://' . $host . $path;
@@ -40,7 +49,9 @@ $maxPerWin = (int) env('MAGIC_MAX_PER_WINDOW', '5');
$defaultReturn = '/';
$sanitize = static function (string $url) use ($defaultReturn): string {
$url = trim($url);
if ($url === '' || !str_starts_with($url, '/')) return $defaultReturn;
if ($url === '' || !str_starts_with($url, '/')) {
return $defaultReturn;
}
return $url;
};
$returnTo = $sanitize((string)($_GET['return_to'] ?? ($_SERVER['HTTP_REFERER'] ?? $defaultReturn)));
@@ -49,7 +60,10 @@ $returnTo = $sanitize((string)($_GET['return_to'] ?? ($_SERVER['HTTP_REFERER'] ?
$oidcEnabled = (bool) (env('OIDC_ISSUER') && env('OIDC_CLIENT_ID'));
$oidcLoginUrl = '/login/oidc' . ($returnTo ? ('?return_to=' . urlencode($returnTo)) : '');
$oidcAuto = (isset($_GET['sso']) && $_GET['sso'] === '1') || (env('OIDC_AUTO', '0') === '1');
if ($oidcEnabled && $oidcAuto) { header('Location: ' . $oidcLoginUrl, true, 302); exit; }
if ($oidcEnabled && $oidcAuto) {
header('Location: ' . $oidcLoginUrl, true, 302);
exit;
}
// --- form: demande de lien magique ---
$errors = [];
@@ -65,13 +79,15 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
} else {
// rate limit simple par email et IP
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0');
if (strpos($ip, ',') !== false) $ip = trim(explode(',', $ip, 2)[0]);
if (strpos($ip, ',') !== false) {
$ip = trim(explode(',', $ip, 2)[0]);
}
$pdo = db();
$pdo->beginTransaction();
try {
// purge expirés / consommés
$pdo->prepare("DELETE FROM auth_magic_links WHERE email = :e AND (expires_at < NOW() OR consumed_at IS NOT NULL)")
$pdo->prepare('DELETE FROM auth_magic_links WHERE email = :e AND (expires_at < NOW() OR consumed_at IS NOT NULL)')
->execute([':e' => $email]);
// 1) cooldown: refuser si un envoi récent < coolMin
@@ -130,7 +146,9 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
$okMsg = "Un lien vient d'être envoyé. Vérifiez votre boîte de réception et le dossier spam/indésirables.";
} catch (\Throwable $ex) {
if ($pdo->inTransaction()) $pdo->rollBack();
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
$errors[] = $ex->getMessage();
}
}