199 lines
8.6 KiB
PHP
199 lines
8.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
final class AuthController
|
|
{
|
|
public static function logo(): void
|
|
{
|
|
header('Content-Type: image/png');
|
|
header('Cache-Control: public,max-age=86400');
|
|
readfile(__DIR__ . '/../../resources/branding/Logo_Globinours_maxi.png');
|
|
exit();
|
|
}
|
|
public static function pwaIcon(int $size): void
|
|
{
|
|
$size = in_array($size, [192, 512], true) ? $size : 192;
|
|
$source = __DIR__ . '/../../resources/branding/Logo_Globinours_maxi.png';
|
|
if (!extension_loaded('gd') || !is_file($source)) {
|
|
self::logo();
|
|
return;
|
|
}
|
|
$src = imagecreatefrompng($source);
|
|
if (!$src) {
|
|
self::logo();
|
|
return;
|
|
}
|
|
$dst = imagecreatetruecolor($size, $size);
|
|
imagesavealpha($dst, true);
|
|
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
|
|
imagefill($dst, 0, 0, $transparent);
|
|
$sw = imagesx($src);
|
|
$sh = imagesy($src);
|
|
$ratio = min(($size * 0.88) / $sw, ($size * 0.88) / $sh);
|
|
$w = (int) round($sw * $ratio);
|
|
$h = (int) round($sh * $ratio);
|
|
imagecopyresampled($dst, $src, (int) (($size - $w) / 2), (int) (($size - $h) / 2), 0, 0, $w, $h, $sw, $sh);
|
|
header('Content-Type: image/png');
|
|
header('Cache-Control: public,max-age=604800,immutable');
|
|
imagepng($dst);
|
|
imagedestroy($src);
|
|
imagedestroy($dst);
|
|
exit();
|
|
}
|
|
public static function setup(): void
|
|
{
|
|
if (Auth::countUsers() > 0) {
|
|
header('Location: /login');
|
|
exit();
|
|
}
|
|
$error = null;
|
|
$checks = InstallationService::runtimeChecks();
|
|
$canInstall = InstallationService::canInstall($checks);
|
|
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
|
|
if (!Auth::validCsrf($_POST['csrf'] ?? null)) {
|
|
$error = t('auth.session_expired');
|
|
} elseif (!$canInstall) {
|
|
$error = t('install.requirements_block');
|
|
} else {
|
|
$username = trim((string) ($_POST['username'] ?? ''));
|
|
$display = trim((string) ($_POST['display_name'] ?? ''));
|
|
$password = (string) ($_POST['password'] ?? '');
|
|
$confirm = (string) ($_POST['password_confirm'] ?? '');
|
|
$association = trim((string) ($_POST['association_name'] ?? ''));
|
|
$email = trim((string) ($_POST['association_email'] ?? ''));
|
|
$language = (string) ($_POST['app_language'] ?? 'fr');
|
|
$routing = (string) ($_POST['public_site_routing'] ?? 'integrated');
|
|
if ($association === '') {
|
|
$error = t('install.association_required');
|
|
} elseif ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
$error = t('settings.invalid_email');
|
|
} elseif (!preg_match('/^[a-zA-Z0-9._-]{3,40}$/', $username)) {
|
|
$error = t('auth.invalid_username');
|
|
} elseif ($display === '') {
|
|
$error = t('auth.display_required');
|
|
} elseif (strlen($password) < 10) {
|
|
$error = t('auth.password_min', ['count' => 10]);
|
|
} elseif ($password !== $confirm) {
|
|
$error = t('auth.password_mismatch');
|
|
} else {
|
|
$db = DB::pdo();
|
|
try {
|
|
$db->beginTransaction();
|
|
$db->prepare(
|
|
"INSERT INTO users(username,password_hash,display_name,role) VALUES(:u,:p,:d,'admin')",
|
|
)->execute([
|
|
':u' => $username,
|
|
':p' => password_hash($password, PASSWORD_DEFAULT),
|
|
':d' => $display,
|
|
]);
|
|
AppSettings::save(
|
|
[
|
|
'association_name' => $association,
|
|
'association_address' => trim((string) ($_POST['association_address'] ?? '')),
|
|
'association_postal_code' => trim((string) ($_POST['association_postal_code'] ?? '')),
|
|
'association_city' => trim((string) ($_POST['association_city'] ?? '')),
|
|
'association_phone' => trim((string) ($_POST['association_phone'] ?? '')),
|
|
'association_email' => $email,
|
|
'association_siret' => trim((string) ($_POST['association_siret'] ?? '')),
|
|
'association_rna' => trim((string) ($_POST['association_rna'] ?? '')),
|
|
'app_language' => isset(I18n::LOCALES[$language]) ? $language : 'fr',
|
|
'public_site_routing' => in_array($routing, ['integrated', 'root'], true)
|
|
? $routing
|
|
: 'integrated',
|
|
],
|
|
(int) $db->lastInsertId(),
|
|
);
|
|
$db->commit();
|
|
Auth::login($username, $password);
|
|
AuditService::log(
|
|
'setup',
|
|
'/setup',
|
|
'Installation initiale et création du premier administrateur',
|
|
'user',
|
|
Auth::id(),
|
|
);
|
|
header('Location: /settings/health?installed=1');
|
|
exit();
|
|
} catch (Throwable $e) {
|
|
if ($db->inTransaction()) {
|
|
$db->rollBack();
|
|
}
|
|
$error = SecurityService::publicError($e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
self::view(t('auth.first_setup'), 'setup', $error, [
|
|
'checks' => $checks,
|
|
'canInstall' => $canInstall,
|
|
'publicPath' => InstallationService::publicPath(),
|
|
]);
|
|
}
|
|
public static function setupUnavailable(array $checks): void
|
|
{
|
|
self::view(t('auth.first_setup'), 'setup', $error = t('install.runtime_missing'), [
|
|
'checks' => $checks,
|
|
'canInstall' => false,
|
|
'publicPath' => InstallationService::publicPath(),
|
|
]);
|
|
}
|
|
public static function login(): void
|
|
{
|
|
if (Auth::countUsers() === 0) {
|
|
header('Location: /setup');
|
|
exit();
|
|
}
|
|
if (Auth::user()) {
|
|
header('Location: ' . PermissionService::firstAllowedPath());
|
|
exit();
|
|
}
|
|
$error = null;
|
|
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
|
|
if (!Auth::validCsrf($_POST['csrf'] ?? null)) {
|
|
$error = t('auth.session_expired');
|
|
} elseif (
|
|
Auth::login(
|
|
(string) ($_POST['username'] ?? ''),
|
|
(string) ($_POST['password'] ?? ''),
|
|
isset($_POST['remember_device']),
|
|
(string) ($_POST['device_name'] ?? ''),
|
|
)
|
|
) {
|
|
AuditService::log('login', '/login', 'Connexion réussie');
|
|
$next = (string) ($_POST['next'] ?? '');
|
|
if (
|
|
!str_starts_with($next, '/') ||
|
|
str_starts_with($next, '//') ||
|
|
!PermissionService::canOpenPath((string) (parse_url($next, PHP_URL_PATH) ?: '/'))
|
|
) {
|
|
$next = PermissionService::firstAllowedPath();
|
|
}
|
|
header('Location: ' . $next);
|
|
exit();
|
|
} else {
|
|
AuditService::log('login_failed', '/login', 'Échec de connexion', null, null, [
|
|
'username_hash' => hash('sha256', mb_strtolower(trim((string) ($_POST['username'] ?? '')))),
|
|
]);
|
|
$error = t('auth.invalid_credentials');
|
|
}
|
|
}
|
|
self::view(t('auth.login'), 'login', $error);
|
|
}
|
|
public static function logout(): void
|
|
{
|
|
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST' || !Auth::validCsrf($_POST['csrf'] ?? null)) {
|
|
http_response_code(400);
|
|
return;
|
|
}
|
|
AuditService::log('logout', '/logout', 'Déconnexion');
|
|
Auth::logout();
|
|
header('Location: /login');
|
|
exit();
|
|
}
|
|
private static function view(string $title, string $mode, ?string $error, array $vars = []): void
|
|
{
|
|
extract($vars, EXTR_SKIP);
|
|
require __DIR__ . '/../Views/auth.php';
|
|
}
|
|
}
|