feat(auth): real JWT login, RBAC routes, RGPD cookie banner + users/journal admin
Build & Deploy / build (push) Successful in 11s
Build & Deploy / build (push) Successful in 11s
This commit is contained in:
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user