feat: add password recovery

This commit is contained in:
Tim
2025-07-28 13:02:02 +08:00
parent a5900aa60d
commit 0c784dc5cc
8 changed files with 292 additions and 1 deletions

View File

@@ -13,6 +13,7 @@ import com.openisle.service.RegisterModeService;
import com.openisle.service.NotificationService;
import com.openisle.model.RegisterMode;
import com.openisle.repository.UserRepository;
import com.openisle.exception.FieldException;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
@@ -38,6 +39,7 @@ public class AuthController {
private final NotificationService notificationService;
private final UserRepository userRepository;
@Value("${app.captcha.enabled:false}")
private boolean captchaEnabled;
@@ -270,6 +272,41 @@ public class AuthController {
return ResponseEntity.ok(Map.of("valid", true));
}
@PostMapping("/forgot/send")
public ResponseEntity<?> sendReset(@RequestBody ForgotPasswordRequest req) {
Optional<User> userOpt = userService.findByEmail(req.getEmail());
if (userOpt.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "User not found"));
}
String code = userService.generatePasswordResetCode(req.getEmail());
emailService.sendEmail(req.getEmail(), "Password Reset Code", "Your verification code is " + code);
return ResponseEntity.ok(Map.of("message", "Verification code sent"));
}
@PostMapping("/forgot/verify")
public ResponseEntity<?> verifyReset(@RequestBody VerifyForgotRequest req) {
boolean ok = userService.verifyPasswordResetCode(req.getEmail(), req.getCode());
if (ok) {
String username = userService.findByEmail(req.getEmail()).get().getUsername();
return ResponseEntity.ok(Map.of("token", jwtService.generateResetToken(username)));
}
return ResponseEntity.badRequest().body(Map.of("error", "Invalid verification code"));
}
@PostMapping("/forgot/reset")
public ResponseEntity<?> resetPassword(@RequestBody ResetPasswordRequest req) {
String username = jwtService.validateAndGetSubjectForReset(req.getToken());
try {
userService.updatePassword(username, req.getPassword());
return ResponseEntity.ok(Map.of("message", "Password updated"));
} catch (FieldException e) {
return ResponseEntity.badRequest().body(Map.of(
"field", e.getField(),
"error", e.getMessage()
));
}
}
@Data
private static class RegisterRequest {
private String username;
@@ -320,4 +357,21 @@ public class AuthController {
private String token;
private String reason;
}
@Data
private static class ForgotPasswordRequest {
private String email;
}
@Data
private static class VerifyForgotRequest {
private String email;
private String code;
}
@Data
private static class ResetPasswordRequest {
private String token;
private String password;
}
}

View File

@@ -37,6 +37,8 @@ public class User {
private String verificationCode;
private String passwordResetCode;
private String avatar;
@Column(length = 1000)

View File

@@ -21,6 +21,9 @@ public class JwtService {
@Value("${app.jwt.reason-secret}")
private String reasonSecret;
@Value("${app.jwt.reset-secret}")
private String resetSecret;
@Value("${app.jwt.expiration}")
private long expiration;
@@ -56,6 +59,17 @@ public class JwtService {
.compact();
}
public String generateResetToken(String subject) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration);
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(getSigningKeyForSecret(resetSecret))
.compact();
}
public String validateAndGetSubject(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKeyForSecret(secret))
@@ -73,4 +87,13 @@ public class JwtService {
.getBody();
return claims.getSubject();
}
public String validateAndGetSubjectForReset(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKeyForSecret(resetSecret))
.build()
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
}

View File

@@ -155,4 +155,32 @@ public class UserService {
}
return userRepository.save(user);
}
public String generatePasswordResetCode(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
String code = genCode();
user.setPasswordResetCode(code);
userRepository.save(user);
return code;
}
public boolean verifyPasswordResetCode(String email, String code) {
Optional<User> userOpt = userRepository.findByEmail(email);
if (userOpt.isPresent() && code.equals(userOpt.get().getPasswordResetCode())) {
User user = userOpt.get();
user.setPasswordResetCode(null);
userRepository.save(user);
return true;
}
return false;
}
public User updatePassword(String username, String newPassword) {
passwordValidator.validate(newPassword);
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
user.setPassword(passwordEncoder.encode(newPassword));
return userRepository.save(user);
}
}