Skip to content

Commit

Permalink
Merge pull request #486 from bounswe/forgot-password
Browse files Browse the repository at this point in the history
Forgot Password Implementation - BE
  • Loading branch information
halisbal authored Oct 23, 2023
2 parents 848a980 + e193588 commit 68a5757
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 4 deletions.
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}

0 comments on commit 68a5757

Please sign in to comment.