feat: roles, permissions, grille full-width, SSO display name

- Admin/roles : tableau des roles avec edition par role (/admin/role/<nom>)
- Permissions par role : cases a cocher groupees (Articles, Acces & lecture)
- Nouvelles capacites : propose/validate/publish articles (own/all), view_previews
- Nom technique auto-genere depuis le label (JS + fallback serveur)
- Blocage suppression du dernier administrateur
- user_capabilities table ajoutee en DB
- Navbar : dropdown unique (nom + Mon identite + Administration + Deconnexion)
- SSO callback : preserve le nom personnalise, ne l ecrase plus a la connexion
- Grille articles : CSS Grid auto-fill full-width, hauteur uniforme par ligne
- CSP : add_files.js et post_confirm.js externalises
This commit is contained in:
Cedric Abonnel
2026-05-12 15:51:06 +02:00
parent 5275edfd20
commit 1d2e3d9a24
15 changed files with 1029 additions and 332 deletions
+34 -22
View File
@@ -139,19 +139,48 @@ if (!$email) {
}
// Nom d'affichage depuis les claims SSO
$displayName = '';
$ssoName = '';
if (!empty($claims['given_name']) || !empty($claims['family_name'])) {
$displayName = trim(($claims['given_name'] ?? '') . ' ' . ($claims['family_name'] ?? ''));
$ssoName = trim(($claims['given_name'] ?? '') . ' ' . ($claims['family_name'] ?? ''));
} elseif (!empty($claims['name'])) {
$displayName = trim($claims['name']);
$ssoName = trim($claims['name']);
} elseif (!empty($claims['preferred_username'])) {
$displayName = trim($claims['preferred_username']);
$ssoName = trim($claims['preferred_username']);
}
// Charge le nom personnalisé depuis la base (prioritaire sur le SSO)
require_once dirname(__DIR__, 2) . '/src/auth.php';
$pdo = dbPdo();
$dbName = '';
if ($pdo) {
try {
$st = $pdo->prepare('SELECT display_name FROM user_profiles WHERE email = :e');
$st->execute([':e' => strtolower(trim($email))]);
$dbName = (string)($st->fetchColumn() ?: '');
} catch (\Throwable) {}
}
if ($dbName !== '') {
// Nom personnalisé existant → on le conserve, le SSO ne l'écrase pas
$sessionName = $dbName;
} else {
// Première connexion → on persiste le nom SSO
$sessionName = $ssoName;
if ($ssoName !== '' && $pdo) {
try {
$pdo->prepare(
'INSERT INTO user_profiles (email, display_name, updated_at)
VALUES (:e, :n, now())
ON CONFLICT (email) DO NOTHING'
)->execute([':e' => strtolower(trim($email)), ':n' => $ssoName]);
} catch (\Throwable) {}
}
}
// Ouvre la session authentifiée
session_regenerate_id(true);
$_SESSION['user_email'] = strtolower(trim($email));
$_SESSION['user_display_name'] = $displayName;
$_SESSION['user_display_name'] = $sessionName;
$_SESSION['oidc'] = [
'issuer' => $OIDC_ISSUER,
'sub' => $claims['sub'] ?? null,
@@ -160,23 +189,6 @@ $_SESSION['oidc'] = [
'expires_at' => time() + (int)($tokens['expires_in'] ?? 3600),
];
// Persiste le nom d'affichage en base (seulement s'il vient du SSO et que la table existe)
if ($displayName !== '') {
require_once dirname(__DIR__, 2) . '/src/auth.php';
$pdo = dbPdo();
if ($pdo) {
try {
$st = $pdo->prepare(
'INSERT INTO user_profiles (email, display_name, updated_at)
VALUES (:e, :n, now())
ON CONFLICT (email) DO NOTHING'
);
$st->execute([':e' => strtolower(trim($email)), ':n' => $displayName]);
} catch (\Throwable) {
}
}
}
$target = $_SESSION['oidc_return_to'] ?? '/';
unset($_SESSION['oidc_return_to'], $_SESSION['oidc_flow']);
if (!is_string($target) || $target === '' || $target[0] !== '/') {