diff --git a/Back-end/src/main/java/com/example/starter/AuthHandler.java b/Back-end/src/main/java/com/example/starter/AuthHandler.java index 68de2e4..7021f26 100644 --- a/Back-end/src/main/java/com/example/starter/AuthHandler.java +++ b/Back-end/src/main/java/com/example/starter/AuthHandler.java @@ -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()); - }); -}} \ No newline at end of file + 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()); + }); + } +} \ No newline at end of file diff --git a/Front-end/src/components/Header.jsx b/Front-end/src/components/Header.jsx index 54b2a34..f19e99e 100644 --- a/Front-end/src/components/Header.jsx +++ b/Front-end/src/components/Header.jsx @@ -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 (
@@ -90,17 +96,48 @@ function Header() { )} - {(user?.role === "admin")&&( -
  • - setIsMenuOpen(false)} - className="text-gray-600 hover:text-indigo-600" - > - Administration - -
  • + {user?.role === "admin" && ( +
  • + + {showAdminDropdown && ( +
    + setShowAdminDropdown(false)} + > + Dashboard + + setShowAdminDropdown(false)} + > + Gestion des Utilisateurs + + setShowAdminDropdown(false)} + > + Gestion des Objets Connectés + +
    + )} +
  • )} +
  • { + 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 (
    @@ -200,13 +171,13 @@ function Dashboard() { {users.slice(0, 5).map((user) => ( - {user.username} + {user.pseudo} {user.email} - {user.accessLevel} + {user.role} ))} diff --git a/Front-end/src/pages/Admin/User.jsx b/Front-end/src/pages/Admin/User.jsx index 7b61bdc..97e2553 100644 --- a/Front-end/src/pages/Admin/User.jsx +++ b/Front-end/src/pages/Admin/User.jsx @@ -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 ( -
    +
    -

    Gestion des utilisateurs

    -

    Gérez les utilisateurs à partir de ce panneau.

    +

    + User Management +

    +

    Manage users from this panel.

    {/* Formulaire d'ajout d'utilisateur */}
    setName(e.target.value)} + onChange={(e) => setname(e.target.value)} required - className="p-2 border border-gray-300 rounded-md" /> setEmail(e.target.value)} required - className="p-2 border border-gray-300 rounded-md" />
    {/* Tableau des utilisateurs */} - +
    - - - - - + + + + + {users.map((user) => ( - - - + + - - @@ -260,38 +237,32 @@ function User() {
    NomEmailNiveau d'accèsPointsActionsNomEmailNiveau d'accèsPointsActions
    {user.name}{user.email} + {user.name}{user.email} + setPointsInput({ ...pointsInput, [user.id]: e.target.value, }) } - className="w-16 ml-2 p-1 border border-gray-300 rounded-md" /> +
    {/* Tableau des logs */} -
    -

    - Historique des connexions et journal des logs +
    +

    + Historique des connexions et journal des logs{" "}

    - +
    - - - + + + {logs.map((log) => ( - - - + + + ))}
    NomActionTimestampNomActionTimestamp
    {log.name}{log.action}{log.timestamp}{log.name}{log.action}{log.timestamp}
    -

    ); } -export default User; +export default User; \ No newline at end of file diff --git a/Front-end/src/pages/Login.jsx b/Front-end/src/pages/Login.jsx index 8eca535..2a1f16e 100644 --- a/Front-end/src/pages/Login.jsx +++ b/Front-end/src/pages/Login.jsx @@ -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: + }, + error: { + bgColor: "bg-red-50", + borderColor: "border-red-200", + textColor: "text-red-700", + icon: + }, + info: { + bgColor: "bg-blue-50", + borderColor: "border-blue-200", + textColor: "text-blue-700", + icon: + }, + warning: { + bgColor: "bg-yellow-50", + borderColor: "border-yellow-200", + textColor: "text-yellow-700", + icon: + } + }; + return (
    @@ -74,11 +124,19 @@ function Login() { Connexion - {/* Message d'erreur */} - {error && ( -
    - - {error} + {/* Système d'alertes */} + {alert.show && ( +
    +
    + {alertConfig[alert.type].icon} + {alert.message} +
    +
    )} @@ -154,4 +212,4 @@ function Login() { ); } -export default Login; +export default Login; \ No newline at end of file