diff --git a/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java b/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java index fa2b2020..e06c3e4b 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java +++ b/src/main/java/net/gepafin/tendermanagement/config/SecurityConfig.java @@ -54,17 +54,17 @@ public class SecurityConfig { } @Bean - MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { + public MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { return new MvcRequestMatcher.Builder(introspector); } - + @Bean public WebSecurityCustomizer webSecurityCustomizer(MvcRequestMatcher.Builder mvc) { return (web) -> web.ignoring().requestMatchers(mvc.pattern(HttpMethod.OPTIONS, "/**")) - .requestMatchers(new AntPathRequestMatcher("/i18n/**")) - .requestMatchers(new AntPathRequestMatcher("/content/**")) - .requestMatchers(new AntPathRequestMatcher("/swagger-ui/index.html")) - .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")); + .requestMatchers(new AntPathRequestMatcher("/i18n/**")) + .requestMatchers(new AntPathRequestMatcher("/content/**")) + .requestMatchers(new AntPathRequestMatcher("/swagger-ui/index.html")) + .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")); } @@ -87,9 +87,10 @@ public class SecurityConfig { return new CorsFilter(source); } + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception { - http + http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers(mvc.pattern(HttpMethod.POST, "/v1/user/login")).permitAll() diff --git a/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java b/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java index c3c54d99..3068fefb 100644 --- a/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java +++ b/src/main/java/net/gepafin/tendermanagement/config/jwt/TokenProvider.java @@ -48,6 +48,7 @@ public class TokenProvider { public static final String INVALID_USER = "invalid_user"; static final String AUTH_SECRET = "X-Api-Secret"; + private final Set invalidatedTokens = new HashSet<>(); @PostConstruct public void init() { @@ -113,6 +114,10 @@ public class TokenProvider { public boolean validateToken(String authToken) { try { + if (isTokenInvalid(authToken)) { + log.warn("Token is invalidated."); + return false; + } Jwts.parserBuilder() .setSigningKey(key) .build() @@ -124,6 +129,15 @@ public class TokenProvider { return false; } } + + public void invalidateToken(String token) { + invalidatedTokens.add(token); + log.info("Token invalidated: {}", token); + } + + public boolean isTokenInvalid(String token) { + return invalidatedTokens.contains(token); + } public Map getUserInfoAndUserIdFromToken(HttpServletRequest request) { Map userInfo = new HashMap<>(); String authSecretHeader=request.getHeader(AUTH_SECRET); @@ -196,4 +210,12 @@ public class TokenProvider { Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody(); return claims.getSubject(); } + public String extractTokenFromRequest(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); // Remove "Bearer " prefix + } + return null; // Return null if token is not found or not in Bearer format + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java index c5b1a697..7d9683db 100644 --- a/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java +++ b/src/main/java/net/gepafin/tendermanagement/constants/GepafinConstant.java @@ -63,5 +63,13 @@ public class GepafinConstant { public static final String LOOKUP_DATA_DELETED_SUCCESSFULLY = "lookupdata.deleted.successfully"; public static final String DOCUMENT_UPDATED_SUCCESSFULLY = "document.updated.successfully"; public static final String DOCUMENT_FETCHED_SUCCESSFULLY = "document.fetched.successfully"; + public static final String RESET_PASSWORD_INITIATED = "password.reset.initiated"; + public static final String PASSWORD_RESET_SUCCESS = "password.reset.success"; + public static final String INVALID_TOKEN_MSG = "invalid.token.msg"; + public static final String CURRENT_PASSWORD_INCORRECT = "current.password.incorrect"; + + public static final String LOGOUT_SUCCESSFUL_MSG = "logout.successful.msg"; + public static final String SUCCESS_PASSWORD_CHANGED = "success.password.changed"; + public static final String UPDATE_USER_STATUS_SUCCESS_MSG = "update.user.status.success"; } diff --git a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java index dc06326f..3ee707c2 100644 --- a/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java +++ b/src/main/java/net/gepafin/tendermanagement/dao/UserDao.java @@ -1,13 +1,13 @@ package net.gepafin.tendermanagement.dao; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.constants.GepafinConstant; import net.gepafin.tendermanagement.entities.RoleEntity; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.UserStatusEnum; -import net.gepafin.tendermanagement.model.request.LoginReq; -import net.gepafin.tendermanagement.model.request.UpdateUserReq; -import net.gepafin.tendermanagement.model.request.UserReq; +import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.RoleResponseBean; import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; @@ -23,6 +23,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Repository; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Optional; + import static net.gepafin.tendermanagement.util.ObjectUtils.setIfUpdated; @Repository @@ -149,4 +153,82 @@ public class UserDao { log.info("Login successful for email: {}", loginReq.getEmail()); return jwtToken; } + public String generateSecureToken() { + SecureRandom secureRandom = new SecureRandom(); + byte[] tokenBytes = new byte[24]; + secureRandom.nextBytes(tokenBytes); + String token = Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes); + log.debug("Generated secure token: {}", token); + return token; + } + + public String initiatePasswordReset(InitiatePasswordResetReq resetReq) { + UserEntity user = userRepository.findByEmail(resetReq.getEmail()); + if (user == null) { + log.info("Password reset attempt for non-existent user: {}", resetReq.getEmail()); + throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG)); + } + String token = generateSecureToken(); + user.setResetPasswordToken(token); + userRepository.save(user); + log.info("Password reset token generated for user: {}", resetReq.getEmail()); + return token; + } + + public Boolean resetPassword(ResetPasswordReq resetPasswordReq) { + UserEntity user = userRepository.findByEmail(resetPasswordReq.getEmail()); + if (user == null) { + log.info("Password reset attempt for non-existent user: {}", resetPasswordReq.getEmail()); + throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG)); + } + if (!resetPasswordReq.getNewPassword().equals(resetPasswordReq.getConfirmPassword())) { + log.info("User creation failed: Passwords do not match for email {}", user.getEmail()); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.PASSWORD_DOESNT_MATCH)); + } + String dbToken = user.getResetPasswordToken(); + + if (dbToken == null || !dbToken.equals(resetPasswordReq.getToken())) { + log.info("Invalid password reset token for user: {}", resetPasswordReq.getEmail()); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.INVALID_TOKEN_MSG)); + } + + user.setPassword(passwordEncoder.encode(resetPasswordReq.getNewPassword())); + user.setResetPasswordToken(null); + userRepository.save(user); + log.info("Password successfully reset for user: {}", resetPasswordReq.getEmail()); + return true; + } + + public Boolean changePassword(ChangePasswordRequest request) { + UserEntity user = userRepository.findByEmail(request.getEmail()); + if (user == null) { + log.info("Password reset attempt for non-existent user: {}", request.getEmail()); + throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG)); + } + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + throw new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.CURRENT_PASSWORD_INCORRECT)); + } + if (!request.getNewPassword().equals(request.getConfirmPassword())) { + log.info("User creation failed: Passwords do not match for email {}", user.getEmail()); + throw new CustomValidationException(Status.VALIDATION_ERROR, Translator.toLocale(GepafinConstant.PASSWORD_DOESNT_MATCH)); + } + user.setPassword(passwordEncoder.encode(request.getNewPassword())); + userRepository.save(user); + return true; + } + public void logout(HttpServletRequest request, HttpServletResponse response) { + authService.logout(request, response); + log.info("User successfully logged out."); + } + + public UserResponseBean updateUserStatus(Long userId, UserStatusEnum statusReq) { + log.info("Updating status for user with ID: {}", userId); + UserEntity userEntity = userRepository.findById(userId) + .orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG))); + userEntity.setStatus(statusReq.getValue()); + userEntity = userRepository.save(userEntity); + log.info("User status updated to {} for user ID: {}", statusReq, userId); + return convertUserEntityToUserResponse(userEntity); + } + } diff --git a/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java b/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java index aac82db9..5bd88f33 100644 --- a/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java +++ b/src/main/java/net/gepafin/tendermanagement/entities/UserEntity.java @@ -58,4 +58,6 @@ public class UserEntity extends BaseEntity { @Column(name = "COUNTRY", length = 50, nullable = true) private String country; + @Column(name = "RESET_PASSWORD_TOKEN", length = 255, nullable = true) + private String resetPasswordToken; } diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/ChangePasswordRequest.java b/src/main/java/net/gepafin/tendermanagement/model/request/ChangePasswordRequest.java new file mode 100644 index 00000000..a15f8cfc --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/ChangePasswordRequest.java @@ -0,0 +1,16 @@ +package net.gepafin.tendermanagement.model.request; + +import lombok.Data; + +@Data +public class ChangePasswordRequest { + + + private String email; + + private String password; + + private String newPassword; + + private String confirmPassword; +} \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/InitiatePasswordResetReq.java b/src/main/java/net/gepafin/tendermanagement/model/request/InitiatePasswordResetReq.java new file mode 100644 index 00000000..ae2ba0d0 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/InitiatePasswordResetReq.java @@ -0,0 +1,8 @@ +package net.gepafin.tendermanagement.model.request; + +import lombok.Data; + +@Data +public class InitiatePasswordResetReq { + private String email; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/LogoutReq.java b/src/main/java/net/gepafin/tendermanagement/model/request/LogoutReq.java new file mode 100644 index 00000000..00c07a25 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/LogoutReq.java @@ -0,0 +1,10 @@ +package net.gepafin.tendermanagement.model.request; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Data; + +@Data +public class LogoutReq { + private String email; +} diff --git a/src/main/java/net/gepafin/tendermanagement/model/request/ResetPasswordReq.java b/src/main/java/net/gepafin/tendermanagement/model/request/ResetPasswordReq.java new file mode 100644 index 00000000..019ca582 --- /dev/null +++ b/src/main/java/net/gepafin/tendermanagement/model/request/ResetPasswordReq.java @@ -0,0 +1,13 @@ +package net.gepafin.tendermanagement.model.request; + +import lombok.Data; + +@Data +public class ResetPasswordReq { + private String email; + private String token; + private String newPassword; + private String confirmPassword; + + +} diff --git a/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java b/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java index 6b6fcbe0..7720430c 100644 --- a/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java +++ b/src/main/java/net/gepafin/tendermanagement/repositories/UserRepository.java @@ -8,4 +8,5 @@ import java.util.Optional; public interface UserRepository extends JpaRepository { Optional findByEmailIgnoreCase(String email); boolean existsByEmailIgnoreCase(String email); + UserEntity findByEmail(String email); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/UserService.java b/src/main/java/net/gepafin/tendermanagement/service/UserService.java index 68912240..c609a5ac 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/UserService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/UserService.java @@ -1,8 +1,9 @@ package net.gepafin.tendermanagement.service; -import net.gepafin.tendermanagement.model.request.LoginReq; -import net.gepafin.tendermanagement.model.request.UpdateUserReq; -import net.gepafin.tendermanagement.model.request.UserReq; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import net.gepafin.tendermanagement.enums.UserStatusEnum; +import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; @@ -16,4 +17,15 @@ public interface UserService { void deleteUser(Long userId); JWTToken login(LoginReq loginReq); + + + String initiatePasswordReset(InitiatePasswordResetReq resetReq); + + Boolean resetPassword(ResetPasswordReq resetPasswordReq); + + Boolean changePassword(ChangePasswordRequest request); + + void logoutUser(HttpServletRequest request, HttpServletResponse response); + + UserResponseBean updateUserStatus(Long userId, UserStatusEnum statusReq); } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java index 6955edf4..62ead6f2 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/AuthenticationService.java @@ -1,5 +1,7 @@ package net.gepafin.tendermanagement.service.impl; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.config.Translator; import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.GepafinConstant; @@ -7,6 +9,7 @@ import net.gepafin.tendermanagement.dao.RoleDao; import net.gepafin.tendermanagement.entities.UserEntity; import net.gepafin.tendermanagement.enums.UserStatusEnum; import net.gepafin.tendermanagement.model.request.LoginReq; +import net.gepafin.tendermanagement.model.request.LogoutReq; import net.gepafin.tendermanagement.model.response.LoginResponse; import net.gepafin.tendermanagement.model.response.RoleResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; @@ -21,7 +24,9 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Service; + import java.time.LocalDateTime; @Service @@ -54,8 +59,8 @@ public class AuthenticationService { UserEntity user = userRepository.findByEmailIgnoreCase(loginReq.getEmail()).orElseThrow(()-> new CustomValidationException(Status.NOT_FOUND, Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG))); if (Boolean.FALSE.equals(UserStatusEnum.ACTIVE.getValue().equals(user.getStatus()))) { - new CustomValidationException(Status.NOT_FOUND, - Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG)); + throw new CustomValidationException(Status.NOT_FOUND, + Translator.toLocale(GepafinConstant.USER_NOT_FOUND_MSG)); } user.setLastLogin(DateTimeUtil.DateServerToUTC(LocalDateTime.now())); userRepository.save(user); @@ -67,10 +72,10 @@ public class AuthenticationService { JWTToken jwtToken = new JWTToken(token, loginResponse); - log.info("Login successful for email: {}", loginReq.getEmail()); - return jwtToken; - } - + log.info("Login successful for email: {}", loginReq.getEmail()); + return jwtToken; + } + private static LoginResponse getLoginResponse(UserEntity user, RoleResponseBean roleResponseBean) { LoginResponse loginResponse = new LoginResponse(); loginResponse.setId(user.getId()); @@ -89,5 +94,15 @@ public class AuthenticationService { loginResponse.setUpdatedDate(user.getUpdatedDate()); return loginResponse; } + public void logout(HttpServletRequest request, HttpServletResponse response) + { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); + if (auth != null) { + String token = tokenProvider.extractTokenFromRequest(request); + tokenProvider.invalidateToken(token); + new SecurityContextLogoutHandler().logout(request, response, auth); + } + SecurityContextHolder.getContext().setAuthentication(null); + SecurityContextHolder.clearContext(); +} } diff --git a/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java b/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java index c6205aae..6499aa29 100644 --- a/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java +++ b/src/main/java/net/gepafin/tendermanagement/service/impl/UserServiceImpl.java @@ -1,9 +1,10 @@ package net.gepafin.tendermanagement.service.impl; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import net.gepafin.tendermanagement.dao.UserDao; -import net.gepafin.tendermanagement.model.request.LoginReq; -import net.gepafin.tendermanagement.model.request.UpdateUserReq; -import net.gepafin.tendermanagement.model.request.UserReq; +import net.gepafin.tendermanagement.enums.UserStatusEnum; +import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.LoginResponse; import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; @@ -50,4 +51,27 @@ public class UserServiceImpl implements UserService { } + @Override + public String initiatePasswordReset(InitiatePasswordResetReq resetReq) { + return userDao.initiatePasswordReset(resetReq); + } + + @Override + public Boolean resetPassword(ResetPasswordReq resetPasswordReq) { + return userDao.resetPassword(resetPasswordReq); + } + @Override + public Boolean changePassword(ChangePasswordRequest request){ + return userDao.changePassword(request); + } + @Override + public void logoutUser(HttpServletRequest request, HttpServletResponse response) { + userDao.logout(request,response); + } + @Override + @Transactional(rollbackFor = Exception.class) + public UserResponseBean updateUserStatus(Long userId, UserStatusEnum statusReq) { + return userDao.updateUserStatus(userId, statusReq); + + } } \ No newline at end of file diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java index cca0b247..c60cef58 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/UserApi.java @@ -5,10 +5,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.responses.ApiResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import net.gepafin.tendermanagement.model.request.LoginReq; -import net.gepafin.tendermanagement.model.request.UpdateUserReq; -import net.gepafin.tendermanagement.model.request.UserReq; +import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.enums.UserStatusEnum; +import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; import net.gepafin.tendermanagement.model.util.Response; @@ -18,10 +20,7 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.*; @Validated public interface UserApi { @@ -105,4 +104,77 @@ public interface UserApi { method = RequestMethod.POST) ResponseEntity> login( @Parameter(description = "Login request object", required = true) @Valid @RequestBody LoginReq loginReq); + @Operation(summary = "Api to initiate password reset request", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)}))}) + @RequestMapping(value = "/reset-password/initiate", + produces = {"application/json"}, + method = RequestMethod.POST) + ResponseEntity> initiatePasswordReset( + @Parameter(description = "Initiate password reset request object", required = true) @Valid @RequestBody InitiatePasswordResetReq initiatePasswordResetReq); + + @Operation(summary = "Api to reset password", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)}))}) + @RequestMapping(value = "/reset-password", + produces = {"application/json"}, + method = RequestMethod.POST) + ResponseEntity> resetPassword( + @Parameter(description = "Reset password request object", required = true) @Valid @RequestBody ResetPasswordReq resetPasswordReq); + @Operation(summary = "Api to change user password", + responses = { + @ApiResponse(responseCode = "200", description = "Password Changed Successfully", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = "{ \"message\": \"Password changed successfully.\" }")})), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)}))}) + @RequestMapping(value = "/change-password", + produces = {"application/json"}, + method = RequestMethod.POST) + ResponseEntity> changePassword( + @Parameter(description = "Change password request object", required = true) @Valid @RequestBody ChangePasswordRequest changePasswordRequest); @Operation(summary = "Api to logout user", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)}))}) + @RequestMapping(value = "/logout", + method = RequestMethod.POST) + ResponseEntity> logoutUser( + @Parameter(description = "The request object is not needed for logout", required = false) HttpServletRequest request, + @Parameter(description = "The response object is not needed for logout", required = false) HttpServletResponse response); + @Operation(summary = "Api to update user active/deactive status", + responses = { + @ApiResponse(responseCode = "200", description = "OK"), + @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.NOTFOUND_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.UNAUTHORIZED_ERROR_EXAMPLE)})), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, examples = { + @ExampleObject(value = ErrorConstants.BADREQUEST_ERROR_EXAMPLE)})) + }) + @RequestMapping(value = "/{userId}/status", + produces = {"application/json"}, + method = RequestMethod.PUT) + @PreAuthorize("hasRole('ROLE_SUPER_ADMIN')") + default ResponseEntity> updateUserStatus( + @Parameter(description = "The user id", required = true) @PathVariable("userId") Long userId, + @Parameter(description = "status", required = true)@RequestParam(value = "status", required = true) UserStatusEnum status) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + + } + diff --git a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java index f44c2e87..fb4801b7 100644 --- a/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java +++ b/src/main/java/net/gepafin/tendermanagement/web/rest/api/impl/UserApiController.java @@ -1,17 +1,19 @@ package net.gepafin.tendermanagement.web.rest.api.impl; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import net.gepafin.tendermanagement.config.Translator; +import net.gepafin.tendermanagement.config.jwt.TokenProvider; import net.gepafin.tendermanagement.constants.GepafinConstant; -import net.gepafin.tendermanagement.model.request.LoginReq; -import net.gepafin.tendermanagement.model.request.UpdateUserReq; -import net.gepafin.tendermanagement.model.request.UserReq; +import net.gepafin.tendermanagement.entities.UserEntity; +import net.gepafin.tendermanagement.enums.UserStatusEnum; +import net.gepafin.tendermanagement.model.request.*; import net.gepafin.tendermanagement.model.response.UserResponseBean; import net.gepafin.tendermanagement.model.util.JWTToken; import net.gepafin.tendermanagement.model.util.Response; import net.gepafin.tendermanagement.service.UserService; import net.gepafin.tendermanagement.web.rest.api.UserApi; - import net.gepafin.tendermanagement.web.rest.api.errors.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +29,8 @@ import org.springframework.web.bind.annotation.*; public class UserApiController implements UserApi { private final Logger log = LoggerFactory.getLogger(UserApiController.class); - + @Autowired + private TokenProvider tokenProvider; @Autowired private UserService userService; @@ -74,4 +77,46 @@ public class UserApiController implements UserApi { JWTToken jwtToken = userService.login(loginReq); return ResponseEntity.ok(new Response<>(jwtToken, Status.SUCCESS, Translator.toLocale(GepafinConstant.LOGIN_SUCCESS_MSG))); } -} + @Override + public ResponseEntity> changePassword(@Valid @RequestBody ChangePasswordRequest request) { + log.info("Change Password attempt for email: {}", request.getEmail()); + userService.changePassword(request); + return ResponseEntity.ok(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.SUCCESS_PASSWORD_CHANGED))); + } + @Override + public ResponseEntity> initiatePasswordReset(InitiatePasswordResetReq request) { + log.info("Initiating password reset for email: {}", request.getEmail()); + String resetToken = userService.initiatePasswordReset(request); + log.info("Password reset token generated for email: {}", request.getEmail()); + return ResponseEntity.ok(new Response<>(resetToken, Status.SUCCESS, Translator.toLocale(GepafinConstant.RESET_PASSWORD_INITIATED))); + } + + @Override + public ResponseEntity> resetPassword(ResetPasswordReq request) { + log.info("Resetting password for username: {}", request.getEmail()); + Boolean success = userService.resetPassword(request); + if (success) { + log.info("Password reset successfully for username: {}", request.getEmail()); + } else { + log.error("Password reset failed for username: {}", request.getEmail()); + } + return ResponseEntity.ok(new Response<>(success, Status.SUCCESS, Translator.toLocale(GepafinConstant.PASSWORD_RESET_SUCCESS))); + } + + @Override + public ResponseEntity> logoutUser(HttpServletRequest request, HttpServletResponse response) { + userService.logoutUser(request, response); + + log.info("User has successfully logged"); + return ResponseEntity.status(HttpStatus.OK) + .body(new Response<>(null, Status.SUCCESS, Translator.toLocale(GepafinConstant.LOGOUT_SUCCESSFUL_MSG))); + } + + @Override + public ResponseEntity> updateUserStatus(@PathVariable Long userId, @RequestParam UserStatusEnum status) { + log.info("Update User Status for- User ID: {}, Request Body: {}", userId, status); + UserResponseBean updatedUser = userService.updateUserStatus(userId, status); + return ResponseEntity.ok(new Response<>(updatedUser, Status.SUCCESS, Translator.toLocale(GepafinConstant.UPDATE_USER_STATUS_SUCCESS_MSG))); + } + +} \ No newline at end of file diff --git a/src/main/resources/db/changelog/db.changelog-1.0.0.xml b/src/main/resources/db/changelog/db.changelog-1.0.0.xml index 5293ec62..2d2e63c2 100644 --- a/src/main/resources/db/changelog/db.changelog-1.0.0.xml +++ b/src/main/resources/db/changelog/db.changelog-1.0.0.xml @@ -395,4 +395,12 @@ + + + + + + + + diff --git a/src/main/resources/message_en.properties b/src/main/resources/message_en.properties index 918cd38a..e044c0ef 100644 --- a/src/main/resources/message_en.properties +++ b/src/main/resources/message_en.properties @@ -65,6 +65,20 @@ evaluation.criteria.created.successfully=EvaluationCriteria created successfully evaluation.criteria.fetch.successfully=EvaluationCriteria fetched successfully. evaluation.criteria.updated.successfully=EvaluationCriteria updated successfully. evaluation.criteria.deleted.successfully=EvaluationCriteria deleted successfully. +# Password reset messages +password.reset.initiated=Password reset initiated. +password.reset.success=Password has been successfully reset. +#Change Password +invalid.token.msg=The token provided is invalid or expired. Please request a new token. +current.password.incorrect = Current password is incorrect. +success.password.changed=Password changed successfully. +#Logout +logout.successful.msg=Logout successful. You have been logged out successfully. +#Update user Active or Deactive status +update.user.status.success=User status has been successfully updated. + + + #Faq-related messages faq.not.found=Faq not found. diff --git a/src/main/resources/message_it.properties b/src/main/resources/message_it.properties index 39cdf933..6e135982 100644 --- a/src/main/resources/message_it.properties +++ b/src/main/resources/message_it.properties @@ -81,4 +81,14 @@ lookupdata.deleted.successfully=LookUpData eliminato correttamente. #Document-related message document.updated.successfully=Documento aggiornato con successo. -document.fetched.successfully=Documento recuperato con successo. \ No newline at end of file +document.fetched.successfully=Documento recuperato con successo. +# Password reset messages +password.reset.initiated=Reimpostazione della password avviata. +password.reset.success=La password � stata reimpostata con successo. +invalid.token.msg=Il token fornito � invalido o scaduto. Si prega di richiedere un nuovo token. +current.password.incorrect = La password attuale non � corretta. +success.password.changed=Password cambiata con successo. +logout.successful.msg=Logout riuscito. Sei stato disconnesso con successo. + +update.user.status.success=Lo stato dell'utente � stato aggiornato con successo. +