From da567d278e271e7b126e489c2ef67bc2fbe161de Mon Sep 17 00:00:00 2001 From: nhkhai Date: Fri, 22 Mar 2024 09:40:07 +0800 Subject: [PATCH 1/4] Update pom.xml Reorganized dependencies. --- pom.xml | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/pom.xml b/pom.xml index 76d8a50..0fd76f0 100644 --- a/pom.xml +++ b/pom.xml @@ -1,6 +1,7 @@ + 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"> 4.0.0 org.springframework.boot @@ -20,7 +21,9 @@ org.springframework.boot - spring-boot-starter-web + spring-boot-devtools + runtime + true org.springframework.boot @@ -30,6 +33,15 @@ org.springframework.boot spring-boot-starter-validation + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + diff --git a/src/main/java/sg/com/smartinventory/entities/ErrorResponse.java b/src/main/java/sg/com/smartinventory/entities/ErrorResponse.java new file mode 100644 index 0000000..42ae4d9 --- /dev/null +++ b/src/main/java/sg/com/smartinventory/entities/ErrorResponse.java @@ -0,0 +1,15 @@ +package sg.com.smartinventory.entities; + +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class ErrorResponse { + private String message; + private LocalDateTime timestamp; +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/entities/User.java b/src/main/java/sg/com/smartinventory/entities/User.java new file mode 100644 index 0000000..af96d06 --- /dev/null +++ b/src/main/java/sg/com/smartinventory/entities/User.java @@ -0,0 +1,69 @@ +package sg.com.smartinventory.entities; + +import java.util.List; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.JoinTable; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.Digits; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "my_user") +public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @NotBlank(message = "Username cannot be blank") + @Column(name = "username") + private String username; + + @NotBlank(message = "Password cannot be blank") + @Column(name = "password") + private String password; + + @NotBlank(message = "First name cannot be blank") + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + @Digits(fraction = 0, integer = 8, message = "ContactNo must be 8 digits") + @Column(name = "contact_no") + private String contactNo; + + @Email(message = "Email format must be valid") + @Column(name = "email") + private String email; + + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable(name = "user_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { + @JoinColumn(name = "user_role_id") }) + private List roles; + + public User(String email) { + this.email = email; + } +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/entities/UserRole.java b/src/main/java/sg/com/smartinventory/entities/UserRole.java new file mode 100644 index 0000000..660d6ac --- /dev/null +++ b/src/main/java/sg/com/smartinventory/entities/UserRole.java @@ -0,0 +1,43 @@ +package sg.com.smartinventory.entities; + +import java.util.List; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToMany; +import jakarta.persistence.Table; + +import com.fasterxml.jackson.annotation.JsonBackReference; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "user_role") +public class UserRole { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "role_name") + private String roleName; + + @Column(name = "description") + private String description; + + @JsonBackReference + @ManyToMany(mappedBy = "roles") + private List users; +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java b/src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java new file mode 100644 index 0000000..ca6e759 --- /dev/null +++ b/src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java @@ -0,0 +1,70 @@ +package sg.com.smartinventory.exceptions; + +import java.time.LocalDateTime; +import java.util.List; + +import sg.com.smartinventory.entities.ErrorResponse; + +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.ObjectError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + // This is handler for CustomerNotFoundException + @ExceptionHandler({ CustomerNotFoundException.class, ProductNotFoundException.class, + ReviewNotFoundException.class }) + public ResponseEntity handleResourceNotFoundException(CustomerNotFoundException ex) { + ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), LocalDateTime.now()); + + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + + // @ExceptionHandler(ProductNotFoundException.class) + // public ResponseEntity + // handleProductNotFoundException(CustomerNotFoundException ex){ + // ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), + // LocalDateTime.now()); + // + // return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + // } + + @ExceptionHandler(EmptyResultDataAccessException.class) + public ResponseEntity handleEmptyResultDataAccessException(EmptyResultDataAccessException ex) { + ErrorResponse errorResponse = new ErrorResponse("Entry does not exist. ", LocalDateTime.now()); + + return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) { + // Get a list of all validation errors from the exception object. + List validationErrors = ex.getBindingResult().getAllErrors(); + + // Create a StringBuilder to store all error messages. + StringBuilder sb = new StringBuilder(); + + for (ObjectError error : validationErrors) { + sb.append(error.getDefaultMessage() + "."); + } + + ErrorResponse errorResponse = new ErrorResponse(sb.toString(), LocalDateTime.now()); + + return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception ex) { + // We can log the exception here. + // logger.error(ex.getMessage(), ex); + + // Return generic error message. + ErrorResponse errorResponse = new ErrorResponse("Something went wrong", LocalDateTime.now()); + + return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); + } +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/repositories/UserRepository.java b/src/main/java/sg/com/smartinventory/repositories/UserRepository.java new file mode 100644 index 0000000..6f20afc --- /dev/null +++ b/src/main/java/sg/com/smartinventory/repositories/UserRepository.java @@ -0,0 +1,9 @@ +package sg.com.smartinventory.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; + +import sg.com.smartinventory.entities.User; + +public interface UserRepository extends JpaRepository { + User findByUsername(String username); +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java b/src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java new file mode 100644 index 0000000..941b23c --- /dev/null +++ b/src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java @@ -0,0 +1,61 @@ +package sg.com.smartinventory.security; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.ArrayList; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import io.jsonwebtoken.Claims; +import sg.com.smartinventory.entities.ErrorResponse; + +@Component +public class JwtTokenFilter extends OncePerRequestFilter { + + private JwtTokenUtil jwtTokenUtil; + + public JwtTokenFilter(JwtTokenUtil jwtTokenUtil) { + this.jwtTokenUtil = jwtTokenUtil; + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + try { + String accessToken = jwtTokenUtil.resolveToken(request); + if (accessToken == null) { + filterChain.doFilter(request, response); + return; + } + + Claims claims = jwtTokenUtil.resolveClaims(request); + + if (claims != null & jwtTokenUtil.validateClaims(claims)) { + String email = claims.getSubject(); + + Authentication authentication = new UsernamePasswordAuthenticationToken(email, "", new ArrayList<>()); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + } catch (Exception ex) { + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.getWriter().write(new ErrorResponse("Authentication failure", LocalDateTime.now()).toString()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + + } + filterChain.doFilter(request, response); + + } +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java b/src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java new file mode 100644 index 0000000..3092157 --- /dev/null +++ b/src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java @@ -0,0 +1,92 @@ +package sg.com.smartinventory.security; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import jakarta.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Component; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import sg.com.smartinventory.entities.User; + +@Component +public class JwtTokenUtil { + + private final String JWT_SECRET_KEY = "mysecretkey"; + + @Value("${jwt.session.period:3600000}") + private long jwtSessionPeriod; + + private final JwtParser jwtParser; + + private final String TOKEN_HEADER = "Authorization"; + private final String TOKEN_PREFIX = "Bearer "; + + public JwtTokenUtil() { + this.jwtParser = Jwts.parser().setSigningKey(JWT_SECRET_KEY); + } + + public String createToken(User user) { + Claims claims = Jwts.claims().setSubject(user.getEmail()); + claims.put("userName", user.getUsername()); + claims.put("firstName", user.getFirstName()); + claims.put("lastName", user.getLastName()); + + Date tokenCreateTime = new Date(); + Date tokenValidity = new Date(tokenCreateTime.getTime() + TimeUnit.MINUTES.toMillis(jwtSessionPeriod)); + + return Jwts.builder() + .setClaims(claims) + .setExpiration(tokenValidity) + .signWith(SignatureAlgorithm.HS256, JWT_SECRET_KEY) + .compact(); + } + + private Claims parseJwtClaims(String token) { + return jwtParser.parseClaimsJws(token).getBody(); + } + + public String resolveToken(HttpServletRequest request) { + + String bearerToken = request.getHeader(TOKEN_HEADER); + if (bearerToken != null && bearerToken.startsWith(TOKEN_PREFIX)) { + return bearerToken.substring(TOKEN_PREFIX.length()); + } + return null; + } + + public Claims resolveClaims(HttpServletRequest req) { + try { + String token = resolveToken(req); + if (token != null) { + return parseJwtClaims(token); + } + return null; + } catch (ExpiredJwtException ex) { + req.setAttribute("expired", ex.getMessage()); + throw ex; + } catch (Exception ex) { + req.setAttribute("invalid", ex.getMessage()); + throw ex; + } + } + + public boolean validateClaims(Claims claims) throws AuthenticationException { + try { + return claims.getExpiration().after(new Date()); + } catch (Exception e) { + throw e; + } + } + + public String getEmail(Claims claims) { + return claims.getSubject(); + } +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java b/src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java new file mode 100644 index 0000000..87a03d5 --- /dev/null +++ b/src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java @@ -0,0 +1,65 @@ +package sg.com.smartinventory.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import sg.com.smartinventory.serviceImpls.CustomUserDetailsService; + +import jakarta.servlet.http.HttpServletResponse; + +@Configuration +@EnableWebSecurity +public class SecurityConfiguration { + + private final JwtTokenFilter jwtTokenFilter; + private final CustomUserDetailsService customUserDetailsService; + + public SecurityConfiguration(JwtTokenFilter jwtTokenFilter, CustomUserDetailsService customUserDetailsService) { + this.jwtTokenFilter = jwtTokenFilter; + this.customUserDetailsService = customUserDetailsService; + } + + @Bean + PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder) + throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = http + .getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder); + return authenticationManagerBuilder.build(); + } + + @Bean + SecurityFilterChain securityFitlerChain(HttpSecurity http) throws Exception { + http.csrf(csrf -> csrf.disable()); + + http = http.sessionManagement(management -> management + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); + + http = http.exceptionHandling(handling -> handling.authenticationEntryPoint((request, response, ex) -> { + response.sendError( + HttpServletResponse.SC_UNAUTHORIZED, + ex.getMessage()); + })); + + http.authorizeRequests( + (authorize) -> authorize.antMatchers("/auth/**").permitAll().anyRequest().authenticated()); + + http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java b/src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java new file mode 100644 index 0000000..b7411e6 --- /dev/null +++ b/src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java @@ -0,0 +1,35 @@ +package sg.com.smartinventory.serviceImpls; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import sg.com.smartinventory.entities.User; +import sg.com.smartinventory.repositories.UserRepository; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + public CustomUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepository.findByUsername(username); + List roles = new ArrayList<>(); + roles.add("USER"); + UserDetails userDetails = org.springframework.security.core.userdetails.User.builder() + .username(user.getUsername()) + .password(user.getPassword()) + .roles(roles.toArray(new String[0])) + .build(); + return userDetails; + } +} \ No newline at end of file diff --git a/src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java b/src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java new file mode 100644 index 0000000..ae73c98 --- /dev/null +++ b/src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java @@ -0,0 +1,15 @@ +package sg.com.smartinventory.entities; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import org.mockito.InjectMocks; +import org.mockito.Mock; + +public class ErrorResponseTest { + @DisplayName("Error Response Test") + @Test + public void errorResponseTest() throws Exception { + + } +} \ No newline at end of file From 682f9305efbc96e2e53512c304be58335ebdd917 Mon Sep 17 00:00:00 2001 From: nhkhai Date: Fri, 22 Mar 2024 17:40:12 +0800 Subject: [PATCH 4/4] Revert "Partially added JWT support." This reverts commit 79391634cb04a5656d5b16e3be12c60d0d2b8175. --- pom.xml | 14 --- .../entities/ErrorResponse.java | 15 --- .../sg/com/smartinventory/entities/User.java | 69 -------------- .../com/smartinventory/entities/UserRole.java | 43 --------- .../exceptions/GlobalExceptionHandler.java | 70 -------------- .../repositories/UserRepository.java | 9 -- .../security/JwtTokenFilter.java | 61 ------------ .../smartinventory/security/JwtTokenUtil.java | 92 ------------------- .../security/SecurityConfiguration.java | 65 ------------- .../CustomUserDetailsService.java | 35 ------- .../entities/ErrorResponseTest.java | 15 --- 11 files changed, 488 deletions(-) delete mode 100644 src/main/java/sg/com/smartinventory/entities/ErrorResponse.java delete mode 100644 src/main/java/sg/com/smartinventory/entities/User.java delete mode 100644 src/main/java/sg/com/smartinventory/entities/UserRole.java delete mode 100644 src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java delete mode 100644 src/main/java/sg/com/smartinventory/repositories/UserRepository.java delete mode 100644 src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java delete mode 100644 src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java delete mode 100644 src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java delete mode 100644 src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java delete mode 100644 src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java diff --git a/pom.xml b/pom.xml index eb4b8f0..0fd76f0 100644 --- a/pom.xml +++ b/pom.xml @@ -37,10 +37,6 @@ org.springframework.boot spring-boot-starter-web - - org.springframework.boot - spring-boot-starter-security - org.springframework.boot spring-boot-starter-test @@ -80,11 +76,6 @@ 3.25.3 test - - io.jsonwebtoken - jjwt - 0.9.1 - @@ -98,11 +89,6 @@ org.projectlombok lombok - diff --git a/src/main/java/sg/com/smartinventory/entities/ErrorResponse.java b/src/main/java/sg/com/smartinventory/entities/ErrorResponse.java deleted file mode 100644 index 42ae4d9..0000000 --- a/src/main/java/sg/com/smartinventory/entities/ErrorResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package sg.com.smartinventory.entities; - -import java.time.LocalDateTime; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@AllArgsConstructor -public class ErrorResponse { - private String message; - private LocalDateTime timestamp; -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/entities/User.java b/src/main/java/sg/com/smartinventory/entities/User.java deleted file mode 100644 index af96d06..0000000 --- a/src/main/java/sg/com/smartinventory/entities/User.java +++ /dev/null @@ -1,69 +0,0 @@ -package sg.com.smartinventory.entities; - -import java.util.List; - -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.JoinTable; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.Table; -import jakarta.validation.constraints.Digits; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Table(name = "my_user") -public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @NotBlank(message = "Username cannot be blank") - @Column(name = "username") - private String username; - - @NotBlank(message = "Password cannot be blank") - @Column(name = "password") - private String password; - - @NotBlank(message = "First name cannot be blank") - @Column(name = "first_name") - private String firstName; - - @Column(name = "last_name") - private String lastName; - - @Digits(fraction = 0, integer = 8, message = "ContactNo must be 8 digits") - @Column(name = "contact_no") - private String contactNo; - - @Email(message = "Email format must be valid") - @Column(name = "email") - private String email; - - @ManyToMany(cascade = CascadeType.ALL) - @JoinTable(name = "user_user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = { - @JoinColumn(name = "user_role_id") }) - private List roles; - - public User(String email) { - this.email = email; - } -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/entities/UserRole.java b/src/main/java/sg/com/smartinventory/entities/UserRole.java deleted file mode 100644 index 660d6ac..0000000 --- a/src/main/java/sg/com/smartinventory/entities/UserRole.java +++ /dev/null @@ -1,43 +0,0 @@ -package sg.com.smartinventory.entities; - -import java.util.List; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.ManyToMany; -import jakarta.persistence.Table; - -import com.fasterxml.jackson.annotation.JsonBackReference; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@Builder -@AllArgsConstructor -@NoArgsConstructor -@Entity -@Table(name = "user_role") -public class UserRole { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id") - private Long id; - - @Column(name = "role_name") - private String roleName; - - @Column(name = "description") - private String description; - - @JsonBackReference - @ManyToMany(mappedBy = "roles") - private List users; -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java b/src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java deleted file mode 100644 index ca6e759..0000000 --- a/src/main/java/sg/com/smartinventory/exceptions/GlobalExceptionHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package sg.com.smartinventory.exceptions; - -import java.time.LocalDateTime; -import java.util.List; - -import sg.com.smartinventory.entities.ErrorResponse; - -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.ObjectError; -import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; - -@ControllerAdvice -public class GlobalExceptionHandler { - // This is handler for CustomerNotFoundException - @ExceptionHandler({ CustomerNotFoundException.class, ProductNotFoundException.class, - ReviewNotFoundException.class }) - public ResponseEntity handleResourceNotFoundException(CustomerNotFoundException ex) { - ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), LocalDateTime.now()); - - return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); - } - - // @ExceptionHandler(ProductNotFoundException.class) - // public ResponseEntity - // handleProductNotFoundException(CustomerNotFoundException ex){ - // ErrorResponse errorResponse = new ErrorResponse(ex.getMessage(), - // LocalDateTime.now()); - // - // return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); - // } - - @ExceptionHandler(EmptyResultDataAccessException.class) - public ResponseEntity handleEmptyResultDataAccessException(EmptyResultDataAccessException ex) { - ErrorResponse errorResponse = new ErrorResponse("Entry does not exist. ", LocalDateTime.now()); - - return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); - } - - @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleValidationExceptions(MethodArgumentNotValidException ex) { - // Get a list of all validation errors from the exception object. - List validationErrors = ex.getBindingResult().getAllErrors(); - - // Create a StringBuilder to store all error messages. - StringBuilder sb = new StringBuilder(); - - for (ObjectError error : validationErrors) { - sb.append(error.getDefaultMessage() + "."); - } - - ErrorResponse errorResponse = new ErrorResponse(sb.toString(), LocalDateTime.now()); - - return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST); - } - - @ExceptionHandler(Exception.class) - public ResponseEntity handleException(Exception ex) { - // We can log the exception here. - // logger.error(ex.getMessage(), ex); - - // Return generic error message. - ErrorResponse errorResponse = new ErrorResponse("Something went wrong", LocalDateTime.now()); - - return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); - } -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/repositories/UserRepository.java b/src/main/java/sg/com/smartinventory/repositories/UserRepository.java deleted file mode 100644 index 6f20afc..0000000 --- a/src/main/java/sg/com/smartinventory/repositories/UserRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package sg.com.smartinventory.repositories; - -import org.springframework.data.jpa.repository.JpaRepository; - -import sg.com.smartinventory.entities.User; - -public interface UserRepository extends JpaRepository { - User findByUsername(String username); -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java b/src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java deleted file mode 100644 index 941b23c..0000000 --- a/src/main/java/sg/com/smartinventory/security/JwtTokenFilter.java +++ /dev/null @@ -1,61 +0,0 @@ -package sg.com.smartinventory.security; - -import java.io.IOException; -import java.time.LocalDateTime; -import java.util.ArrayList; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import io.jsonwebtoken.Claims; -import sg.com.smartinventory.entities.ErrorResponse; - -@Component -public class JwtTokenFilter extends OncePerRequestFilter { - - private JwtTokenUtil jwtTokenUtil; - - public JwtTokenFilter(JwtTokenUtil jwtTokenUtil) { - this.jwtTokenUtil = jwtTokenUtil; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) - throws ServletException, IOException { - - try { - String accessToken = jwtTokenUtil.resolveToken(request); - if (accessToken == null) { - filterChain.doFilter(request, response); - return; - } - - Claims claims = jwtTokenUtil.resolveClaims(request); - - if (claims != null & jwtTokenUtil.validateClaims(claims)) { - String email = claims.getSubject(); - - Authentication authentication = new UsernamePasswordAuthenticationToken(email, "", new ArrayList<>()); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - - } catch (Exception ex) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - response.getWriter().write(new ErrorResponse("Authentication failure", LocalDateTime.now()).toString()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - - } - filterChain.doFilter(request, response); - - } -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java b/src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java deleted file mode 100644 index 3092157..0000000 --- a/src/main/java/sg/com/smartinventory/security/JwtTokenUtil.java +++ /dev/null @@ -1,92 +0,0 @@ -package sg.com.smartinventory.security; - -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import jakarta.servlet.http.HttpServletRequest; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Component; - -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.JwtParser; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import sg.com.smartinventory.entities.User; - -@Component -public class JwtTokenUtil { - - private final String JWT_SECRET_KEY = "mysecretkey"; - - @Value("${jwt.session.period:3600000}") - private long jwtSessionPeriod; - - private final JwtParser jwtParser; - - private final String TOKEN_HEADER = "Authorization"; - private final String TOKEN_PREFIX = "Bearer "; - - public JwtTokenUtil() { - this.jwtParser = Jwts.parser().setSigningKey(JWT_SECRET_KEY); - } - - public String createToken(User user) { - Claims claims = Jwts.claims().setSubject(user.getEmail()); - claims.put("userName", user.getUsername()); - claims.put("firstName", user.getFirstName()); - claims.put("lastName", user.getLastName()); - - Date tokenCreateTime = new Date(); - Date tokenValidity = new Date(tokenCreateTime.getTime() + TimeUnit.MINUTES.toMillis(jwtSessionPeriod)); - - return Jwts.builder() - .setClaims(claims) - .setExpiration(tokenValidity) - .signWith(SignatureAlgorithm.HS256, JWT_SECRET_KEY) - .compact(); - } - - private Claims parseJwtClaims(String token) { - return jwtParser.parseClaimsJws(token).getBody(); - } - - public String resolveToken(HttpServletRequest request) { - - String bearerToken = request.getHeader(TOKEN_HEADER); - if (bearerToken != null && bearerToken.startsWith(TOKEN_PREFIX)) { - return bearerToken.substring(TOKEN_PREFIX.length()); - } - return null; - } - - public Claims resolveClaims(HttpServletRequest req) { - try { - String token = resolveToken(req); - if (token != null) { - return parseJwtClaims(token); - } - return null; - } catch (ExpiredJwtException ex) { - req.setAttribute("expired", ex.getMessage()); - throw ex; - } catch (Exception ex) { - req.setAttribute("invalid", ex.getMessage()); - throw ex; - } - } - - public boolean validateClaims(Claims claims) throws AuthenticationException { - try { - return claims.getExpiration().after(new Date()); - } catch (Exception e) { - throw e; - } - } - - public String getEmail(Claims claims) { - return claims.getSubject(); - } -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java b/src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java deleted file mode 100644 index 87a03d5..0000000 --- a/src/main/java/sg/com/smartinventory/security/SecurityConfiguration.java +++ /dev/null @@ -1,65 +0,0 @@ -package sg.com.smartinventory.security; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -import sg.com.smartinventory.serviceImpls.CustomUserDetailsService; - -import jakarta.servlet.http.HttpServletResponse; - -@Configuration -@EnableWebSecurity -public class SecurityConfiguration { - - private final JwtTokenFilter jwtTokenFilter; - private final CustomUserDetailsService customUserDetailsService; - - public SecurityConfiguration(JwtTokenFilter jwtTokenFilter, CustomUserDetailsService customUserDetailsService) { - this.jwtTokenFilter = jwtTokenFilter; - this.customUserDetailsService = customUserDetailsService; - } - - @Bean - PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder) - throws Exception { - AuthenticationManagerBuilder authenticationManagerBuilder = http - .getSharedObject(AuthenticationManagerBuilder.class); - authenticationManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder); - return authenticationManagerBuilder.build(); - } - - @Bean - SecurityFilterChain securityFitlerChain(HttpSecurity http) throws Exception { - http.csrf(csrf -> csrf.disable()); - - http = http.sessionManagement(management -> management - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - - http = http.exceptionHandling(handling -> handling.authenticationEntryPoint((request, response, ex) -> { - response.sendError( - HttpServletResponse.SC_UNAUTHORIZED, - ex.getMessage()); - })); - - http.authorizeRequests( - (authorize) -> authorize.antMatchers("/auth/**").permitAll().anyRequest().authenticated()); - - http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } -} \ No newline at end of file diff --git a/src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java b/src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java deleted file mode 100644 index b7411e6..0000000 --- a/src/main/java/sg/com/smartinventory/serviceImpls/CustomUserDetailsService.java +++ /dev/null @@ -1,35 +0,0 @@ -package sg.com.smartinventory.serviceImpls; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.stereotype.Service; - -import sg.com.smartinventory.entities.User; -import sg.com.smartinventory.repositories.UserRepository; - -@Service -public class CustomUserDetailsService implements UserDetailsService { - - private final UserRepository userRepository; - - public CustomUserDetailsService(UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Override - public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { - User user = userRepository.findByUsername(username); - List roles = new ArrayList<>(); - roles.add("USER"); - UserDetails userDetails = org.springframework.security.core.userdetails.User.builder() - .username(user.getUsername()) - .password(user.getPassword()) - .roles(roles.toArray(new String[0])) - .build(); - return userDetails; - } -} \ No newline at end of file diff --git a/src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java b/src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java deleted file mode 100644 index ae73c98..0000000 --- a/src/test/java/sg/com/smartinventory/entities/ErrorResponseTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package sg.com.smartinventory.entities; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import org.mockito.InjectMocks; -import org.mockito.Mock; - -public class ErrorResponseTest { - @DisplayName("Error Response Test") - @Test - public void errorResponseTest() throws Exception { - - } -} \ No newline at end of file