diff --git a/app/backend/pom.xml b/app/backend/pom.xml index ceb91d70..a0459ca4 100644 --- a/app/backend/pom.xml +++ b/app/backend/pom.xml @@ -52,6 +52,10 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-starter-mail + io.jsonwebtoken jjwt diff --git a/app/backend/src/main/java/com/app/gamereview/controller/AuthController.java b/app/backend/src/main/java/com/app/gamereview/controller/AuthController.java index d3b32aea..7b0c18a1 100644 --- a/app/backend/src/main/java/com/app/gamereview/controller/AuthController.java +++ b/app/backend/src/main/java/com/app/gamereview/controller/AuthController.java @@ -3,15 +3,23 @@ import com.app.gamereview.dto.request.LoginUserRequestDto; import com.app.gamereview.dto.request.ChangeUserPasswordRequestDto; import com.app.gamereview.dto.request.RegisterUserRequestDto; +import com.app.gamereview.dto.request.VerifyResetCodeRequestDto; import com.app.gamereview.dto.response.LoginUserResponseDto; +import com.app.gamereview.model.ResetCode; import com.app.gamereview.model.User; +import com.app.gamereview.repository.ResetCodeRepository; import com.app.gamereview.service.AuthService; +import com.app.gamereview.service.EmailService; +import com.app.gamereview.service.UserService; import com.app.gamereview.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.Date; import java.util.Optional; +import java.util.UUID; @RestController @RequestMapping("/api/auth") @@ -19,14 +27,21 @@ public class AuthController { private final AuthService authService; + private final EmailService emailService; + + private final UserService userService; + + private final ResetCodeRepository resetCodeRepository; + @Autowired - public AuthController(AuthService authService) { + public AuthController(AuthService authService, EmailService emailService, UserService userService, + ResetCodeRepository resetCodeRepository) { this.authService = authService; + this.emailService = emailService; + this.userService = userService; + this.resetCodeRepository = resetCodeRepository; } - @Autowired - private JwtUtil jwtUtil; - @PostMapping("/register") public ResponseEntity registerUser(@RequestBody RegisterUserRequestDto registerUserRequestDto) { User userToCreate = authService.registerUser(registerUserRequestDto); @@ -45,4 +60,66 @@ public ResponseEntity login(@RequestBody LoginUserRequestD return ResponseEntity.ok(loginResponse); } + @PostMapping("/forgot-password") + public ResponseEntity forgotPassword(@RequestParam String email) { + User user = userService.getUserByEmail(email); + + if (user == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found"); + } + + // Generate and save a reset code (you can use UUID or any secure method) + String code = generateResetCode(user.getId()); + + // Send email with reset code + String subject = "Password Reset"; + String message = "Your password reset code is: " + code; + message += "\n The reset code will expire after 24 hours."; + emailService.sendEmail(email, subject, message); + + return ResponseEntity.ok("Reset code sent successfully"); + } + + @PostMapping("/verify-reset-code") + public ResponseEntity verifyResetCode(@RequestBody VerifyResetCodeRequestDto request) { + Optional resetCodeOptional = resetCodeRepository.findByCode(request.getResetCode()); + if (resetCodeOptional.isEmpty() || resetCodeOptional.get().getExpirationDate().before(new Date())) { + // Invalid or expired reset code + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Invalid or expired reset code"); + } + + ResetCode resetCode = resetCodeOptional.get(); + + // Check if the reset code matches the user + String userEmail = userService.getUserById(resetCode.getUserId()).getEmail(); + if (!userEmail.equals(request.getUserEmail())) { + // Reset code does not match the user + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(userEmail + " " + request.getUserEmail()); + } + + // Reset code is valid, generate a JWT token for the user + String token = JwtUtil.generateToken(userService.getUserById(resetCode.getUserId()).getEmail()); + + // Clear the reset code after generating the token + resetCodeRepository.deleteByUserId(resetCode.getUserId()); + + return ResponseEntity.ok(token); + } + + private String generateResetCode(String userId) { + // Check if a reset code exists for the user + ResetCode existingResetCode = resetCodeRepository.findByUserId(userId); + + // If a reset code exists, delete it + if (existingResetCode != null) { + resetCodeRepository.delete(existingResetCode); + } + String code = UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase(); + + ResetCode resetCode = new ResetCode(code, userId, new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)); + resetCodeRepository.save(resetCode); + + return code; + } + } diff --git a/app/backend/src/main/java/com/app/gamereview/dto/request/VerifyResetCodeRequestDto.java b/app/backend/src/main/java/com/app/gamereview/dto/request/VerifyResetCodeRequestDto.java new file mode 100644 index 00000000..12252c08 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/dto/request/VerifyResetCodeRequestDto.java @@ -0,0 +1,12 @@ +package com.app.gamereview.dto.request; + +import lombok.Getter; + +@Getter +public class VerifyResetCodeRequestDto { + + private String resetCode; + + private String userEmail; + +} \ No newline at end of file diff --git a/app/backend/src/main/java/com/app/gamereview/model/ResetCode.java b/app/backend/src/main/java/com/app/gamereview/model/ResetCode.java new file mode 100644 index 00000000..7128d171 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/model/ResetCode.java @@ -0,0 +1,60 @@ +package com.app.gamereview.model; + +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.index.Indexed; +import org.springframework.data.mongodb.core.mapping.Document; +import java.util.Date; + +@Document(collection = "resetCodes") +public class ResetCode { + + @Id + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getUserId() { + return userId; + } + + public void setUser(String userId) { + this.userId = userId; + } + + public Date getExpirationDate() { + return expirationDate; + } + + public void setExpirationDate(Date expirationDate) { + this.expirationDate = expirationDate; + } + + private String code; + + @Indexed(unique = true) // Ensures a unique constraint on userId field + private String userId; // ID of the associated user + + private Date expirationDate; + + public ResetCode(String code, String userId, Date expirationDate) { + + this.code = code; + this.userId = userId; + this.expirationDate = expirationDate; + } + +} diff --git a/app/backend/src/main/java/com/app/gamereview/repository/ResetCodeRepository.java b/app/backend/src/main/java/com/app/gamereview/repository/ResetCodeRepository.java new file mode 100644 index 00000000..37c30813 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/repository/ResetCodeRepository.java @@ -0,0 +1,16 @@ +package com.app.gamereview.repository; + +import com.app.gamereview.model.ResetCode; +import org.springframework.data.mongodb.repository.MongoRepository; + +import java.util.Optional; + +public interface ResetCodeRepository extends MongoRepository { + + ResetCode findByUserId(String userId); + + Optional findByCode(String code); + + void deleteByUserId(String userId); + +} \ No newline at end of file diff --git a/app/backend/src/main/java/com/app/gamereview/repository/UserRepository.java b/app/backend/src/main/java/com/app/gamereview/repository/UserRepository.java index a0a22c8f..a9786afa 100644 --- a/app/backend/src/main/java/com/app/gamereview/repository/UserRepository.java +++ b/app/backend/src/main/java/com/app/gamereview/repository/UserRepository.java @@ -2,6 +2,7 @@ import com.app.gamereview.model.User; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import java.util.List; import java.util.Optional; @@ -13,4 +14,7 @@ public interface UserRepository extends MongoRepository { Optional findByEmailAndIsDeletedFalse(String email); + @Query("{ 'email' : ?0 }") + Optional findByEmail(String email); + } diff --git a/app/backend/src/main/java/com/app/gamereview/service/EmailService.java b/app/backend/src/main/java/com/app/gamereview/service/EmailService.java new file mode 100644 index 00000000..127a1f03 --- /dev/null +++ b/app/backend/src/main/java/com/app/gamereview/service/EmailService.java @@ -0,0 +1,25 @@ +package com.app.gamereview.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +public class EmailService { + + @Autowired + private JavaMailSender mailSender; + + public void sendEmail(String toEmail, String subject, String body) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom("bounswe2023.group5@gmail.com"); + message.setTo(toEmail); + message.setText(body); + message.setSubject(subject); + mailSender.send(message); + System.out.println("Mail Send..."); + + } + +} diff --git a/app/backend/src/main/java/com/app/gamereview/service/UserService.java b/app/backend/src/main/java/com/app/gamereview/service/UserService.java index 7a03e4b6..0952546e 100644 --- a/app/backend/src/main/java/com/app/gamereview/service/UserService.java +++ b/app/backend/src/main/java/com/app/gamereview/service/UserService.java @@ -69,4 +69,10 @@ public Boolean deleteUserById(String id) { return false; } + public User getUserByEmail(String email) { + Optional getResult = userRepository.findByEmail(email); + + return getResult.orElse(null); + } + } diff --git a/app/backend/src/main/resources/.env.example b/app/backend/src/main/resources/.env.example new file mode 100644 index 00000000..6eb48ecf --- /dev/null +++ b/app/backend/src/main/resources/.env.example @@ -0,0 +1,10 @@ +MONGO_DATABASE= +MONGO_USER= +MONGO_PASSWORD= +MONGO_CLUSTER= +MAIL_HOST= +MAIL_PORT= +MAIL_USERNAME= +MAIL_PASSWORD= +MAIL_AUTH= +MAIL_STARTTLS= \ No newline at end of file diff --git a/app/backend/src/main/resources/application.properties b/app/backend/src/main/resources/application.properties index a7ba4c82..aefdd78c 100644 --- a/app/backend/src/main/resources/application.properties +++ b/app/backend/src/main/resources/application.properties @@ -1,2 +1,8 @@ spring.data.mongodb.database=${MONGO_DATABASE} spring.data.mongodb.uri=mongodb+srv://${MONGO_USER}:${MONGO_PASSWORD}@${MONGO_CLUSTER} +spring.mail.host=${MAIL_HOST} +spring.mail.port=${MAIL_PORT} +spring.mail.username=${MAIL_USERNAME} +spring.mail.password=${MAIL_PASSWORD} +spring.mail.properties.mail.smtp.auth=${MAIL_AUTH} +spring.mail.properties.mail.smtp.starttls.enable=${MAIL_STARTTLS} \ No newline at end of file diff --git a/app/backend/target/classes/application.properties b/app/backend/target/classes/application.properties index a7ba4c82..aefdd78c 100644 --- a/app/backend/target/classes/application.properties +++ b/app/backend/target/classes/application.properties @@ -1,2 +1,8 @@ spring.data.mongodb.database=${MONGO_DATABASE} spring.data.mongodb.uri=mongodb+srv://${MONGO_USER}:${MONGO_PASSWORD}@${MONGO_CLUSTER} +spring.mail.host=${MAIL_HOST} +spring.mail.port=${MAIL_PORT} +spring.mail.username=${MAIL_USERNAME} +spring.mail.password=${MAIL_PASSWORD} +spring.mail.properties.mail.smtp.auth=${MAIL_AUTH} +spring.mail.properties.mail.smtp.starttls.enable=${MAIL_STARTTLS} \ No newline at end of file