mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-24 07:00:49 +08:00
feat: add password recovery
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ public class User {
|
||||
|
||||
private String verificationCode;
|
||||
|
||||
private String passwordResetCode;
|
||||
|
||||
private String avatar;
|
||||
|
||||
@Column(length = 1000)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user