Commit 58a1b9d6 authored by Phùng Quốc Toàn's avatar Phùng Quốc Toàn

feat: add password management features including change, reset, and forgot password functionality

parent 09bbd610
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.devteria</groupId>
<artifactId>identity-service</artifactId>
<name>identity-service</name>
<version>0.0.1-SNAPSHOT</version>
<description>Identity service</description>
<properties>
<java.version>21</java.version>
<projectlombok-lombok.version>1.18.30</projectlombok-lombok.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${projectlombok-lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.devteria</groupId>
<artifactId>identity-service</artifactId>
<name>identity-service</name>
<version>0.0.1-SNAPSHOT</version>
<description>Identity service</description>
<properties>
<java.version>21</java.version>
<projectlombok-lombok.version>1.18.30</projectlombok-lombok.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-crypto -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-crypto</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${projectlombok-lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${projectlombok-lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.suppressGeneratorTimestamp=true</arg>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
<arg>-Amapstruct.verbose=true</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${projectlombok-lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Amapstruct.suppressGeneratorTimestamp=true</arg>
<arg>-Amapstruct.defaultComponentModel=spring</arg>
<arg>-Amapstruct.verbose=true</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
</project>
......@@ -3,7 +3,8 @@ package com.devteria.identityservice.configuration;
import com.devteria.identityservice.dto.request.IntrospectRequest;
import com.devteria.identityservice.service.AuthenticationService;
import com.nimbusds.jose.JOSEException;
import org.springframework.beans.factory.annotation.Autowired;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
......@@ -14,21 +15,27 @@ import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec;
import java.text.ParseException;
import java.util.Objects;
@Component
@RequiredArgsConstructor
public class CustomJwtDecoder implements JwtDecoder {
private final AuthenticationService authenticationService;
@Value("${jwt.signer-key}")
private String signerKey;
@Autowired
private AuthenticationService authenticationService;
private NimbusJwtDecoder nimbusJwtDecoder = null;
private NimbusJwtDecoder nimbusJwtDecoder;
@PostConstruct
public void initDecoder() {
SecretKeySpec secretKeySpec = new SecretKeySpec(signerKey.getBytes(), "HmacSHA512");
nimbusJwtDecoder = NimbusJwtDecoder
.withSecretKey(secretKeySpec)
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
@Override
public Jwt decode(String token) throws JwtException {
try {
var response = authenticationService.introspect(IntrospectRequest.builder()
.token(token)
......@@ -40,14 +47,6 @@ public class CustomJwtDecoder implements JwtDecoder {
throw new JwtException(e.getMessage());
}
if (Objects.isNull(nimbusJwtDecoder)) {
SecretKeySpec secretKeySpec = new SecretKeySpec(signerKey.getBytes(), "HS512");
nimbusJwtDecoder = NimbusJwtDecoder
.withSecretKey(secretKeySpec)
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
return nimbusJwtDecoder.decode(token);
}
}
\ No newline at end of file
......@@ -20,7 +20,7 @@ import org.springframework.security.web.SecurityFilterChain;
@RequiredArgsConstructor
public class SecurityConfig {
private final String[] PUBLIC_ENDPOINTS = new String[]{"/auth/token", "/auth/introspect", "/users", "/auth/logout", "/auth/refresh-token"};
private final String[] PUBLIC_ENDPOINTS = new String[]{"/auth/token", "/auth/introspect", "/users", "/auth/logout", "/auth/refresh-token", "/auth/forgot-password", "/auth/reset-password"};
CustomJwtDecoder customJwtDecoder;
......
......@@ -5,6 +5,7 @@ import com.devteria.identityservice.dto.response.AuthenticationResponse;
import com.devteria.identityservice.dto.response.IntrospectResponse;
import com.devteria.identityservice.service.AuthenticationService;
import com.nimbusds.jose.JOSEException;
import jakarta.mail.MessagingException;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
......@@ -54,4 +55,28 @@ public class AuthenticationController {
.result("Logout successful")
.build();
}
@PostMapping("/change-password")
ApiResponse<String> changePassword(@RequestBody ChangePasswordRequest request) {
authenticationService.changePassword(request);
return ApiResponse.<String>builder()
.result("Password changed successfully")
.build();
}
@PostMapping("/forgot-password")
ApiResponse<String> forgotPassword(@RequestBody ForgotPasswordRequest request) throws MessagingException {
authenticationService.forgotPassword(request);
return ApiResponse.<String>builder()
.result("Otp sent to your email")
.build();
}
@PostMapping("/reset-password")
ApiResponse<String> resetPassword(@RequestBody ResetPasswordRequest request) {
authenticationService.resetPassword(request);
return ApiResponse.<String>builder()
.result("Password reset successfully")
.build();
}
}
......@@ -34,7 +34,7 @@ public class UserController {
ApiResponse<List<UserResponse>> getUsers() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
log.info("User :{}", authentication.getName());
log.info("User id:{}", authentication.getName());
authentication.getAuthorities().forEach(grantedAuthority -> log.info(grantedAuthority.getAuthority()));
return ApiResponse.<List<UserResponse>>builder()
......
package com.devteria.identityservice.dto.request;
import lombok.*;
import lombok.experimental.FieldDefaults;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ChangePasswordRequest {
String oldPassword;
String newPassword;
}
package com.devteria.identityservice.dto.request;
import lombok.*;
import lombok.experimental.FieldDefaults;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ForgotPasswordRequest {
String email;
}
package com.devteria.identityservice.dto.request;
import lombok.*;
import lombok.experimental.FieldDefaults;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@FieldDefaults(level = AccessLevel.PRIVATE)
public class ResetPasswordRequest {
String email;
String newPassword;
String otp;
}
package com.devteria.identityservice.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.*;
import lombok.experimental.FieldDefaults;
......@@ -15,8 +17,13 @@ public class UserCreationRequest {
@Size(min = 3, message = "USERNAME_INVALID")
String username;
@Email
@NotBlank
String email;
@Size(min = 8, message = "INVALID_PASSWORD")
String password;
String firstName;
String lastName;
......
......@@ -19,6 +19,9 @@ public class User {
@GeneratedValue(strategy = GenerationType.UUID)
String id;
String username;
String email;
String password;
String firstName;
String lastName;
......
package com.devteria.identityservice.enums;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.experimental.FieldDefaults;
@Getter
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public enum OtpType {
RESET_PASSWORD("reset-password:", 2 * 60), // 2 phút
REGISTRATION("registration:", 5 * 60); // 5 phút
String name;
int expireTimeInSeconds;
}
......@@ -15,7 +15,9 @@ public enum ErrorCode {
UNAUTHENTICATED(1006, "Unauthenticated", HttpStatus.UNAUTHORIZED),
UNAUTHORIZED(1007, "You do not have permission", HttpStatus.FORBIDDEN),
INVALID_DOB(1008, "Date of birth must be at least {min} years old", HttpStatus.BAD_REQUEST),
;
PASSWORD_NOT_MATCH(1009, "Password not match", HttpStatus.BAD_REQUEST),
INVALID_OTP(1010, "Mã OTP không hợp lệ hoặc đã hết hạn", HttpStatus.BAD_REQUEST),
EMAIL_SENDING_FAILED(1011, "Không thể gửi email", HttpStatus.INTERNAL_SERVER_ERROR);;
private int code;
......
......@@ -9,5 +9,10 @@ import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, String> {
boolean existsByUsername(String username);
Optional<User> findByUsername(String username);
boolean existsByEmail(String email);
Optional<User> findByEmail(String email);
}
package com.devteria.identityservice.service;
import com.devteria.identityservice.dto.request.AuthenticationRequest;
import com.devteria.identityservice.dto.request.IntrospectRequest;
import com.devteria.identityservice.dto.request.LogoutRequest;
import com.devteria.identityservice.dto.request.RefreshTokenRequest;
import com.devteria.identityservice.dto.request.*;
import com.devteria.identityservice.dto.response.AuthenticationResponse;
import com.devteria.identityservice.dto.response.IntrospectResponse;
import com.devteria.identityservice.entity.InvalidatedToken;
import com.devteria.identityservice.entity.User;
import com.devteria.identityservice.enums.OtpType;
import com.devteria.identityservice.exception.AppException;
import com.devteria.identityservice.exception.ErrorCode;
import com.devteria.identityservice.repository.UserRepository;
......@@ -16,6 +14,7 @@ import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import jakarta.mail.MessagingException;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
......@@ -23,6 +22,7 @@ import lombok.experimental.NonFinal;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
......@@ -41,8 +41,13 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class AuthenticationService {
private static final String TOKEN_KEY_PREFIX = "invalid_token:";
private static String TOKEN_KEY_PREFIX = "invalid_token:";
RedisTemplate<String, Object> redisTemplate;
PasswordEncoder passwordEncoder;
OtpService otpService;
EmailService emailService;
UserRepository userRepository;
@NonFinal
......@@ -95,7 +100,7 @@ public class AuthenticationService {
JWSHeader header = new JWSHeader(JWSAlgorithm.HS512);
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
.subject(user.getUsername())
.subject(user.getId())
.issuer("demo_jwt")
.issueTime(new Date())
.expirationTime(new Date(
......@@ -217,4 +222,39 @@ public class AuthenticationService {
private boolean existsInvalidatedToken(String tokenId) {
return Boolean.TRUE.equals(redisTemplate.hasKey(TOKEN_KEY_PREFIX + tokenId));
}
public void changePassword(ChangePasswordRequest request) {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
User user = userRepository.findById(name).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_EXISTED));
if (!passwordEncoder.matches(request.getOldPassword(), user.getPassword())) {
throw new AppException(ErrorCode.PASSWORD_NOT_MATCH);
}
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
userRepository.save(user);
}
public void resetPassword(ResetPasswordRequest request) {
if (!(otpService.validateOtp(request.getOtp(), request.getEmail(), OtpType.RESET_PASSWORD))) {
throw new AppException(ErrorCode.INVALID_OTP);
}
User user = userRepository.findByEmail(request.getEmail())
.orElseThrow(() -> new AppException(ErrorCode.USER_NOT_EXISTED));
user.setPassword(passwordEncoder.encode(request.getNewPassword()));
userRepository.save(user);
}
public void forgotPassword(ForgotPasswordRequest request) throws MessagingException {
if (!(userRepository.existsByEmail(request.getEmail()))) {
throw new AppException(ErrorCode.USER_NOT_EXISTED);
}
String otp = otpService.generateAndSaveOtp(request.getEmail(), OtpType.RESET_PASSWORD);
emailService.sendOtpEmail(request.getEmail(), otp, OtpType.RESET_PASSWORD);
}
}
package com.devteria.identityservice.service;
import com.devteria.identityservice.enums.OtpType;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
@Slf4j
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public class EmailService {
// @Lazy
// final EmailService asyncEmailService;
final JavaMailSender mailSender;
@Async
public void sendEmail(String to, String subject, String text, boolean isHtml) throws MessagingException {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
String fromEmail = "hello@demomailtrap.co";
helper.setFrom(fromEmail);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(text, isHtml);
log.info("Sending email to {} from {}", to, fromEmail);
mailSender.send(message);
log.info("Email sent successfully to: {}", to);
} catch (MessagingException e) {
log.error("Failed to send email to {}: {}", to, e.getMessage());
throw e;
}
}
public void sendOtpEmail(String to, String otp, OtpType otpType) throws MessagingException {
switch (otpType) {
case RESET_PASSWORD:
sendPasswordResetOtp(to, otp);
break;
case REGISTRATION:
sendRegistrationOtp(to, otp);
break;
default:
log.error("Unknown OTP type: {}", otpType);
throw new IllegalArgumentException("Không hỗ trợ loại OTP này");
}
}
private void sendRegistrationOtp(String to, String otp) throws MessagingException {
String subject = "Mã OTP xác thực tài khoản của bạn";
String htmlContent = String.format(
"<div style='font-family: Arial, sans-serif;'>" +
"<h2>Xác thực tài khoản</h2>" +
"<p>Mã OTP của bạn để xác thực tài khoản là:</p>" +
"<h1 style='color: #4285f4; font-size: 32px; letter-spacing: 2px;'>%s</h1>" +
"<p>Mã này có hiệu lực trong 5 phút.</p>" +
"<p>Nếu bạn không yêu cầu xác thực tài khoản, vui lòng bỏ qua email này.</p>" +
"</div>", otp);
sendEmail(to, subject, htmlContent, true);
}
private void sendPasswordResetOtp(String to, String otp) throws MessagingException {
String subject = "Mã OTP đổi mật khẩu của bạn";
String htmlContent = String.format(
"<div style='font-family: Arial, sans-serif;'>" +
"<h2>Yêu cầu đổi mật khẩu</h2>" +
"<p>Mã OTP của bạn để đổi mật khẩu là:</p>" +
"<h1 style='color: #4285f4; font-size: 32px; letter-spacing: 2px;'>%s</h1>" +
"<p>Mã này có hiệu lực trong 5 phút.</p>" +
"<p>Nếu bạn không yêu cầu đổi mật khẩu, vui lòng bỏ qua email này.</p>" +
"</div>", otp);
sendEmail(to, subject, htmlContent, true);
}
}
package com.devteria.identityservice.service;
import com.devteria.identityservice.enums.OtpType;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
@Slf4j
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class OtpService {
static final String OTP_PREFIX = "otp:";
static final int OTP_LENGTH = 6;
RedisTemplate<String, Object> redisTemplate;
public String generateAndSaveOtp(String identifier, OtpType otpType) {
String otp = generateOtp(OTP_LENGTH);
String key = OTP_PREFIX + otpType.getName() + identifier;
redisTemplate.opsForValue().set(key, otp, otpType.getExpireTimeInSeconds(), TimeUnit.SECONDS);
log.info("Generated OTP for {}: {} with type {} (expires in {} seconds)",
identifier, key, otpType, otpType.getExpireTimeInSeconds());
return otp;
}
public boolean validateOtp(String otp, String identifier, OtpType otpType) {
String key = OTP_PREFIX + otpType.getName() + identifier;
String storedOtp = (String) redisTemplate.opsForValue().get(key);
if (storedOtp != null && storedOtp.equals(otp)) {
// Delete OTP after successful validation
redisTemplate.delete(key);
return true;
}
return false;
}
private String generateOtp(int length) {
SecureRandom random = new SecureRandom();
StringBuilder otp = new StringBuilder();
for (int i = 0; i < length; i++) {
otp.append(random.nextInt(10));
}
return otp.toString();
}
}
......@@ -30,6 +30,7 @@ import java.util.List;
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@Slf4j
public class UserService {
OtpService otpService;
UserRepository userRepository;
UserMapper userMapper;
PasswordEncoder passwordEncoder;
......@@ -82,7 +83,7 @@ public class UserService {
public UserResponse getMyInfo() {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
User user = userRepository.findByUsername(name).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_EXISTED));
User user = userRepository.findById(name).orElseThrow(() -> new AppException(ErrorCode.USER_NOT_EXISTED));
return userMapper.toUserResponse(user);
}
......
......@@ -12,6 +12,22 @@ spring:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
mail:
username: api
password: 476d44bdd59d2aa8c8bf66934f8869fd
host: live.smtp.mailtrap.io
port: 587
properties:
mail:
smtp:
auth: true
starttls:
enable: true
ssl:
enable: false
jwt:
signer-key: "1TjXchw5FloESb63Kc+DFhTARvpWL4jUGCwfGWxuG5SIf/1y/LgJxHnMqaF6A/ij"
......@@ -24,3 +40,4 @@ redis:
password: 123456a@
time-to-live: 60
db: 15
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment