From 0e7066808b6c4e1176c15b5202b5417e70561132 Mon Sep 17 00:00:00 2001 From: kmitresse Date: Fri, 22 Mar 2024 10:53:13 +0100 Subject: [PATCH] draft: dev-web - forgotten password token --- S2/DevWeb/Projet/mysql/init.sql | 14 ++ S2/DevWeb/Projet/pom.xml | 7 +- .../java/uppa/project/dao/GameDAOFactory.java | 4 + .../jpa/DAO_JPA_RecoveryPasswordToken.java | 78 +++++++++++ .../project/dao/jpa/Game_JPA_DAO_Factory.java | 6 + .../project/pojo/RecoveryPasswordToken.java | 123 ++++++++++++++++++ .../servlet/ForgottenPasswordServlet.java | 105 ++++++++++++++- .../WEB-INF/static/js/forgotten-password.js | 26 ++++ .../WEB-INF/views/forgotten-password.jsp | 15 ++- 9 files changed, 375 insertions(+), 3 deletions(-) create mode 100644 S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/DAO_JPA_RecoveryPasswordToken.java create mode 100644 S2/DevWeb/Projet/src/main/java/uppa/project/pojo/RecoveryPasswordToken.java create mode 100644 S2/DevWeb/Projet/src/main/webapp/WEB-INF/static/js/forgotten-password.js diff --git a/S2/DevWeb/Projet/mysql/init.sql b/S2/DevWeb/Projet/mysql/init.sql index 1edbdad..8a9ffae 100644 --- a/S2/DevWeb/Projet/mysql/init.sql +++ b/S2/DevWeb/Projet/mysql/init.sql @@ -43,3 +43,17 @@ CREATE TABLE IF NOT EXISTS player FOREIGN KEY (game_id) REFERENCES game (id), FOREIGN KEY (user_id) REFERENCES `user` (id) ); + +CREATE TABLE IF NOT EXISTS recovery_password_token( + id INT NOT NULL AUTO_INCREMENT, + email VARCHAR(255) NOT NULL, + token VARCHAR(255) NOT NULL, + expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +DELIMITER // +CREATE TRIGGER expires_at BEFORE INSERT ON recovery_password_token FOR EACH ROW +BEGIN + SET NEW.`expires_at` = TIMESTAMPADD(MINUTE, 15, CURRENT_TIMESTAMP); +END;// diff --git a/S2/DevWeb/Projet/pom.xml b/S2/DevWeb/Projet/pom.xml index 391f972..b31c033 100644 --- a/S2/DevWeb/Projet/pom.xml +++ b/S2/DevWeb/Projet/pom.xml @@ -51,6 +51,11 @@ ${junit.version} test + + com.sun.mail + javax.mail + 1.6.2 + @@ -61,4 +66,4 @@ 3.3.2 - \ No newline at end of file + diff --git a/S2/DevWeb/Projet/src/main/java/uppa/project/dao/GameDAOFactory.java b/S2/DevWeb/Projet/src/main/java/uppa/project/dao/GameDAOFactory.java index f667611..249bc39 100644 --- a/S2/DevWeb/Projet/src/main/java/uppa/project/dao/GameDAOFactory.java +++ b/S2/DevWeb/Projet/src/main/java/uppa/project/dao/GameDAOFactory.java @@ -8,6 +8,7 @@ package uppa.project.dao; import uppa.project.pojo.Game; import uppa.project.pojo.Player; +import uppa.project.pojo.RecoveryPasswordToken; import uppa.project.pojo.User; /** @@ -21,6 +22,7 @@ public abstract class GameDAOFactory { protected DAO daoUser = null; protected DAO daoGame = null; protected DAO daoPlayer = null; + protected DAO daoRecoveryPasswordToken = null; /** * @return le DAO pour la classe/table User @@ -43,4 +45,6 @@ public abstract class GameDAOFactory { */ public abstract DAO getDAOPlayer() throws DAOException; + public abstract DAO getDAORecoveryPasswordToken() throws DAOException; + } diff --git a/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/DAO_JPA_RecoveryPasswordToken.java b/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/DAO_JPA_RecoveryPasswordToken.java new file mode 100644 index 0000000..3667c0b --- /dev/null +++ b/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/DAO_JPA_RecoveryPasswordToken.java @@ -0,0 +1,78 @@ +/* + * DAO_JPA_RecoveryPasswordToken.java, 20/03/2024 + * UPPA M1 TI 2023-2024 + * Pas de copyright, aucun droits + */ + +package uppa.project.dao.jpa; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import java.math.BigDecimal; +import java.util.List; +import uppa.project.dao.DAO; +import uppa.project.dao.DAOException; +import uppa.project.pojo.RecoveryPasswordToken; +import uppa.project.provider.EntityManagerProvider; + +/** + * DAO pour les utilisateurs + * + * @author Kévin Mitresse + * @author Lucàs Vabre + * @see RecoveryPasswordToken + * @see DAO + */ +public class DAO_JPA_RecoveryPasswordToken extends DAO { + + /** + * Gestionnaire d'entités + */ + private final EntityManager entityManager; + + public DAO_JPA_RecoveryPasswordToken() throws DAOException { + this.entityManager = EntityManagerProvider.getInstance(); + } + + @Override + public RecoveryPasswordToken findById(int id) throws DAOException { + RecoveryPasswordToken result = entityManager.find(RecoveryPasswordToken.class, new BigDecimal(id)); + entityManager.flush(); + return result; + } + + public RecoveryPasswordToken[] findByField(String field, String value) throws DAOException { + String sqlQuery = String.format("SELECT r FROM RecoveryPasswordToken r WHERE r.%s = (:val)", field); + + TypedQuery query = entityManager.createQuery(sqlQuery, RecoveryPasswordToken.class); + query.setParameter("val", value); + List results = query.getResultList(); + return results.toArray(new RecoveryPasswordToken[0]); + } + + @Override + public RecoveryPasswordToken[] findAll() throws DAOException { + TypedQuery query = entityManager.createQuery("SELECT r FROM RecoveryPasswordToken r", RecoveryPasswordToken.class); + List results = query.getResultList(); + return results.toArray(new RecoveryPasswordToken[0]); + } + + @Override + public void create(RecoveryPasswordToken data) throws DAOException { + update(data); + } + + @Override + public void update(RecoveryPasswordToken data) throws DAOException { + entityManager.getTransaction().begin(); + entityManager.merge(data); + entityManager.getTransaction().commit(); + } + + @Override + public void delete(RecoveryPasswordToken data) throws DAOException { + entityManager.getTransaction().begin(); + entityManager.remove(data); + entityManager.getTransaction().commit(); + } +} diff --git a/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/Game_JPA_DAO_Factory.java b/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/Game_JPA_DAO_Factory.java index 8ff49a0..3e80654 100644 --- a/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/Game_JPA_DAO_Factory.java +++ b/S2/DevWeb/Projet/src/main/java/uppa/project/dao/jpa/Game_JPA_DAO_Factory.java @@ -36,4 +36,10 @@ public class Game_JPA_DAO_Factory extends GameDAOFactory { if (daoPlayer == null) daoPlayer = new DAO_JPA_Player(); return daoPlayer; } + + @Override + public DAO getDAORecoveryPasswordToken() throws DAOException { + if (daoRecoveryPasswordToken == null) daoRecoveryPasswordToken= new DAO_JPA_RecoveryPasswordToken(); + return daoRecoveryPasswordToken; + } } diff --git a/S2/DevWeb/Projet/src/main/java/uppa/project/pojo/RecoveryPasswordToken.java b/S2/DevWeb/Projet/src/main/java/uppa/project/pojo/RecoveryPasswordToken.java new file mode 100644 index 0000000..c759368 --- /dev/null +++ b/S2/DevWeb/Projet/src/main/java/uppa/project/pojo/RecoveryPasswordToken.java @@ -0,0 +1,123 @@ +package uppa.project.pojo; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import java.util.Date; + +/** + * Représentation d'un token de réinitialisation de mot de passe + * + * @author Kevin Mitressé + * @author Lucàs Vabre + */ +@Entity +@Table(name = "recovery_password_token") +public class RecoveryPasswordToken { + + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private int id; + @Column(name = "token") + private String token; + @Column(name = "email") + private String email; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "expires_at") + private Date expiresAt; + + public RecoveryPasswordToken() { + } + + /** + * Constructeur + * @param token + * @param email + */ + public RecoveryPasswordToken(String token, String email) { + this.token = token; + this.email = email; + } + + /** + * Constructeur depuis la base de données + * @param id + * @param token + * @param email + */ + public RecoveryPasswordToken(int id, String token, String email) { + this.id = id; + this.token = token; + this.email = email; + } + + /** + * Récupère l'id de l'instance + * + * @return l'id + */ + public int getId() { + return id; + } + + /** + * Récupère le token + * + * @return le token + */ + public String getToken() { + return token; + } + + /** + * Définit le token + * + * @param token + */ + public void setToken(String token) { + this.token = token; + } + + /** + * Récupère l'email associé au token + * + * @return l'email associé au token + */ + public String getEmail() { + return email; + } + + /** + * Définit l'email associé au token + * + * @param email + */ + public void setEmail(String email) { + this.email = email; + } + + /** + * Récupère la date d'expiration du token + * + * @return la date d'expiration du token + */ + public Date getExpiresAt() { + return expiresAt; + } + + /** + * Définit la date d'expiration du token + * + * @param expiresAt + */ + public void setExpiresAt(Date expiresAt) { + this.expiresAt = expiresAt; + } +} diff --git a/S2/DevWeb/Projet/src/main/java/uppa/project/servlet/ForgottenPasswordServlet.java b/S2/DevWeb/Projet/src/main/java/uppa/project/servlet/ForgottenPasswordServlet.java index 0b25c38..c4fcb92 100644 --- a/S2/DevWeb/Projet/src/main/java/uppa/project/servlet/ForgottenPasswordServlet.java +++ b/S2/DevWeb/Projet/src/main/java/uppa/project/servlet/ForgottenPasswordServlet.java @@ -12,6 +12,17 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.UUID; +import javax.mail.Message; +import uppa.project.dao.DAO; +import uppa.project.dao.DAOException; +import uppa.project.dao.jpa.DAO_JPA_User; +import uppa.project.dao.jpa.Game_JPA_DAO_Factory; +import uppa.project.pojo.RecoveryPasswordToken; +import uppa.project.pojo.User; +import java.util.Properties; +import javax.mail.*; +import javax.mail.internet.*; @WebServlet(name = "forgottenPasswordServlet", value = "/forgotten-password") public class ForgottenPasswordServlet extends HttpServlet { @@ -28,6 +39,98 @@ public class ForgottenPasswordServlet extends HttpServlet { request.getRequestDispatcher("/WEB-INF/views/forgotten-password.jsp").forward(request, response); } + /** + * Gestion de la réinitialisation de mot de passe + * + * @param request + * @param response + * @throws IOException + */ + public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + String email = request.getParameter("email"); + User user = getUserByEmail(email); + if (user == null) { + response.sendRedirect(request.getContextPath() + "/forgotten-password?error=1"); + } else { + String token = UUID.randomUUID().toString(); + System.out.println("Token : " + token); + System.out.println("1"); + RecoveryPasswordToken recoveryPasswordToken = new RecoveryPasswordToken(token, user.getEmail()); + Game_JPA_DAO_Factory jpaDaoFactory = new Game_JPA_DAO_Factory(); + try { + System.out.println("2"); + DAO daoJpaRecoveryPasswordToken = jpaDaoFactory.getDAORecoveryPasswordToken(); + System.out.println("3"); + daoJpaRecoveryPasswordToken.create(recoveryPasswordToken); + } catch (DAOException e) { + throw new RuntimeException(e); + } + sendRecoveryEmail(email, token); + response.sendRedirect(request.getContextPath() + "/forgotten-password?success=200"); + } + + } + + /** + * Envoi d'un e-mail de réinitialisation de mot de passe + * + * @param email + * @param token + */ + public void sendRecoveryEmail(String email, String token) { + + String host = "smtp.gmail.com"; + String port = "587"; + String username = System.getenv("MAIL_USERNAME"); + String password = System.getenv("MAIL_PASSWORD"); + + Properties props = new Properties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.host", host); + props.put("mail.smtp.port", port); + + Session session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(username, password); + } + }); + + try { + // Création du message + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(username)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(email)); + message.setSubject("Réinitialisation de votre mot de passe"); + message.setText("Bonjour,\n\n" + + "Vous avez demandé la réinitialisation de votre mot de passe.\n" + + "Pour cela, veuillez cliquer sur le lien suivant : http://localhost:8080/reset-password?token=" + token + "\n\n" + + "Cordialement,\n" + + "L'équipe CardRush"); + // Envoi du message + Transport.send(message); + + System.out.println("E-mail envoyé avec succès à : " + email); + } catch (MessagingException e) { + throw new RuntimeException("Erreur lors de l'envoi de l'e-mail", e); + } + } + + public static User getUserByEmail(String email){ + try { + DAO_JPA_User daoJpaUser = new DAO_JPA_User(); + User[] users = daoJpaUser.findByField("email", email); + if (users.length == 0) { + return null; + } + return users[0]; + } catch (DAOException e) { + throw new RuntimeException(e); + } + + } + public void destroy() { } -} \ No newline at end of file +} diff --git a/S2/DevWeb/Projet/src/main/webapp/WEB-INF/static/js/forgotten-password.js b/S2/DevWeb/Projet/src/main/webapp/WEB-INF/static/js/forgotten-password.js new file mode 100644 index 0000000..e9bac97 --- /dev/null +++ b/S2/DevWeb/Projet/src/main/webapp/WEB-INF/static/js/forgotten-password.js @@ -0,0 +1,26 @@ +const forgottenPasswordForm = document.getElementById("forgottenPasswordForm"); + +forgottenPasswordForm.addEventListener("submit", (event) => { + event.preventDefault(); + //Recuperer les données du formulaire + const formData = new FormData(forgottenPasswordForm); + const data = {}; + formData.forEach((value, key) => data[key] = value); + //Recuperer l'URL de l'action et la methode + const action = forgottenPasswordForm.getAttribute("action"); + const method = forgottenPasswordForm.getAttribute("method"); + //Redirection vers le servlet ForgottenPasswordServlet + fetch(action, { + headers: {"Content-Type": "application/json"}, + body: JSON.stringify(data), + method, + }) + .then(res => res.json()) + .then(data => { + console.log(data); + // if (data.status === 200) window.location.href = data.redirect; + }) + .catch(error => console.error("Error:", error)) + +}); + diff --git a/S2/DevWeb/Projet/src/main/webapp/WEB-INF/views/forgotten-password.jsp b/S2/DevWeb/Projet/src/main/webapp/WEB-INF/views/forgotten-password.jsp index 4e852f2..7187490 100644 --- a/S2/DevWeb/Projet/src/main/webapp/WEB-INF/views/forgotten-password.jsp +++ b/S2/DevWeb/Projet/src/main/webapp/WEB-INF/views/forgotten-password.jsp @@ -11,6 +11,19 @@ Forgotten Password - +
+

Forgotten Password

+

Entrer votre email pour recevoir un lien de récupération

+
+ + + +
+ <%if(request.getParameter("error") != null){%> +

L'adresse mail insérée est incorrecte

+ <%} else if (request.getParameter("success") != null) {%> +

Un email vous a été envoyé

+ <%}%> +