diff --git a/Back-end/keystore.jceks b/Back-end/keystore.jceks new file mode 100644 index 0000000..1f4ee58 Binary files /dev/null and b/Back-end/keystore.jceks differ diff --git a/Back-end/keystore.jceks.old b/Back-end/keystore.jceks.old new file mode 100644 index 0000000..f00730b Binary files /dev/null and b/Back-end/keystore.jceks.old differ diff --git a/Back-end/mvnw b/Back-end/mvnw old mode 100644 new mode 100755 diff --git a/Back-end/pom.xml b/Back-end/pom.xml index 15670a8..5d0ce82 100644 --- a/Back-end/pom.xml +++ b/Back-end/pom.xml @@ -82,6 +82,18 @@ ${junit-jupiter.version} test + + at.favre.lib + bcrypt + 0.9.0 + + + io.vertx + vertx-auth-jwt + 4.5.13 + + + diff --git a/Back-end/src/main/java/com/example/starter/AuthHandler.java b/Back-end/src/main/java/com/example/starter/AuthHandler.java new file mode 100644 index 0000000..2052b71 --- /dev/null +++ b/Back-end/src/main/java/com/example/starter/AuthHandler.java @@ -0,0 +1,113 @@ +package com.example.starter; + +import io.vertx.core.json.JsonObject; +import io.vertx.ext.web.RoutingContext; +import at.favre.lib.crypto.bcrypt.BCrypt; +import io.vertx.ext.auth.jwt.JWTAuth; +import com.example.starter.auth.JwtAuthProvider; +import io.vertx.sqlclient.Tuple; + +public class AuthHandler { + private final DatabaseService databaseService; + private final JWTAuth jwtAuth; + + public AuthHandler(DatabaseService databaseService, JWTAuth jwtAuth) { + this.databaseService = databaseService; + this.jwtAuth = jwtAuth; + } + + public void handleSignup(RoutingContext context) { + JsonObject body = context.body().asJsonObject(); + + if (body == null) { + context.response() + .setStatusCode(400) + .end(new JsonObject().put("error", "Requête invalide").encode()); + return; + } + + String name = body.getString("name"); + String surname = body.getString("surname"); + String email = body.getString("email"); + String gender = body.getString("gender"); + String password = body.getString("password"); + + if (name == null || surname == null || email == null || gender == null || password == null) { + context.response() + .setStatusCode(400) + .end(new JsonObject().put("error", "Tous les champs sont requis").encode()); + return; + } + + String hashedPassword = BCrypt.withDefaults().hashToString(12, password.toCharArray()); + + databaseService.pool + .preparedQuery("INSERT INTO users (name, surname, email, gender, password) VALUES (?, ?, ?, ?, ?)") + .execute(Tuple.of(name, surname, email, gender, hashedPassword)) + .onSuccess(result -> { + context.response() + .setStatusCode(201) + .end(new JsonObject().put("message", "Utilisateur inscrit avec succès").encode()); + }) + .onFailure(err -> { + System.err.println("Erreur d'inscription : " + err.getMessage()); + context.response() + .setStatusCode(500) + .end(new JsonObject().put("error", "Erreur d'inscription").encode()); + }); + } + + public void handleLogin(RoutingContext context) { + JsonObject body = context.body().asJsonObject(); + + if (body == null) { + context.response() + .setStatusCode(400) + .end(new JsonObject().put("error", "Requête invalide").encode()); + return; + } + + String email = body.getString("email"); + String password = body.getString("password"); + + if (email == null || password == null) { + context.response() + .setStatusCode(400) + .end(new JsonObject().put("error", "Email et mot de passe requis").encode()); + return; + } + + databaseService.pool + .preparedQuery("SELECT password 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; + } + + String storedHashedPassword = result.iterator().next().getString("password"); + BCrypt.Result verification = BCrypt.verifyer().verify(password.toCharArray(), storedHashedPassword); + + if (verification.verified) { + JsonObject claims = new JsonObject().put("sub", email).put("role", "user"); + 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()); + }); + } +} diff --git a/Back-end/src/main/java/com/example/starter/DatabaseService.java b/Back-end/src/main/java/com/example/starter/DatabaseService.java index 1985cc4..f4e05d5 100644 --- a/Back-end/src/main/java/com/example/starter/DatabaseService.java +++ b/Back-end/src/main/java/com/example/starter/DatabaseService.java @@ -3,6 +3,8 @@ package com.example.starter; import io.vertx.core.Vertx; import io.vertx.jdbcclient.JDBCConnectOptions; import io.vertx.jdbcclient.JDBCPool; +import io.vertx.ext.auth.jwt.JWTAuth; +import io.vertx.ext.auth.jwt.JWTAuthOptions; import io.vertx.sqlclient.PoolOptions; public class DatabaseService { @@ -11,7 +13,7 @@ public class DatabaseService { public DatabaseService(Vertx vertx) { pool = JDBCPool.pool(vertx, new JDBCConnectOptions() - .setJdbcUrl("jdbc:postgresql://localhost:5432/postgres?useUnicode=true&characterEncoding=UTF-8") // URL de la base de données + .setJdbcUrl("jdbc:postgresql://localhost:5432/users?useUnicode=true&characterEncoding=UTF-8") //Url de la bdd .setUser("postgres") // Nom d'utilisateur PostgreSQL .setPassword("admin"), // Mot de passe PostgreSQL new PoolOptions() diff --git a/Back-end/src/main/java/com/example/starter/JwtAuthProvider.java b/Back-end/src/main/java/com/example/starter/JwtAuthProvider.java new file mode 100644 index 0000000..01d26d9 --- /dev/null +++ b/Back-end/src/main/java/com/example/starter/JwtAuthProvider.java @@ -0,0 +1,18 @@ +package com.example.starter.auth; + +import io.vertx.core.Vertx; +import io.vertx.ext.auth.jwt.JWTAuth; +import io.vertx.ext.auth.jwt.JWTAuthOptions; +import io.vertx.ext.auth.KeyStoreOptions; +import com.example.starter.auth.JwtAuthProvider; + + +public class JwtAuthProvider { + + public static JWTAuth createJwtAuth(Vertx vertx) { + return JWTAuth.create(vertx, new JWTAuthOptions() + .setKeyStore(new KeyStoreOptions() + .setPath("keystore.jceks") + .setPassword("secret"))); + } +} diff --git a/Back-end/src/main/java/com/example/starter/MainVerticle.java b/Back-end/src/main/java/com/example/starter/MainVerticle.java index 32c09dd..99ad4e4 100644 --- a/Back-end/src/main/java/com/example/starter/MainVerticle.java +++ b/Back-end/src/main/java/com/example/starter/MainVerticle.java @@ -1,16 +1,27 @@ package com.example.starter; -import io.vertx.ext.web.handler.BodyHandler; -import io.vertx.ext.web.handler.CorsHandler; -import io.vertx.core.http.HttpMethod; import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; - +import io.vertx.core.http.HttpMethod; import io.vertx.ext.web.Router; +import io.vertx.ext.web.handler.BodyHandler; +import io.vertx.ext.web.handler.CorsHandler; +import io.vertx.ext.auth.jwt.JWTAuth; +import com.example.starter.auth.JwtAuthProvider; +import io.vertx.ext.web.handler.JWTAuthHandler; + public class MainVerticle extends AbstractVerticle { - private DatabaseService databaseService; + private DatabaseService databaseService; + private Router router; + @Override + public void start(Promise startPromise) throws Exception { + databaseService = new DatabaseService(vertx); + + // Initialisation du fournisseur JWT + JWTAuth jwtAuth = JwtAuthProvider.createJwtAuth(vertx); + @Override public void start(Promise startPromise) throws Exception { databaseService = new DatabaseService(vertx); @@ -35,17 +46,35 @@ public class MainVerticle extends AbstractVerticle { router.post("/addObject").handler(setObjects::newObject); router.get("/getRange").handler(queryWeather::getRangeData); router.post("/modifRangeData").handler(setWeatherData::setRangeData); + // Routes d'authentification + router.post("/signup").handler(authHandler::handleSignup); + router.post("/login").handler(authHandler::handleLogin); - vertx.createHttpServer() - .requestHandler(router) - .listen(8888) - .onSuccess(server -> { - System.out.println("HTTP server started on port " + server.actualPort()); - startPromise.complete(); - }) - .onFailure(throwable -> { - throwable.printStackTrace(); - startPromise.fail(throwable); - }); - } -} \ No newline at end of file + + // Protéger toutes les routes commençant par "/api/" + router.route("/api/*").handler(JWTAuthHandler.create(jwtAuth)); + + + // Initialisation des handlers de requêtes + QueryObjects queryObjects = new QueryObjects(databaseService); + QueryWeatherData queryWeather = new QueryWeatherData(databaseService); + SetObjects setObjects = new SetObjects(databaseService); + AuthHandler authHandler = new AuthHandler(databaseService, jwtAuth); + + + + // Création du serveur HTTP + vertx.createHttpServer() + .requestHandler(router) + .listen(8888) + .onSuccess(server -> { + System.out.println("HTTP server started on port " + server.actualPort()); + startPromise.complete(); + }) + .onFailure(throwable -> { + throwable.printStackTrace(); + startPromise.fail(throwable); + }); + } +} +} diff --git a/Front-end/package-lock.json b/Front-end/package-lock.json index 20a5b55..df3870c 100644 --- a/Front-end/package-lock.json +++ b/Front-end/package-lock.json @@ -1975,7 +1975,6 @@ "version": "1.8.4", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", - "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", diff --git a/Front-end/public/images/snow.jpg b/Front-end/public/images/snow.jpg new file mode 100644 index 0000000..a9e9abd Binary files /dev/null and b/Front-end/public/images/snow.jpg differ diff --git a/Front-end/src/App.jsx b/Front-end/src/App.jsx index d76005c..ea42a87 100644 --- a/Front-end/src/App.jsx +++ b/Front-end/src/App.jsx @@ -1,4 +1,5 @@ -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { AuthProvider } from './AuthContext.jsx'; import Home from "./pages/Home.jsx"; import About from "./pages/About.jsx"; import Gestion from "./pages/Gestion/Gestion.jsx"; @@ -6,22 +7,30 @@ import Header from "./components/Header.jsx"; import ObjectManagement from "./pages/Gestion/ObjectManagement.jsx"; import Objet from "./pages/Gestion/Objet.jsx"; import AddObject from "./pages/Gestion/AddObject.jsx"; -function App() { +import Signup from './pages/Signup.jsx'; +import Login from './pages/Login.jsx'; +import Settings from './pages/Settings.jsx'; +function App() { return ( - -
-
- - } /> - } /> - } /> - } /> - } /> - } /> - -
-
+ {/* Enveloppe l'application avec AuthProvider */} + +
+
+ + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + +
+
+
); } diff --git a/Front-end/src/AuthContext.jsx b/Front-end/src/AuthContext.jsx new file mode 100644 index 0000000..ca5abc9 --- /dev/null +++ b/Front-end/src/AuthContext.jsx @@ -0,0 +1,42 @@ +// src/AuthContext.js +import React, { createContext, useContext, useState, useEffect } from "react"; + +// Créer le contexte +const AuthContext = createContext(); + +// Hook pour accéder facilement au contexte +export const useAuth = () => useContext(AuthContext); + +// Fournisseur de contexte qui gère l'état du token +export const AuthProvider = ({ children }) => { + const [token, setToken] = useState(localStorage.getItem("token")); + + // Met à jour le token lorsque localStorage change + useEffect(() => { + const handleStorageChange = () => { + setToken(localStorage.getItem("token")); + }; + + window.addEventListener("storage", handleStorageChange); + + return () => { + window.removeEventListener("storage", handleStorageChange); + }; + }, []); + + const login = (newToken) => { + localStorage.setItem("token", newToken); + setToken(newToken); + }; + + const logout = () => { + localStorage.removeItem("token"); + setToken(null); + }; + + return ( + + {children} + + ); +}; diff --git a/Front-end/src/components/Header.jsx b/Front-end/src/components/Header.jsx index ca0873d..9bbd666 100644 --- a/Front-end/src/components/Header.jsx +++ b/Front-end/src/components/Header.jsx @@ -1,70 +1,61 @@ -import React, { useState } from "react"; -import { LogIn, UserPlus, Menu, X } from "lucide-react"; +import React, { useState, useEffect } from "react"; +import { LogIn, UserPlus, LogOut, Settings } from "lucide-react"; +import { Link } from "react-router-dom"; +import { useAuth } from "../AuthContext"; function Header() { - const [isMenuOpen, setIsMenuOpen] = useState(false); + const { token, logout } = useAuth(); return (
-
-

VigiMétéo

- - - - - -
- - +
+
+

VigiMétéo

+ +
+ {token ? ( + <> + + + + + + + ) : ( + <> + + + Connexion + + + + Inscription + + + )} +
); } -export default Header; \ No newline at end of file +export default Header; diff --git a/Front-end/src/pages/AdminDashboard.jsx b/Front-end/src/pages/AdminDashboard.jsx new file mode 100644 index 0000000..403da86 --- /dev/null +++ b/Front-end/src/pages/AdminDashboard.jsx @@ -0,0 +1,44 @@ +import React, { useState } from "react"; +import { Calendar, Settings, LayoutDashboard } from "lucide-react"; + +function AdminDashboard() { + + const [activeTab, setActiveTab] = useState('dashboard'); + + return ( +
+ {/* SideMenu */} +
+

+ Administration +

+ +

Test

+
+
+ ); +} + +export default AdminDashboard; diff --git a/Front-end/src/pages/Home.jsx b/Front-end/src/pages/Home.jsx index cb63aa6..885b92b 100644 --- a/Front-end/src/pages/Home.jsx +++ b/Front-end/src/pages/Home.jsx @@ -1,115 +1,123 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Search, MapPin, Calendar, Bus, ArrowRight, LogIn, UserPlus } from 'lucide-react'; -import { useEffect, useState } from 'react'; -import axios from 'axios'; +import { useAuth } from "../AuthContext"; + function Home() { - const [searchQuery, setSearchQuery] = useState(''); - const [activeFilter, setActiveFilter] = useState('all'); - const [name, setName] = useState([]); - return ( -
-
-
-

- Bienvenue dans ta ville intelligente. -

-

- Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit. + const [searchQuery, setSearchQuery] = useState(''); + const [activeFilter, setActiveFilter] = useState('all'); + const [name, setName] = useState([]); + const { token, logout } = useAuth(); + + return ( +

+
+
+

+ Bienvenue dans ta ville intelligente.

+ {token ? ( + <>

Tu es connecté

+ + ):( +

Non connecté

+ )} +

+ Découvrez tout ce que votre ville a à offrir - des événements locaux aux horaires de transport, le tout en un seul endroit. +

+
+ +
+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + + + +
+
+ + {/* Features Grid */} +
+
+
+ +
+

Points d'intérêt

+

+ Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de restaurants, de parcs ou de lieux culturels.

+ + Explorer les lieux +
- -
-
- - setSearchQuery(e.target.value)} - /> -
-
- - - - + +
+
+
+

Evenements locaux

+

+ Restez informé des derniers événements, festivals et rassemblements communautaires dans votre région. +

+ + Voir les événements +
- - {/* Features Grid */} -
-
-
- -
-

Points d'intérêt

-

- Découvrez les meilleurs endroits de votre ville, qu'il s'agisse de restaurants, de parcs ou de lieux culturels. -

- - Explorer les lieux - -
- -
-
- -
-

Evenements locaux

-

- Restez informé des derniers événements, festivals et rassemblements communautaires dans votre région. -

- - Voir les événements - -
- -
-
- -
-

Transports publics

-

- Accédez en temps réel aux horaires et aux itinéraires des bus, des trains et des autres transports publics. -

- - Vérifier les horaires - + +
+
+
+

Transports publics

+

+ Accédez en temps réel aux horaires et aux itinéraires des bus, des trains et des autres transports publics. +

+ + Vérifier les horaires +
- ); +
+ ); } -export default Home; \ No newline at end of file +export default Home; + \ No newline at end of file diff --git a/Front-end/src/pages/Login.jsx b/Front-end/src/pages/Login.jsx new file mode 100644 index 0000000..5aab8e4 --- /dev/null +++ b/Front-end/src/pages/Login.jsx @@ -0,0 +1,121 @@ +import React, { useState } from 'react'; +import { Mail, Lock } from 'lucide-react'; +import { useNavigate, Link } from 'react-router-dom'; +import axios from 'axios'; // Assurez-vous d'avoir axios importé +import { useAuth } from '../AuthContext'; + +function Login() { + const [formData, setFormData] = useState({ + email: '', + password: '' + }); + const { login } = useAuth(); // Utilisation du hook useAuth pour accéder à la fonction login + const navigate = useNavigate(); // Initialisation de useNavigate + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + try { + const response = await fetch("http://localhost:8888/login", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + // Récupérer les données JSON de la réponse + const data = await response.json(); + + // Vérifiez que la réponse contient bien un token + if (data.token) { + // Appel de la fonction login du contexte pour stocker le token + login(data.token); + + // Rediriger vers la page d'accueil après la connexion + navigate('/'); + } else { + console.error('Token manquant dans la réponse'); + } + } catch (error) { + console.error('Erreur lors de la connexion', error); + } + }; + return ( +
+
+

Connexion

+
+ {/* Email */} +
+ +
+
+ +
+ +
+
+ + {/* Mot de passe */} +
+ +
+
+ +
+ +
+
+ + {/* Bouton de connexion */} +
+ +
+ + {/* Lien vers la page d'inscription */} +
+

+ Vous n'avez pas de compte ? + Inscrivez-vous ici +

+
+
+
+
+ ); +} + +export default Login; diff --git a/Front-end/src/pages/Settings.jsx b/Front-end/src/pages/Settings.jsx new file mode 100644 index 0000000..b749a20 --- /dev/null +++ b/Front-end/src/pages/Settings.jsx @@ -0,0 +1,161 @@ +import React, { useState } from 'react'; +import { Mail, User, Lock } from 'lucide-react'; +import { useNavigate, Link} from 'react-router-dom'; // Importation du hook useNavigate + +function Settings() { + const [formData, setFormData] = useState({ + name: '', + surname: '', + email: '', + gender: '', + password: '', + confirmPassword: '' + }); + const navigate = useNavigate(); // Initialisation de useNavigate + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (formData.password !== formData.confirmPassword) { + alert("Les mots de passe ne correspondent pas !"); + return; + } + + try { + const response = await fetch("http://localhost:8888/settings", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Erreur lors de la modification"); + } + + alert("Modification réussie !"); + + // Redirection vers la page d'accueil après une inscription réussie + navigate("/home"); // Remplace "/home" par l'URL de ta page d'accueil + } catch (error) { + alert(error.message); + } + }; + + return ( +
+
+

Settings

+
+ {/* (Formulaire changement Email, Mot de passe) */} + + + + {/* Email */} +
+ +
+
+ +
+ +
+
+ + {/* Mot de passe */} +
+ +
+
+ +
+ +
+
+ + {/* nouveau mot de passe */} +
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+ +
+
+ {/* Bouton d'inscription */} +
+ +
+
+ +
+
+ ); +} + +export default Settings; diff --git a/Front-end/src/pages/Signup.jsx b/Front-end/src/pages/Signup.jsx new file mode 100644 index 0000000..595e7ec --- /dev/null +++ b/Front-end/src/pages/Signup.jsx @@ -0,0 +1,217 @@ +import React, { useState } from 'react'; +import { Mail, User, Lock } from 'lucide-react'; +import { useNavigate, Link} from 'react-router-dom'; // Importation du hook useNavigate + +function Signup() { + const [formData, setFormData] = useState({ + name: '', + surname: '', + email: '', + gender: '', + password: '', + confirmPassword: '' + }); + const navigate = useNavigate(); // Initialisation de useNavigate + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if (formData.password !== formData.confirmPassword) { + alert("Les mots de passe ne correspondent pas !"); + return; + } + + try { + const response = await fetch("http://localhost:8888/signup", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); + + const data = await response.json(); + + if (!response.ok) { + throw new Error(data.error || "Erreur lors de l'inscription"); + } + + alert("Inscription réussie !"); + + // Redirection vers la page d'accueil après une inscription réussie + navigate("/home"); // Remplace "/home" par l'URL de ta page d'accueil + } catch (error) { + alert(error.message); + } + }; + + return ( +
+
+

Inscription

+
+ {/* Formulaire (Nom, Prénom, Sexe, Email, Mot de passe) */} +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ + {/* Sexe */} +
+ +
+ + +
+
+ + {/* Email */} +
+ +
+
+ +
+ +
+
+ + {/* Mot de passe */} +
+ +
+
+ +
+ +
+
+ + {/* Confirmer mot de passe */} +
+ +
+
+ +
+ +
+
+ + {/* Bouton d'inscription */} +
+ +
+ + {/*Si il a déjà un compte*/} +
+

+ Vous avez déjà un compte ? + Connectez-vous ici +

+
+
+ +
+
+ ); +} + +export default Signup; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cb77e89 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Projet-Dev-Web-Ing1", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}