feat: Permettre au super admin de se connecter et accéder à son dashboard
Le super admin (table super_admins, master DB) ne pouvait pas se connecter via /api/login car ce firewall n'utilisait que le provider tenant. De même, le JWT n'était pas enrichi pour les super admins, l'endpoint /api/me/roles les rejetait, et le frontend redirigeait systématiquement vers /dashboard. Un chain provider (super_admin + tenant) résout l'authentification, le JwtPayloadEnricher et MyRolesProvider gèrent désormais les deux types d'utilisateurs, et le frontend redirige selon le rôle après login.
This commit is contained in:
@@ -327,6 +327,18 @@ export function isAuthenticated(): boolean {
|
||||
return accessToken !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse les rôles depuis le JWT en mémoire.
|
||||
* Utilisé pour la redirection post-login (super admin vs utilisateur normal).
|
||||
*/
|
||||
export function getJwtRoles(): string[] {
|
||||
if (!accessToken) return [];
|
||||
const payload = parseJwtPayload(accessToken);
|
||||
if (!payload) return [];
|
||||
const roles = payload['roles'];
|
||||
return Array.isArray(roles) ? roles : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le token actuel (pour debug uniquement).
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@ export {
|
||||
authenticatedFetch,
|
||||
isAuthenticated,
|
||||
getAccessToken,
|
||||
getJwtRoles,
|
||||
getCurrentUserId,
|
||||
type LoginCredentials,
|
||||
type LoginResult,
|
||||
|
||||
81
frontend/src/lib/features/super-admin/api/super-admin.ts
Normal file
81
frontend/src/lib/features/super-admin/api/super-admin.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { getApiBaseUrl } from '$lib/api/config';
|
||||
import { authenticatedFetch } from '$lib/auth/auth.svelte';
|
||||
|
||||
const apiUrl = getApiBaseUrl();
|
||||
|
||||
export interface EstablishmentData {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
name: string;
|
||||
subdomain: string;
|
||||
databaseName?: string;
|
||||
status: string;
|
||||
createdAt?: string;
|
||||
lastActivityAt?: string;
|
||||
}
|
||||
|
||||
export interface EstablishmentMetrics {
|
||||
establishmentId: string;
|
||||
name: string;
|
||||
status: string;
|
||||
userCount: number;
|
||||
studentCount: number;
|
||||
teacherCount: number;
|
||||
lastLoginAt: string | null;
|
||||
}
|
||||
|
||||
export interface CreateEstablishmentInput {
|
||||
name: string;
|
||||
subdomain: string;
|
||||
adminEmail: string;
|
||||
}
|
||||
|
||||
export async function getEstablishments(): Promise<EstablishmentData[]> {
|
||||
const response = await authenticatedFetch(`${apiUrl}/super-admin/establishments`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors du chargement des établissements');
|
||||
}
|
||||
const data = await response.json();
|
||||
return data['hydra:member'] ?? data['member'] ?? data;
|
||||
}
|
||||
|
||||
export async function getEstablishment(id: string): Promise<EstablishmentData> {
|
||||
const response = await authenticatedFetch(`${apiUrl}/super-admin/establishments/${id}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Établissement introuvable');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function createEstablishment(input: CreateEstablishmentInput): Promise<EstablishmentData> {
|
||||
const response = await authenticatedFetch(`${apiUrl}/super-admin/establishments`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(input)
|
||||
});
|
||||
if (!response.ok) {
|
||||
const error = await response.json().catch(() => null);
|
||||
throw new Error(error?.message ?? 'Erreur lors de la création');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export async function getMetrics(): Promise<EstablishmentMetrics[]> {
|
||||
const response = await authenticatedFetch(`${apiUrl}/super-admin/metrics`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors du chargement des métriques');
|
||||
}
|
||||
const data = await response.json();
|
||||
return data['hydra:member'] ?? data['member'] ?? data;
|
||||
}
|
||||
|
||||
export async function switchTenant(tenantId: string): Promise<void> {
|
||||
const response = await authenticatedFetch(`${apiUrl}/super-admin/switch-tenant`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ tenantId })
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Erreur lors du basculement de contexte');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user