feat(auth): real JWT login, RBAC routes, RGPD cookie banner + users/journal admin
Build & Deploy / build (push) Successful in 11s

This commit is contained in:
neckfire
2026-06-20 12:54:01 +02:00
parent 4466b38f75
commit 7a4206a522
12 changed files with 693 additions and 80 deletions
+144 -2
View File
@@ -3,7 +3,44 @@
/* Utilise la variable d'environnement Vite si définie, sinon localhost par défaut */
const BASE_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8000';
/* Fonction utilitaire pour les requêtes fetch avec gestion d'erreur */
/* Clés de stockage local pour le token JWT et l'utilisateur connecté */
const TOKEN_KEY = 'ds_token';
const USER_KEY = 'ds_user';
/* Helpers de gestion du token JWT dans le localStorage */
export function getToken() {
return localStorage.getItem(TOKEN_KEY);
}
export function setToken(token) {
localStorage.setItem(TOKEN_KEY, token);
}
export function clearToken() {
localStorage.removeItem(TOKEN_KEY);
}
/* Construit les en-têtes d'authentification si un token est présent */
function authHeaders(extra = {}) {
const token = getToken();
const headers = { ...extra };
if (token) {
headers.Authorization = `Bearer ${token}`;
}
return headers;
}
/* Gestion d'une réponse 401 : purge de la session et redirection vers /login */
function handleUnauthorized() {
clearToken();
localStorage.removeItem(USER_KEY);
/* Évite une boucle de redirection si on est déjà sur la page de connexion */
if (window.location.pathname !== '/login') {
window.location.href = '/login';
}
}
/* Fonction utilitaire pour les requêtes fetch (GET) avec gestion d'erreur et auth */
async function fetchApi(path, params = {}) {
const url = new URL(`${BASE_URL}${path}`);
Object.entries(params).forEach(([key, value]) => {
@@ -13,7 +50,11 @@ async function fetchApi(path, params = {}) {
});
try {
const response = await fetch(url.toString());
const response = await fetch(url.toString(), { headers: authHeaders() });
if (response.status === 401) {
handleUnauthorized();
throw new Error('Session expirée, veuillez vous reconnecter.');
}
if (!response.ok) {
const errorText = await response.text();
console.error(`Erreur API [${response.status}] ${path}: ${errorText}`);
@@ -26,6 +67,107 @@ async function fetchApi(path, params = {}) {
}
}
/* Helper générique pour les requêtes JSON (POST / PUT / DELETE) avec auth */
async function sendJson(path, method, body) {
const url = `${BASE_URL}${path}`;
const options = {
method,
headers: authHeaders(body !== undefined ? { 'Content-Type': 'application/json' } : {}),
};
if (body !== undefined) {
options.body = JSON.stringify(body);
}
try {
const response = await fetch(url, options);
if (response.status === 401) {
handleUnauthorized();
throw new Error('Session expirée, veuillez vous reconnecter.');
}
if (!response.ok) {
const errorText = await response.text();
console.error(`Erreur API [${response.status}] ${path}: ${errorText}`);
throw new Error(`Erreur ${response.status}: ${errorText}`);
}
/* Certaines réponses (204, DELETE) peuvent ne pas contenir de corps JSON */
const text = await response.text();
return text ? JSON.parse(text) : null;
} catch (error) {
console.error(`Erreur lors de l'appel API ${path}:`, error);
throw error;
}
}
/* Authentification : POST /auth/login en x-www-form-urlencoded (OAuth2) */
export async function login(username, password) {
const body = new URLSearchParams();
body.append('username', username);
body.append('password', password);
const response = await fetch(`${BASE_URL}/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: body.toString(),
});
if (response.status === 401) {
throw new Error('Identifiants invalides');
}
if (response.status === 429) {
throw new Error('Trop de tentatives. Veuillez réessayer dans une minute.');
}
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Erreur ${response.status}: ${errorText}`);
}
const data = await response.json();
setToken(data.access_token);
return data.user;
}
/* Récupère le profil de l'utilisateur connecté */
export async function getMe() {
return fetchApi('/auth/me');
}
/* ---------- Administration des utilisateurs ---------- */
export async function getUsers() {
return fetchApi('/admin/users');
}
export async function createUser({ username, email, password, role }) {
return sendJson('/admin/users', 'POST', { username, email, password, role });
}
export async function updateUser(id_user, changes) {
return sendJson(`/admin/users/${id_user}`, 'PUT', changes);
}
export async function deleteUser(id_user) {
return sendJson(`/admin/users/${id_user}`, 'DELETE');
}
export async function resetUserPassword(id_user, password) {
return sendJson(`/admin/users/${id_user}/reset-password`, 'POST', { password });
}
/* Journal d'audit */
export async function getJournal(limit = 200) {
return fetchApi('/admin/journal', { limit });
}
/* ---------- RGPD : données personnelles ---------- */
export async function exportMyData() {
return fetchApi('/me/data-export');
}
export async function deleteMyAccount() {
return sendJson('/me', 'DELETE');
}
/* Santé de l'API */
export async function getHealth() {
return fetchApi('/health');