Protection route selon role

This commit is contained in:
Charles Mendiburu 2025-04-11 19:51:48 +02:00
parent f868effc9a
commit dcdcdee103
7 changed files with 231 additions and 75 deletions

View File

@ -77,7 +77,7 @@ public class AuthHandler {
} }
databaseService.pool databaseService.pool
.preparedQuery("SELECT password,points FROM users WHERE email = ?") .preparedQuery("SELECT name, surname, password, points FROM users WHERE email = ?") // Ajout de name et surname
.execute(Tuple.of(email)) .execute(Tuple.of(email))
.onSuccess(result -> { .onSuccess(result -> {
if (result.rowCount() == 0) { if (result.rowCount() == 0) {
@ -87,13 +87,20 @@ public class AuthHandler {
return; return;
} }
String storedHashedPassword = result.iterator().next().getString("password"); var row = result.iterator().next();
Integer nbPointsUser = result.iterator().next().getInteger("points"); String storedHashedPassword = row.getString("password");
Integer nbPointsUser = row.getInteger("points");
String name = row.getString("name");
String surname = row.getString("surname");
BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword); BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword);
if (verification.verified) { if (verification.verified) {
JsonObject claims = new JsonObject().put("sub", email); JsonObject claims = new JsonObject()
.put("sub", email)
.put("name", name)
.put("surname", surname);
if (nbPointsUser <= 60) { if (nbPointsUser <= 60) {
claims.put("role", "user"); claims.put("role", "user");
} else if (nbPointsUser <= 100) { } else if (nbPointsUser <= 100) {
@ -103,6 +110,7 @@ public class AuthHandler {
} }
String token = jwtAuth.generateToken(claims); String token = jwtAuth.generateToken(claims);
context.response() context.response()
.setStatusCode(200) .setStatusCode(200)
.end(new JsonObject().put("token", token).encode()); .end(new JsonObject().put("token", token).encode());
@ -118,5 +126,4 @@ public class AuthHandler {
.setStatusCode(500) .setStatusCode(500)
.end(new JsonObject().put("error", "Erreur serveur").encode()); .end(new JsonObject().put("error", "Erreur serveur").encode());
}); });
} }}
}

View File

@ -13,6 +13,7 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1", "@mui/material": "^7.0.1",
"axios": "^1.8.4", "axios": "^1.8.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0", "lucide-react": "^0.427.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-charts": "^3.0.0-beta.57", "react-charts": "^3.0.0-beta.57",
@ -2070,7 +2071,6 @@
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
"integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
} }
@ -2819,6 +2819,71 @@
"is-arrayish": "^0.2.1" "is-arrayish": "^0.2.1"
} }
}, },
"node_modules/es-abstract": {
"version": "1.23.9",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz",
"integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==",
"dev": true,
"dependencies": {
"array-buffer-byte-length": "^1.0.2",
"arraybuffer.prototype.slice": "^1.0.4",
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"data-view-buffer": "^1.0.2",
"data-view-byte-length": "^1.0.2",
"data-view-byte-offset": "^1.0.1",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.0.0",
"es-set-tostringtag": "^2.1.0",
"es-to-primitive": "^1.3.0",
"function.prototype.name": "^1.1.8",
"get-intrinsic": "^1.2.7",
"get-proto": "^1.0.0",
"get-symbol-description": "^1.1.0",
"globalthis": "^1.0.4",
"gopd": "^1.2.0",
"has-property-descriptors": "^1.0.2",
"has-proto": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"internal-slot": "^1.1.0",
"is-array-buffer": "^3.0.5",
"is-callable": "^1.2.7",
"is-data-view": "^1.0.2",
"is-regex": "^1.2.1",
"is-shared-array-buffer": "^1.0.4",
"is-string": "^1.1.1",
"is-typed-array": "^1.1.15",
"is-weakref": "^1.1.0",
"math-intrinsics": "^1.1.0",
"object-inspect": "^1.13.3",
"object-keys": "^1.1.1",
"object.assign": "^4.1.7",
"own-keys": "^1.0.1",
"regexp.prototype.flags": "^1.5.3",
"safe-array-concat": "^1.1.3",
"safe-push-apply": "^1.0.0",
"safe-regex-test": "^1.1.0",
"set-proto": "^1.0.0",
"string.prototype.trim": "^1.2.10",
"string.prototype.trimend": "^1.0.9",
"string.prototype.trimstart": "^1.0.8",
"typed-array-buffer": "^1.0.3",
"typed-array-byte-length": "^1.0.3",
"typed-array-byte-offset": "^1.0.4",
"typed-array-length": "^1.0.7",
"unbox-primitive": "^1.1.0",
"which-typed-array": "^1.1.18"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
@ -3788,12 +3853,63 @@
"integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
"integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
"dev": true,
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"get-intrinsic": "^1.2.6"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": { "node_modules/is-arrayish": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
"integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
"dev": true,
"dependencies": {
"async-function": "^1.0.0",
"call-bound": "^1.0.3",
"get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-bigint": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
"integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
"dev": true,
"dependencies": {
"has-bigints": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@ -4269,6 +4385,14 @@
"node": ">=4.0" "node": ">=4.0"
} }
}, },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"engines": {
"node": ">=18"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",

View File

@ -14,6 +14,7 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/material": "^7.0.1", "@mui/material": "^7.0.1",
"axios": "^1.8.4", "axios": "^1.8.4",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.427.0", "lucide-react": "^0.427.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-charts": "^3.0.0-beta.57", "react-charts": "^3.0.0-beta.57",

View File

@ -14,7 +14,7 @@ import Sidebar from "./pages/Admin/sidebar.jsx";
import User from "./pages/Admin/User.jsx"; import User from "./pages/Admin/User.jsx";
import Dashboard from "./pages/Admin/Dashboard.jsx"; import Dashboard from "./pages/Admin/Dashboard.jsx";
import AdminObjet from "./pages/Admin/AdminObjet.jsx"; import AdminObjet from "./pages/Admin/AdminObjet.jsx";
import ProtectedRoute from './ProtectedRoute.jsx'; // Correction de l'import import ProtectedRoute from "./ProtectedRoute.jsx";
function App() { function App() {
return ( return (
@ -23,19 +23,28 @@ function App() {
<div> <div>
<Header /> <Header />
<Routes> <Routes>
{/* Routes publiques */}
<Route path="/" element={<Home />} /> <Route path="/" element={<Home />} />
<Route path="/about" element={<About />} /> <Route path="/about" element={<About />} />
<Route path="/gestion" element={<ProtectedRoute element={<Gestion />} />} />
<Route path="/gestionObjets" element={<ProtectedRoute element={<ObjectManagement />} />} />
<Route path="/objet" element={<ProtectedRoute element={<Objet />} />} />
<Route path="/signup" element={<Signup />} /> <Route path="/signup" element={<Signup />} />
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/ajouterObjet" element={<ProtectedRoute element={<AddObject />} />} />
<Route path="/settings" element={<Settings />} /> {/* Routes protégées pour tous les utilisateurs connectés */}
<Route path="/sidebar" element={<Sidebar />} /> <Route path="/gestion" element={<ProtectedRoute element={<Gestion />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/user" element={<User />} /> <Route path="/gestionObjets" element={<ProtectedRoute element={<ObjectManagement />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/dashboard" element={<Dashboard />} /> <Route path="/objet" element={<ProtectedRoute element={<Objet />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/adminobjet" element={<ProtectedRoute element={<AdminObjet />} />} />
{/* Routes protégées pour les admins et complexes */}
<Route path="/ajouterObjet" element={<ProtectedRoute element={<AddObject />} allowedRoles={['admin', 'complexe']} />} />
<Route path="/settings" element={<ProtectedRoute element={<Settings />} allowedRoles={['admin', 'complexe']} />} />
{/* Routes protégées pour tous les utilisateurs connectés */}
<Route path="/sidebar" element={<ProtectedRoute element={<Sidebar />} allowedRoles={['admin', 'complexe', 'user']} />} />
<Route path="/user" element={<ProtectedRoute element={<User />} allowedRoles={['admin', 'complexe', 'user']} />} />
{/* Routes protégées pour les admins uniquement */}
<Route path="/dashboard" element={<ProtectedRoute element={<Dashboard />} allowedRoles={['admin']} />} />
<Route path="/adminobjet" element={<ProtectedRoute element={<AdminObjet />} allowedRoles={['admin']} />} />
</Routes> </Routes>
</div> </div>
</Router> </Router>

View File

@ -1,5 +1,6 @@
// src/AuthContext.js
import React, { createContext, useContext, useState, useEffect } from "react"; import React, { createContext, useContext, useState, useEffect } from "react";
import { jwtDecode } from "jwt-decode";
// Créer le contexte // Créer le contexte
const AuthContext = createContext(); const AuthContext = createContext();
@ -7,22 +8,25 @@ const AuthContext = createContext();
// Hook pour accéder facilement au contexte // Hook pour accéder facilement au contexte
export const useAuth = () => useContext(AuthContext); export const useAuth = () => useContext(AuthContext);
// Fournisseur de contexte qui gère l'état du token // Fournisseur de contexte qui gère l'état du token et de l'utilisateur
export const AuthProvider = ({ children }) => { export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem("token")); const [token, setToken] = useState(localStorage.getItem("token"));
const [user, setUser] = useState(null);
// Met à jour le token lorsque localStorage change // Met à jour le token et décode l'utilisateur
useEffect(() => { useEffect(() => {
const handleStorageChange = () => { if (token) {
setToken(localStorage.getItem("token")); try {
}; const decoded = jwtDecode(token);
setUser(decoded);
window.addEventListener("storage", handleStorageChange); } catch (error) {
console.error("Erreur lors du décodage du token:", error);
return () => { setUser(null);
window.removeEventListener("storage", handleStorageChange); }
}; } else {
}, []); setUser(null);
}
}, [token]);
const login = (newToken) => { const login = (newToken) => {
localStorage.setItem("token", newToken); localStorage.setItem("token", newToken);
@ -32,10 +36,11 @@ export const AuthProvider = ({ children }) => {
const logout = () => { const logout = () => {
localStorage.removeItem("token"); localStorage.removeItem("token");
setToken(null); setToken(null);
setUser(null);
}; };
return ( return (
<AuthContext.Provider value={{ token, login, logout }}> <AuthContext.Provider value={{ token, user, login, logout }}>
{children} {children}
</AuthContext.Provider> </AuthContext.Provider>
); );

View File

@ -1,16 +1,22 @@
import { useAuth } from './AuthContext'; // Utilisation du contexte d'authentification import React from "react";
import { Navigate } from 'react-router-dom'; // Utilisation de React Router pour la redirection import { Navigate } from "react-router-dom";
import { useAuth } from "./AuthContext"; // Utilisation du contexte d'authentification
function ProtectedRoute({ element }) { function ProtectedRoute({ element, allowedRoles }) {
const { token } = useAuth(); // Vérifier si un token existe, donc si l'utilisateur est authentifié const { token, user } = useAuth(); // Vérifier si un token existe, donc si l'utilisateur est authentifié
// Si l'utilisateur n'est pas authentifié, redirigez-le vers la page de login // Si l'utilisateur n'est pas authentifié, redirigez-le vers la page de login
if (!token) { if (!token) {
return <Navigate to="/login" />; return <Navigate to="/login" />;
} }
// Si l'utilisateur est authentifié, permettez l'accès à la route // Si l'utilisateur est authentifié mais n'a pas le bon rôle
if (allowedRoles && !allowedRoles.includes(user?.role)) {
return <Navigate to="/" />;
}
// Si l'utilisateur est authentifié et a les bons rôles, permettez l'accès à la route
return element; return element;
} }
export default ProtectedRoute; // Export de la fonction export default ProtectedRoute;

View File

@ -8,6 +8,7 @@ function Home() {
const [activeFilter, setActiveFilter] = useState('all'); const [activeFilter, setActiveFilter] = useState('all');
const [name, setName] = useState([]); const [name, setName] = useState([]);
const { token, logout } = useAuth(); const { token, logout } = useAuth();
const { user } = useAuth();
return ( return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-50">
@ -15,10 +16,13 @@ function Home() {
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-4xl font-bold text-gray-900 mb-4"> <h2 className="text-4xl font-bold text-gray-900 mb-4">
Bienvenue dans ta ville intelligente.</h2> Bienvenue dans ta ville intelligente.</h2>
{token ? ( {user ? (
<><h2>Tu es connecté</h2> <>
<h1>Bienvenue, {user.name} {user.surname}!</h1>
</>):( <p>Email : {user.sub}</p>
<p>Rôle : {user.role}</p>
</>
):(
<h2>Non connecté</h2> <h2>Non connecté</h2>
)} )}
<p className="text-xl text-gray-600 max-w-3xl mx-auto"> <p className="text-xl text-gray-600 max-w-3xl mx-auto">