Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forgot Password Implementation - BE #486

Merged
merged 8 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,45 @@
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")
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<User> registerUser(@RequestBody RegisterUserRequestDto registerUserRequestDto) {
User userToCreate = authService.registerUser(registerUserRequestDto);
Expand All @@ -45,4 +60,66 @@ public ResponseEntity<LoginUserResponseDto> login(@RequestBody LoginUserRequestD
return ResponseEntity.ok(loginResponse);
}

@PostMapping("/forgot-password")
public ResponseEntity<String> 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<String> verifyResetCode(@RequestBody VerifyResetCodeRequestDto request) {
Optional<ResetCode> 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;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.app.gamereview.dto.request;

import lombok.Getter;

@Getter
public class VerifyResetCodeRequestDto {

private String resetCode;

private String userEmail;

}
60 changes: 60 additions & 0 deletions app/backend/src/main/java/com/app/gamereview/model/ResetCode.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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, String> {

ResetCode findByUserId(String userId);

Optional<ResetCode> findByCode(String code);

void deleteByUserId(String userId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -13,4 +14,7 @@ public interface UserRepository extends MongoRepository<User, String> {

Optional<User> findByEmailAndIsDeletedFalse(String email);

@Query("{ 'email' : ?0 }")
Optional<User> findByEmail(String email);

}
Original file line number Diff line number Diff line change
@@ -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("[email protected]");
message.setTo(toEmail);
message.setText(body);
message.setSubject(subject);
mailSender.send(message);
System.out.println("Mail Send...");

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,10 @@ public Boolean deleteUserById(String id) {
return false;
}

public User getUserByEmail(String email) {
Optional<User> getResult = userRepository.findByEmail(email);

return getResult.orElse(null);
}

}
10 changes: 10 additions & 0 deletions app/backend/src/main/resources/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
MONGO_DATABASE=
MONGO_USER=
MONGO_PASSWORD=
MONGO_CLUSTER=
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_AUTH=
MAIL_STARTTLS=
6 changes: 6 additions & 0 deletions app/backend/src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -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}
6 changes: 6 additions & 0 deletions app/backend/target/classes/application.properties
Original file line number Diff line number Diff line change
@@ -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}