feat: Attribution de rôles multiples par utilisateur

Les utilisateurs Classeo étaient limités à un seul rôle, alors que
dans la réalité scolaire un directeur peut aussi être enseignant,
ou un parent peut avoir un rôle vie scolaire. Cette limitation
obligeait à créer des comptes distincts par fonction.

Le modèle User supporte désormais plusieurs rôles simultanés avec
basculement via le header. L'admin peut attribuer/retirer des rôles
depuis l'interface de gestion, avec des garde-fous : pas d'auto-
destitution, pas d'escalade de privilèges (seul SUPER_ADMIN peut
attribuer SUPER_ADMIN), vérification du statut actif pour le
switch de rôle, et TTL explicite sur le cache de rôle actif.
This commit is contained in:
2026-02-10 07:57:43 +01:00
parent 9ccad77bf0
commit e930c505df
93 changed files with 2527 additions and 165 deletions

View File

@@ -1,16 +1,29 @@
<script lang="ts">
import { untrack } from 'svelte';
import { goto } from '$app/navigation';
import { logout } from '$lib/auth/auth.svelte';
import { isAuthenticated, refreshToken, logout } from '$lib/auth/auth.svelte';
import RoleSwitcher from '$lib/components/organisms/RoleSwitcher/RoleSwitcher.svelte';
import { fetchRoles, resetRoleContext } from '$features/roles/roleContext.svelte';
let { children } = $props();
let isLoggingOut = $state(false);
// Note: Authentication is handled by authenticatedFetch in the page component.
// If not authenticated, authenticatedFetch will attempt refresh and redirect to /login if needed.
// Load user roles on mount for multi-role context switching (FR5)
// Guard: only fetch if authenticated (or refresh succeeds), otherwise stay in demo mode
$effect(() => {
untrack(async () => {
if (!isAuthenticated()) {
const refreshed = await refreshToken();
if (!refreshed) return;
}
fetchRoles();
});
});
async function handleLogout() {
isLoggingOut = true;
try {
resetRoleContext();
await logout();
} finally {
isLoggingOut = false;
@@ -33,6 +46,7 @@
<span class="logo-text">Classeo</span>
</button>
<nav class="header-nav">
<RoleSwitcher />
<a href="/dashboard" class="nav-link active">Tableau de bord</a>
<button class="nav-button" onclick={goSettings}>Parametres</button>
<button class="logout-button" onclick={handleLogout} disabled={isLoggingOut}>