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"?> <?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" <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"> 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> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
...@@ -74,6 +74,10 @@ ...@@ -74,6 +74,10 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId> <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -3,7 +3,8 @@ package com.devteria.identityservice.configuration; ...@@ -3,7 +3,8 @@ package com.devteria.identityservice.configuration;
import com.devteria.identityservice.dto.request.IntrospectRequest; import com.devteria.identityservice.dto.request.IntrospectRequest;
import com.devteria.identityservice.service.AuthenticationService; import com.devteria.identityservice.service.AuthenticationService;
import com.nimbusds.jose.JOSEException; 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.beans.factory.annotation.Value;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm; import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
...@@ -14,21 +15,27 @@ import org.springframework.stereotype.Component; ...@@ -14,21 +15,27 @@ import org.springframework.stereotype.Component;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.text.ParseException; import java.text.ParseException;
import java.util.Objects;
@Component @Component
@RequiredArgsConstructor
public class CustomJwtDecoder implements JwtDecoder { public class CustomJwtDecoder implements JwtDecoder {
private final AuthenticationService authenticationService;
@Value("${jwt.signer-key}") @Value("${jwt.signer-key}")
private String signerKey; private String signerKey;
private NimbusJwtDecoder nimbusJwtDecoder;
@Autowired @PostConstruct
private AuthenticationService authenticationService; public void initDecoder() {
SecretKeySpec secretKeySpec = new SecretKeySpec(signerKey.getBytes(), "HmacSHA512");
private NimbusJwtDecoder nimbusJwtDecoder = null; nimbusJwtDecoder = NimbusJwtDecoder
.withSecretKey(secretKeySpec)
.macAlgorithm(MacAlgorithm.HS512)
.build();
}
@Override @Override
public Jwt decode(String token) throws JwtException { public Jwt decode(String token) throws JwtException {
try { try {
var response = authenticationService.introspect(IntrospectRequest.builder() var response = authenticationService.introspect(IntrospectRequest.builder()
.token(token) .token(token)
...@@ -40,14 +47,6 @@ public class CustomJwtDecoder implements JwtDecoder { ...@@ -40,14 +47,6 @@ public class CustomJwtDecoder implements JwtDecoder {
throw new JwtException(e.getMessage()); 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); return nimbusJwtDecoder.decode(token);
} }
} }
\ No newline at end of file
...@@ -20,7 +20,7 @@ import org.springframework.security.web.SecurityFilterChain; ...@@ -20,7 +20,7 @@ import org.springframework.security.web.SecurityFilterChain;
@RequiredArgsConstructor @RequiredArgsConstructor
public class SecurityConfig { 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; CustomJwtDecoder customJwtDecoder;
......
...@@ -5,6 +5,7 @@ import com.devteria.identityservice.dto.response.AuthenticationResponse; ...@@ -5,6 +5,7 @@ import com.devteria.identityservice.dto.response.AuthenticationResponse;
import com.devteria.identityservice.dto.response.IntrospectResponse; import com.devteria.identityservice.dto.response.IntrospectResponse;
import com.devteria.identityservice.service.AuthenticationService; import com.devteria.identityservice.service.AuthenticationService;
import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.JOSEException;
import jakarta.mail.MessagingException;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -54,4 +55,28 @@ public class AuthenticationController { ...@@ -54,4 +55,28 @@ public class AuthenticationController {
.result("Logout successful") .result("Logout successful")
.build(); .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 { ...@@ -34,7 +34,7 @@ public class UserController {
ApiResponse<List<UserResponse>> getUsers() { ApiResponse<List<UserResponse>> getUsers() {
var authentication = SecurityContextHolder.getContext().getAuthentication(); var authentication = SecurityContextHolder.getContext().getAuthentication();
log.info("User :{}", authentication.getName()); log.info("User id:{}", authentication.getName());
authentication.getAuthorities().forEach(grantedAuthority -> log.info(grantedAuthority.getAuthority())); authentication.getAuthorities().forEach(grantedAuthority -> log.info(grantedAuthority.getAuthority()));
return ApiResponse.<List<UserResponse>>builder() 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; package com.devteria.identityservice.dto.request;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size; import jakarta.validation.constraints.Size;
import lombok.*; import lombok.*;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -15,8 +17,13 @@ public class UserCreationRequest { ...@@ -15,8 +17,13 @@ public class UserCreationRequest {
@Size(min = 3, message = "USERNAME_INVALID") @Size(min = 3, message = "USERNAME_INVALID")
String username; String username;
@Email
@NotBlank
String email;
@Size(min = 8, message = "INVALID_PASSWORD") @Size(min = 8, message = "INVALID_PASSWORD")
String password; String password;
String firstName; String firstName;
String lastName; String lastName;
......
...@@ -19,6 +19,9 @@ public class User { ...@@ -19,6 +19,9 @@ public class User {
@GeneratedValue(strategy = GenerationType.UUID) @GeneratedValue(strategy = GenerationType.UUID)
String id; String id;
String username; String username;
String email;
String password; String password;
String firstName; String firstName;
String lastName; 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 { ...@@ -15,7 +15,9 @@ public enum ErrorCode {
UNAUTHENTICATED(1006, "Unauthenticated", HttpStatus.UNAUTHORIZED), UNAUTHENTICATED(1006, "Unauthenticated", HttpStatus.UNAUTHORIZED),
UNAUTHORIZED(1007, "You do not have permission", HttpStatus.FORBIDDEN), 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), 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; private int code;
......
...@@ -9,5 +9,10 @@ import java.util.Optional; ...@@ -9,5 +9,10 @@ import java.util.Optional;
@Repository @Repository
public interface UserRepository extends JpaRepository<User, String> { public interface UserRepository extends JpaRepository<User, String> {
boolean existsByUsername(String username); boolean existsByUsername(String username);
Optional<User> findByUsername(String username); Optional<User> findByUsername(String username);
boolean existsByEmail(String email);
Optional<User> findByEmail(String email);
} }
package com.devteria.identityservice.service; package com.devteria.identityservice.service;
import com.devteria.identityservice.dto.request.AuthenticationRequest; import com.devteria.identityservice.dto.request.*;
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.response.AuthenticationResponse; import com.devteria.identityservice.dto.response.AuthenticationResponse;
import com.devteria.identityservice.dto.response.IntrospectResponse; import com.devteria.identityservice.dto.response.IntrospectResponse;
import com.devteria.identityservice.entity.InvalidatedToken; import com.devteria.identityservice.entity.InvalidatedToken;
import com.devteria.identityservice.entity.User; import com.devteria.identityservice.entity.User;
import com.devteria.identityservice.enums.OtpType;
import com.devteria.identityservice.exception.AppException; import com.devteria.identityservice.exception.AppException;
import com.devteria.identityservice.exception.ErrorCode; import com.devteria.identityservice.exception.ErrorCode;
import com.devteria.identityservice.repository.UserRepository; import com.devteria.identityservice.repository.UserRepository;
...@@ -16,6 +14,7 @@ import com.nimbusds.jose.crypto.MACSigner; ...@@ -16,6 +14,7 @@ import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier; import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT; import com.nimbusds.jwt.SignedJWT;
import jakarta.mail.MessagingException;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults; import lombok.experimental.FieldDefaults;
...@@ -23,6 +22,7 @@ import lombok.experimental.NonFinal; ...@@ -23,6 +22,7 @@ import lombok.experimental.NonFinal;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate; 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.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -41,8 +41,13 @@ import java.util.concurrent.TimeUnit; ...@@ -41,8 +41,13 @@ import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class AuthenticationService { public class AuthenticationService {
private static final String TOKEN_KEY_PREFIX = "invalid_token:"; private static String TOKEN_KEY_PREFIX = "invalid_token:";
RedisTemplate<String, Object> redisTemplate; RedisTemplate<String, Object> redisTemplate;
PasswordEncoder passwordEncoder;
OtpService otpService;
EmailService emailService;
UserRepository userRepository; UserRepository userRepository;
@NonFinal @NonFinal
...@@ -95,7 +100,7 @@ public class AuthenticationService { ...@@ -95,7 +100,7 @@ public class AuthenticationService {
JWSHeader header = new JWSHeader(JWSAlgorithm.HS512); JWSHeader header = new JWSHeader(JWSAlgorithm.HS512);
JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder() JWTClaimsSet jwtClaimsSet = new JWTClaimsSet.Builder()
.subject(user.getUsername()) .subject(user.getId())
.issuer("demo_jwt") .issuer("demo_jwt")
.issueTime(new Date()) .issueTime(new Date())
.expirationTime(new Date( .expirationTime(new Date(
...@@ -217,4 +222,39 @@ public class AuthenticationService { ...@@ -217,4 +222,39 @@ public class AuthenticationService {
private boolean existsInvalidatedToken(String tokenId) { private boolean existsInvalidatedToken(String tokenId) {
return Boolean.TRUE.equals(redisTemplate.hasKey(TOKEN_KEY_PREFIX + 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; ...@@ -30,6 +30,7 @@ import java.util.List;
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
@Slf4j @Slf4j
public class UserService { public class UserService {
OtpService otpService;
UserRepository userRepository; UserRepository userRepository;
UserMapper userMapper; UserMapper userMapper;
PasswordEncoder passwordEncoder; PasswordEncoder passwordEncoder;
...@@ -82,7 +83,7 @@ public class UserService { ...@@ -82,7 +83,7 @@ public class UserService {
public UserResponse getMyInfo() { public UserResponse getMyInfo() {
String name = SecurityContextHolder.getContext().getAuthentication().getName(); 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); return userMapper.toUserResponse(user);
} }
......
...@@ -12,6 +12,22 @@ spring: ...@@ -12,6 +12,22 @@ spring:
hibernate: hibernate:
ddl-auto: update ddl-auto: update
show-sql: true 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: jwt:
signer-key: "1TjXchw5FloESb63Kc+DFhTARvpWL4jUGCwfGWxuG5SIf/1y/LgJxHnMqaF6A/ij" signer-key: "1TjXchw5FloESb63Kc+DFhTARvpWL4jUGCwfGWxuG5SIf/1y/LgJxHnMqaF6A/ij"
...@@ -24,3 +40,4 @@ redis: ...@@ -24,3 +40,4 @@ redis:
password: 123456a@ password: 123456a@
time-to-live: 60 time-to-live: 60
db: 15 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