merge conflict réglés

This commit is contained in:
Mathis 2025-04-12 14:19:23 +02:00
parent 16fd7fe9ff
commit 5fda9e9dac
5 changed files with 286 additions and 212 deletions

View File

@ -5,6 +5,7 @@ import io.vertx.ext.web.RoutingContext;
import at.favre.lib.crypto.bcrypt.BCrypt;
import io.vertx.ext.auth.jwt.JWTAuth;
import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.Row;
public class AuthHandler {
private final DatabaseService databaseService;
@ -43,7 +44,7 @@ public class AuthHandler {
databaseService.pool
.preparedQuery("INSERT INTO users (name, surname, email, gender, password, pseudo) VALUES (?, ?, ?, ?, ?, ?)")
.execute(Tuple.of(name, surname, email, gender, hashedPassword,pseudo))
.execute(Tuple.of(name, surname, email, gender, hashedPassword, pseudo))
.onSuccess(result -> {
context.response()
.setStatusCode(201)
@ -78,55 +79,91 @@ public class AuthHandler {
}
databaseService.pool
.preparedQuery("SELECT id,name, surname, password, points FROM users WHERE email = ?") // Ajout de name et surname
.execute(Tuple.of(email))
.onSuccess(result -> {
if (result.rowCount() == 0) {
context.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
return;
}
.preparedQuery("SELECT id, name, surname, password, points FROM users WHERE email = ?")
.execute(Tuple.of(email))
.onSuccess(result -> {
if (result.rowCount() == 0) {
context.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
return;
}
var row = result.iterator().next();
Integer id = row.getInteger("id");
String storedHashedPassword = row.getString("password");
Integer nbPointsUser = row.getInteger("points");
String name = row.getString("name");
String surname = row.getString("surname");
// Récupération de la ligne de manière sécurisée
Row row = null;
try {
row = result.iterator().next();
} catch (Exception e) {
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur lors de la récupération des données utilisateur").encode());
return;
}
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
// Vérification que row n'est pas null avant d'y accéder
if (row == null) {
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Données utilisateur introuvables").encode());
return;
}
if (verification.verified) {
JsonObject claims = new JsonObject()
.put("sub", email)
.put("name", name)
.put("surname", surname)
.put("id", id);
// Récupération des champs de manière sécurisée
Integer id = row.getInteger("id");
String storedHashedPassword = row.getString("password");
Integer nbPointsUser = row.getInteger("points");
String name = row.getString("name");
String surname = row.getString("surname");
if (nbPointsUser <= 60) {
claims.put("role", "user");
} else if (nbPointsUser <= 100) {
claims.put("role", "complexe");
} else if (nbPointsUser >= 200) {
claims.put("role", "admin");
}
// Vérification des champs obligatoires
if (id == null || storedHashedPassword == null) {
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Données utilisateur incomplètes").encode());
return;
}
String token = jwtAuth.generateToken(claims);
// Valeur par défaut pour les points si null
if (nbPointsUser == null) {
nbPointsUser = 0;
}
context.response()
.setStatusCode(200)
.end(new JsonObject().put("token", token).encode());
} else {
context.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
}
})
.onFailure(err -> {
System.err.println("Erreur de connexion : " + err.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur serveur").encode());
});
}}
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
if (verification.verified) {
JsonObject claims = new JsonObject()
.put("sub", email)
.put("id", id);
// Ajout de name et surname seulement s'ils ne sont pas null
if (name != null) claims.put("name", name);
if (surname != null) claims.put("surname", surname);
// Attribution du rôle en fonction des points
String role = "user"; // Rôle par défaut
if (nbPointsUser >= 200) {
role = "admin";
} else if (nbPointsUser >= 100) {
role = "complexe";
}
claims.put("role", role);
String token = jwtAuth.generateToken(claims);
context.response()
.setStatusCode(200)
.end(new JsonObject().put("token", token).encode());
} else {
context.response()
.setStatusCode(401)
.end(new JsonObject().put("error", "Email ou mot de passe incorrect").encode());
}
})
.onFailure(err -> {
System.err.println("Erreur de connexion : " + err.getMessage());
context.response()
.setStatusCode(500)
.end(new JsonObject().put("error", "Erreur serveur").encode());
});
}
}

View File

@ -6,6 +6,12 @@ import { useAuth } from "../AuthContext";
function Header() {
const { token, user, logout } = useAuth();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [showAdminDropdown, setShowAdminDropdown] = useState(false);
// La fonction toggleAdminDropdown permet d'ouvrir/fermer le menu déroulant
const toggleAdminDropdown = () => {
setShowAdminDropdown((prev) => !prev);
};
return (
<header className="bg-white shadow-md ">
@ -90,17 +96,48 @@ function Header() {
</Link>
</li>
)}
{(user?.role === "admin")&&(
<li>
<Link
to="/dashboard"
onClick={() => setIsMenuOpen(false)}
className="text-gray-600 hover:text-indigo-600"
>
Administration
</Link>
</li>
{user?.role === "admin" && (
<li className="relative">
<button
onClick={toggleAdminDropdown}
className="flex items-center text-gray-600 hover:text-indigo-600 focus:outline-none"
>
Admin
<svg
className="ml-1 h-4 w-4 fill-current"
viewBox="0 0 20 20"
>
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
</button>
{showAdminDropdown && (
<div className="absolute top-full left-0 mt-2 w-48 bg-white border border-gray-200 rounded-md shadow-lg z-50">
<Link
to="/dashboard"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Dashboard
</Link>
<Link
to="/user"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Gestion des Utilisateurs
</Link>
<Link
to="/adminobjet"
className="block px-4 py-2 text-gray-700 hover:bg-gray-100"
onClick={() => setShowAdminDropdown(false)}
>
Gestion des Objets Connectés
</Link>
</div>
)}
</li>
)}
<li className="sm:hidden">
<Link
to="/profil"

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState,useEffect } from "react";
import Sidebar from "./sidebar.jsx";
import { RadioTower, ArrowRight, BadgePlus, Settings } from "lucide-react";
import { API_BASE_URL } from "../../config.js";
@ -13,34 +13,7 @@ const initialWidgets = [
function Dashboard() {
// États simulés (données fictives pour l'exemple)
const [users, setUsers] = useState([
{
id: 1,
username: "Alice",
email: "alice@example.com",
accessLevel: "Admin",
},
{ id: 2, username: "Bob", email: "bob@example.com", accessLevel: "User" },
{
id: 3,
username: "Charlie",
email: "charlie@example.com",
accessLevel: "Guest",
},
{
id: 4,
username: "David",
email: "david@example.com",
accessLevel: "User",
},
{ id: 5, username: "Eva", email: "eva@example.com", accessLevel: "User" },
{
id: 6,
username: "Frank",
email: "frank@example.com",
accessLevel: "Admin",
},
]);
const [users, setUsers] = useState([])
const [logs, setLogs] = useState([
{
@ -56,7 +29,11 @@ function Dashboard() {
timestamp: new Date().toLocaleString(),
},
]);
useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data);
});
}, []);
// État pour simuler les objets présents dans la section AdminObjet.jsx
const [adminObjects, setAdminObjects] = useState([
{
@ -111,13 +88,7 @@ function Dashboard() {
setWidgets([...widgets, newWidget]);
setShowAddWidgetModal(false);
};
/*
useEffect(() => {
axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data);
});
}, []);
*/
return (
<div className="flex min-h-screen">
<Sidebar />
@ -200,13 +171,13 @@ function Dashboard() {
{users.slice(0, 5).map((user) => (
<tr key={user.id}>
<td className="px-2 py-1 border border-gray-200">
{user.username}
{user.pseudo}
</td>
<td className="px-2 py-1 border border-gray-200">
{user.email}
</td>
<td className="px-2 py-1 border border-gray-200">
{user.accessLevel}
{user.role}
</td>
</tr>
))}

View File

@ -3,45 +3,35 @@ import Sidebar from "./sidebar.jsx";
import { API_BASE_URL } from "../../config.js";
import axios from "axios";
// Définition de styles utilisés (si nécessaire vous pouvez convertir ces styles en classes Tailwind)
const thTd = "p-2 border border-gray-300 text-left";
const th = `${thTd} bg-gray-100`;
function User() {
const [users, setUsers] = useState([]);
const [logs, setLogs] = useState([]);
const [name, setName] = useState("");
const [name, setname] = useState("");
const [email, setEmail] = useState("");
const [pointsInput, setPointsInput] = useState({});
// Ajout d'un utilisateur
const handleAddUser = (e) => {
e.preventDefault();
const newUser = {
id: Date.now(), // ID généré temporairement ; en production, il sera géré par la BDD.
id: Date.now(),
name,
email,
accessLevel: "User",
points: 0,
};
setUsers([...users, newUser]);
logAction(name, "User added");
setName("");
logAction(name, "Utilisateur ajouté");
setname("");
setEmail("");
// TODO : Envoyer newUser à l'API si nécessaire.
};
// Chargement des utilisateurs depuis l'API au montage du composant
useEffect(() => {
axios
.get(`${API_BASE_URL}/users`)
.then((response) => setUsers(response.data))
.catch((error) =>
console.error("Erreur lors de la récupération des utilisateurs:", error)
);
axios.get(`${API_BASE_URL}/users`).then((response) => {
setUsers(response.data);
});
}, []);
// Suppression d'un utilisateur
const handleDeleteUser = (userId) => {
const user = users.find((u) => u.id === userId);
@ -75,7 +65,6 @@ function User() {
}
};
// Changement du niveau d'accès
const handleChangeAccessLevel = (userId, newLevel) => {
setUsers(
users.map((user) => {
@ -83,9 +72,11 @@ function User() {
const oldLevel = user.role;
user.role = newLevel;
if (user.role === "user") {
user.points = 60;
} else if (newLevel === "admin") {
user.points = 0;
} else if (user.role === "complexe") {
user.points = 100;
} else if (user.role === "admin") {
user.points = 200;
}
axios
.post(`${API_BASE_URL}/setUserPoints`, {
@ -94,42 +85,42 @@ function User() {
})
.then((response) => {
alert("Le changement de niveau a bien été enregistré !");
console.log("Niveau changé:", response.data);
console.log("Changement de niveau réussit :", response.data);
})
.catch((error) => {
alert("Erreur lors du changement de niveau !");
console.error(error);
alert("Il y a eu une erreur dans le changement de niveau !");
console.error("Erreur lors du changement de niveau :", error);
});
logAction(user.name, `Access level changed from ${oldLevel} to ${newLevel}`);
logAction(
user.name,
`Niveau d'accés changé de ${oldLevel} à ${newLevel}`
);
}
return user;
})
);
};
// Ajustement des points : additionne les points saisis aux points existants
const handleAdjustPoints = (userId) => {
const pointsToAdd = parseInt(pointsInput[userId]) || 0;
setUsers(
users.map((user) => {
if (user.id === userId) {
user.points = pointsToAdd;
// On additionne au lieu de remplacer
user.points = (user.points || 0) + pointsToAdd;
axios
.post(`${API_BASE_URL}/setUserPoints`, {
id: user.id,
points: user.points,
})
.then((response) => {
alert("Les points ont bien été enregistrés !");
console.log("Points mis à jour :", response.data);
alert("Les points ont bien été enregistré !");
console.log("Ajout des points réussit :", response.data);
})
.catch((error) => {
alert("Erreur lors de l'ajustement des points !");
console.error(error);
alert("Il y a eu une erreur dans l'ajout des points!");
console.error("Erreur lors de l'ajout des points :", error);
});
logAction(user.name, `Points adjusted by ${pointsToAdd}, new total: ${user.points}`);
logAction(user.name, `Points ajustés par ${pointsToAdd}`);
}
return user;
})
@ -137,121 +128,107 @@ function User() {
setPointsInput({ ...pointsInput, [userId]: "" });
};
// Fonction de journalisation des actions
const logAction = (name, action) => {
/*TODO*/
/*Ajouter le suivi dans un journal de log*/
const timestamp = new Date().toLocaleString();
setLogs([...logs, { id: Date.now(), name, action, timestamp }]);
};
// Fonction pour générer et télécharger les logs dans un fichier texte
const downloadLogs = () => {
const logText = logs
.map((log) => `${log.timestamp} - ${log.name} - ${log.action}`)
.join("\n");
const blob = new Blob([logText], { type: "text/plain;charset=utf-8" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = "logs.txt";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
return (
<div className="flex h-screen">
<div className="flex min-h-screen">
<Sidebar />
<main className="flex-grow p-5">
<section className="mt-5">
<h1 className="text-2xl font-bold mb-4">Gestion des utilisateurs</h1>
<p className="mb-5">Gérez les utilisateurs à partir de ce panneau.</p>
<h1 className="text-2xl font-bold text-gray-900 mb-5">
User Management
</h1>
<p>Manage users from this panel.</p>
{/* Formulaire d'ajout d'utilisateur */}
<form
className="grid grid-cols-[1fr_1fr_auto] gap-2 mb-5"
className="gap-3 mb-5 grid grid-cols-[1fr_1fr_auto]"
onSubmit={handleAddUser}
>
<input
className="p-3 border rounded-md"
type="text"
id="name"
placeholder="Name"
placeholder="name"
value={name}
onChange={(e) => setName(e.target.value)}
onChange={(e) => setname(e.target.value)}
required
className="p-2 border border-gray-300 rounded-md"
/>
<input
className="p-3 border rounded-md"
type="email"
id="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="p-2 border border-gray-300 rounded-md"
/>
<button
className="p-3 bg-green-600 text-white border-none rounded-md "
type="submit"
className="p-2 bg-green-600 text-white rounded-md cursor-pointer"
>
Add User
Ajouter utilisateur
</button>
</form>
{/* Tableau des utilisateurs */}
<table className="w-full border-collapse mb-6">
<table className="w-full">
<thead>
<tr>
<th className={th}>Nom</th>
<th className={th}>Email</th>
<th className={th}>Niveau d'accès</th>
<th className={th}>Points</th>
<th className={th}>Actions</th>
<th className={`${th}`}>Nom</th>
<th className={`${th}`}>Email</th>
<th className={`${th}`}>Niveau d'accès</th>
<th className={`${th}`}>Points</th>
<th className={`${th}`}>Actions</th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user.id}>
<td className={thTd}>{user.name}</td>
<td className={thTd}>{user.email}</td>
<td className={thTd}>
<td className={`${thTd}`}>{user.name}</td>
<td className={`${thTd}`}>{user.email}</td>
<td className={`${thTd}`}>
<select
value={user.accessLevel}
value={user.role}
onChange={(e) =>
handleChangeAccessLevel(user.id, e.target.value)
}
className="p-1 rounded-md border border-gray-300"
className="p-2 rounded-md"
>
<option value="admin">Admin</option>
<option value="user">User</option>
<option value="complexe">Complexe</option>
</select>
</td>
<td className={thTd}>
<td className={`${thTd}`}>
<input
className="border ml-4 w-16"
type="number"
min="0"
value={pointsInput[user.id] ?? user.points}
value={pointsInput[user.id] || user.points}
onChange={(e) =>
setPointsInput({
...pointsInput,
[user.id]: e.target.value,
})
}
className="w-16 ml-2 p-1 border border-gray-300 rounded-md"
/>
<button
className="p-2 bg-green-600 text-white border-none rounded-md"
onClick={() => handleAdjustPoints(user.id)}
className="p-2 bg-green-600 text-white rounded-md ml-2"
>
Changer
Changer
</button>
</td>
<td className={thTd}>
<td className={`${thTd}`}>
<button
className="p-2 bg-red-600 text-white border-none rounded-md"
onClick={() => handleDeleteUser(user.id)}
className="p-2 bg-red-600 text-white rounded-md"
>
Supprimer
Supprimer
</button>
</td>
</tr>
@ -260,34 +237,28 @@ function User() {
</table>
</section>
{/* Tableau des logs */}
<section className="mt-10">
<h2 className="text-2xl font-bold mb-4">
Historique des connexions et journal des logs
<section className="user-logs mt-10">
<h2 className="text-2xl font-bold text-gray-900 mb-5">
Historique des connexions et journal des logs{" "}
</h2>
<table className="w-full border-collapse mb-4">
<table className="w-full">
<thead>
<tr>
<th className={th}>Nom</th>
<th className={th}>Action</th>
<th className={th}>Timestamp</th>
<th className={`${th}`}>Nom</th>
<th className={`${th}`}>Action</th>
<th className={`${th}`}>Timestamp</th>
</tr>
</thead>
<tbody>
{logs.map((log) => (
<tr key={log.id}>
<td className={thTd}>{log.name}</td>
<td className={thTd}>{log.action}</td>
<td className={thTd}>{log.timestamp}</td>
<td className={`${thTd}`}>{log.name}</td>
<td className={`${thTd}`}>{log.action}</td>
<td className={`${thTd}`}>{log.timestamp}</td>
</tr>
))}
</tbody>
</table>
<button
onClick={downloadLogs}
className="mt-4 px-4 py-2 bg-blue-600 text-white rounded-md"
>
Télécharger les logs
</button>
</section>
</main>
</div>

View File

@ -1,5 +1,5 @@
import React, { useState } from "react";
import { Mail, Lock, AlertCircle } from "lucide-react";
import { Mail, Lock, AlertCircle, CheckCircle, Info, X } from "lucide-react";
import { useNavigate, Link } from "react-router-dom";
import axios from "axios";
import { useAuth } from "../AuthContext";
@ -10,7 +10,11 @@ function Login() {
email: "",
password: "",
});
const [error, setError] = useState("");
const [alert, setAlert] = useState({
show: false,
type: "", // 'success', 'error', 'info', 'warning'
message: "",
});
const { login } = useAuth();
const navigate = useNavigate();
@ -20,14 +24,32 @@ function Login() {
...prev,
[name]: value,
}));
if (error) setError("");
if (alert.show) setAlert({ ...alert, show: false });
};
const showAlert = (type, message) => {
setAlert({
show: true,
type,
message,
});
// Auto-hide success and info alerts after 5 seconds
if (type === 'success' || type === 'info') {
setTimeout(() => {
setAlert(prev => ({ ...prev, show: false }));
}, 5000);
}
};
const handleSubmit = async (e) => {
e.preventDefault();
setError("");
setAlert({ show: false, type: "", message: "" });
try {
// Afficher un message de chargement
showAlert("info", "Connexion en cours...");
const response = await axios.post(`${API_BASE_URL}/login`, formData, {
headers: {
"Content-Type": "application/json",
@ -36,37 +58,65 @@ function Login() {
const data = response.data;
if (data.token) {
showAlert("success", "Connexion réussie! Redirection...");
login(data.token);
navigate("/");
// Court délai pour montrer le message de succès avant la redirection
setTimeout(() => {
navigate("/");
}, 1000);
} else {
setError("Authentification échouée : token manquant dans la réponse");
showAlert("error", "Authentification échouée : token manquant dans la réponse");
}
} catch (error) {
console.error("Erreur lors de la connexion", error);
if (error.response) {
if (error.response.status === 401) {
setError("Email ou mot de passe incorrect");
showAlert("error", "Email ou mot de passe incorrect");
} else if (error.response.status === 422) {
setError("Données de formulaire invalides");
showAlert("error", "Données de formulaire invalides");
} else if (error.response.status >= 500) {
setError("Erreur serveur. Veuillez réessayer plus tard.");
showAlert("error", "Erreur serveur. Veuillez réessayer plus tard.");
} else {
setError(
error.response.data.message ||
"Une erreur s'est produite lors de la connexion"
);
showAlert("error", error.response.data.message || "Une erreur s'est produite lors de la connexion");
}
} else if (error.request) {
setError(
"Impossible de joindre le serveur. Vérifiez votre connexion internet."
);
showAlert("error", "Impossible de joindre le serveur. Vérifiez votre connexion internet.");
} else {
setError("Une erreur s'est produite. Veuillez réessayer.");
showAlert("error", "Une erreur s'est produite. Veuillez réessayer.");
}
}
};
// Configuration des alertes selon le type
const alertConfig = {
success: {
bgColor: "bg-green-50",
borderColor: "border-green-200",
textColor: "text-green-700",
icon: <CheckCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-green-500" />
},
error: {
bgColor: "bg-red-50",
borderColor: "border-red-200",
textColor: "text-red-700",
icon: <AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-red-500" />
},
info: {
bgColor: "bg-blue-50",
borderColor: "border-blue-200",
textColor: "text-blue-700",
icon: <Info className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-blue-500" />
},
warning: {
bgColor: "bg-yellow-50",
borderColor: "border-yellow-200",
textColor: "text-yellow-700",
icon: <AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0 text-yellow-500" />
}
};
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="md:w-96 w-full bg-white rounded-lg shadow-md p-6 mx-auto">
@ -74,11 +124,19 @@ function Login() {
Connexion
</h2>
{/* Message d'erreur */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-md flex items-start">
<AlertCircle className="h-5 w-5 mr-2 mt-0.5 flex-shrink-0" />
<span className="text-sm">{error}</span>
{/* Système d'alertes */}
{alert.show && (
<div className={`mb-4 p-3 ${alertConfig[alert.type].bgColor} border ${alertConfig[alert.type].borderColor} ${alertConfig[alert.type].textColor} rounded-md flex items-start justify-between`}>
<div className="flex items-start">
{alertConfig[alert.type].icon}
<span className="text-sm">{alert.message}</span>
</div>
<button
onClick={() => setAlert({ ...alert, show: false })}
className="ml-2 p-1 hover:bg-gray-200 rounded-full"
>
<X size={14} />
</button>
</div>
)}