Skip to content

Commit

Permalink
refactor: optimize Dockerfile commands and improve security configura…
Browse files Browse the repository at this point in the history
…tion methods
  • Loading branch information
LeonardoMeireles55 committed Feb 4, 2025
1 parent 46b4435 commit b7e4bfc
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 262 deletions.
6 changes: 2 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ COPY src /app/src
COPY pom.xml /app

WORKDIR /app
RUN mvn clean package -DskipTests -U \
&& rm -rf /root/.m2 \
&& rm -rf /app/src
RUN mvn clean package -DskipTests -U && rm -rf /root/.m2 && rm -rf /app/src

# Run stage
FROM eclipse-temurin:21-jre-alpine
Expand All @@ -19,4 +17,4 @@ COPY --from=build /app/target/QualityLabPro-0.8.jar ./app.jar
ENV SPRING_PROFILES_ACTIVE=prod \
SERVER_PORT=8080

EXPOSE ${SERVER_PORT}
EXPOSE ${SERVER_PORT}
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
package leonardo.labutilities.qualitylabpro.configs.docs;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.security.SecurityScheme;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@OpenAPIDefinition
public class SpringDocConfiguration {
@Bean
public OpenAPI customOpenAPI() {
OpenAPI customOpenAPI() {
return new OpenAPI()
.components(new Components().addSecuritySchemes("bearer-key",
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer")
.bearerFormat("JWT")))
new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer")
.bearerFormat("JWT")))
.info(new Info().title("QualityLab-Pro API").version("2.0").description(
"REST API for operations on analytical test specifications and internal " +
"control data")
.contact(new Contact().name("Leonardo Meireles")
.email("[email protected]"))
.license(new License().name("Apache 2.0")
.url("http://labutilities/api/license")));
"REST API for operations on analytical test specifications and internal "
+ "control data")
.contact(new Contact().name("Leonardo Meireles")
.email("[email protected]"))
.license(new License().name("Apache 2.0")
.url("http://labutilities/api/license")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.format.FormatterRegistry;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import leonardo.labutilities.qualitylabpro.utils.components.StringToLocalDateTimeConverter;

Expand All @@ -18,7 +19,7 @@ public WebConfig(StringToLocalDateTimeConverter dateTimeConverter) {
}

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(dateTimeConverter);
public void addFormatters(@NonNull FormatterRegistry registry) {
registry.addConverter(this.dateTimeConverter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ public class CorsConfig implements WebMvcConfigurer {
public void addCorsMappings(@NonNull CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000", "https://quality-lab-pro.vercel.app",
"https://www.lab-spec.systems", "https://lab-spec.systems",
"https://68.183.141.155", "https://leomeireles-dev.xyz")

"https://www.lab-spec.systems", "https://lab-spec.systems")

.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD")
.allowedHeaders("*").exposedHeaders("Authorization").allowCredentials(true)
.maxAge(3600);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package leonardo.labutilities.qualitylabpro.configs.security;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
Expand All @@ -15,77 +14,67 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfiguration {

private final SecurityFilter securityFilter;
private final SecurityFilter securityFilter;

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable).cors(Customizer.withDefaults())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(req -> {
// Public endpoints
req.requestMatchers(HttpMethod.POST, "/users/sign-in").permitAll();
req.requestMatchers(HttpMethod.POST, "/users/sign-up").permitAll();
req.requestMatchers(HttpMethod.POST, "/hematology-analytics/**").permitAll();
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(AbstractHttpConfigurer::disable).cors(Customizer.withDefaults())
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(req -> {
// Public endpoints
req.requestMatchers(HttpMethod.POST, "/users/sign-in").permitAll();
req.requestMatchers(HttpMethod.POST, "/users/sign-up").permitAll();
req.requestMatchers(HttpMethod.POST, "/hematology-analytics/**").permitAll();

// Swagger/OpenAPI endpoints
req.requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**")
.permitAll();
// Swagger/OpenAPI endpoints
req.requestMatchers("/v3/api-docs/**", "/swagger-ui.html", "/swagger-ui/**")
.permitAll();

// Health check endpoints
req.requestMatchers("/actuator/**")
.permitAll();
// Health check endpoints
req.requestMatchers("/actuator/**").permitAll();

// Admin-only endpoints
req.requestMatchers(HttpMethod.DELETE, "/generic-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.DELETE, "/biochemistry-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.DELETE, "/hematology-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.DELETE, "/coagulation-analytics/**")
.hasRole("ADMIN");
// Admin-only endpoints
req.requestMatchers(HttpMethod.DELETE, "/generic-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.DELETE, "/biochemistry-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.DELETE, "/hematology-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.DELETE, "/coagulation-analytics/**").hasRole("ADMIN");

// Add PUT and PATCH restrictions for admin
req.requestMatchers(HttpMethod.PUT, "/generic-analytics/**").hasRole("ADMIN");
// Add PUT and PATCH restrictions for admin
req.requestMatchers(HttpMethod.PUT, "/generic-analytics/**").hasRole("ADMIN");

req.requestMatchers(HttpMethod.PUT, "/biochemistry-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.PUT, "/hematology-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.PUT, "/coagulation-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.PUT, "/biochemistry-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.PUT, "/hematology-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.PUT, "/coagulation-analytics/**").hasRole("ADMIN");

req.requestMatchers(HttpMethod.PATCH, "/generic-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.PATCH, "/generic-analytics/**").hasRole("ADMIN");

req.requestMatchers(HttpMethod.PATCH, "/biochemistry-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.PATCH, "/hematology-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.PATCH, "/coagulation-analytics/**")
.hasRole("ADMIN");
req.requestMatchers(HttpMethod.PATCH, "/biochemistry-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.PATCH, "/hematology-analytics/**").hasRole("ADMIN");
req.requestMatchers(HttpMethod.PATCH, "/coagulation-analytics/**").hasRole("ADMIN");

req.requestMatchers(HttpMethod.DELETE, "/users/**");
req.requestMatchers(HttpMethod.DELETE, "/users/**");

// All other endpoints require authentication
req.anyRequest().authenticated();
}).addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
// All other endpoints require authentication
req.anyRequest().authenticated();
}).addFilterBefore(this.securityFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}

@Bean
public AuthenticationManager authMenager(AuthenticationConfiguration configuration)
throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
AuthenticationManager authMenager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package leonardo.labutilities.qualitylabpro.configs.security;

import java.io.IOException;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import leonardo.labutilities.qualitylabpro.repositories.UserRepository;
import leonardo.labutilities.qualitylabpro.services.authentication.TokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@RequiredArgsConstructor
Expand All @@ -23,15 +22,15 @@ public class SecurityFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
@NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
throws IOException {
try {
var tokenJWT = getToken(request);
var tokenJWT = this.getToken(request);
if (tokenJWT != null) {
var subject = tokenService.getSubject(tokenJWT);
var users = userRepository.getReferenceByUsername(subject);
var subject = this.tokenService.getSubject(tokenJWT);
var users = this.userRepository.getReferenceOneByUsername(subject);
var authentication = new UsernamePasswordAuthenticationToken(users, null,
users.getAuthorities());
users.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package leonardo.labutilities.qualitylabpro.repositories;

import jakarta.persistence.QueryHint;
import jakarta.transaction.Transactional;
import leonardo.labutilities.qualitylabpro.dtos.analytics.AnalyticsDTO;
import leonardo.labutilities.qualitylabpro.entities.Analytic;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -12,9 +10,10 @@
import org.springframework.data.jpa.repository.QueryHints;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.List;
import jakarta.persistence.QueryHint;
import jakarta.transaction.Transactional;
import leonardo.labutilities.qualitylabpro.dtos.analytics.AnalyticsDTO;
import leonardo.labutilities.qualitylabpro.entities.Analytic;

@Repository
public interface AnalyticsRepository extends JpaRepository<Analytic, Long> {
Expand Down Expand Up @@ -65,6 +64,7 @@ List<Analytic> findByNameAndLevel(Pageable pageable, @Param("name") String name,
List<Analytic> findByNameAndLevelAndLevelLot(Pageable pageable, @Param("name") String name,
@Param("level") String level, @Param("levelLot") String levelLot);


@QueryHints({@QueryHint(name = "org.hibernate.readOnly", value = "true"),
@QueryHint(name = "org.hibernate.fetchSize", value = "50"),
@QueryHint(name = "org.hibernate.cacheable", value = "true")})
Expand All @@ -76,6 +76,8 @@ List<Analytic> findByNameAndLevelAndDateBetween(@Param("name") String name,
@Param("level") String level, @Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate, Pageable pageable);



// Fetch Analytics by Multiple Names and Date
@QueryHints({@QueryHint(name = "org.hibernate.readOnly", value = "true"),
@QueryHint(name = "org.hibernate.fetchSize", value = "50"),
Expand All @@ -89,6 +91,7 @@ Page<AnalyticsDTO> findByNameInAndLevelAndDateBetween(@Param("names") List<Strin
@Param("level") String level, @Param("startDate") LocalDateTime startDate,
@Param("endDate") LocalDateTime endDate, Pageable pageable);


@Query(value = """
SELECT ga FROM generic_analytics ga WHERE ga.name IN (:names) AND ga.date BETWEEN :startDate AND :endDate
""")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package leonardo.labutilities.qualitylabpro.repositories;

import jakarta.transaction.Transactional;
import leonardo.labutilities.qualitylabpro.entities.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Repository;
import jakarta.transaction.Transactional;
import leonardo.labutilities.qualitylabpro.entities.User;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
UserDetails getReferenceByUsername(String username);
boolean existsByUsername(String name);

boolean existsByEmail(String email);

UserDetails getReferenceOneByUsername(String username);

User findByUsername(String username);
User findOneByUsername(String username);

User findByEmail(String email);
User findOneByEmail(String email);

User findByUsernameOrEmail(String username, String email);
User findOneByUsernameOrEmail(String username, String email);

boolean existsByUsernameOrEmail(String username, String email);

Expand All @@ -26,16 +30,12 @@ public interface UserRepository extends JpaRepository<User, Long> {

@Transactional
@Modifying
@Query("UPDATE users u SET u.password = ?2 WHERE u.username = ?1")
@Query("UPDATE users u SET u.password = :newPassword WHERE u.username = :username")
void setPasswordWhereByUsername(String username, String newPassword);

@Transactional
@Modifying
@Query("UPDATE users u SET u.password = ?2 WHERE u.email = ?1")
@Query("UPDATE users u SET u.password = :newPassword WHERE u.email = :username")
void setPasswordWhereByEmail(String email, String newPassword);


boolean existsByUsername(String name);

boolean existsByEmail(String email);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package leonardo.labutilities.qualitylabpro.services.authentication;

import leonardo.labutilities.qualitylabpro.repositories.UserRepository;
import lombok.RequiredArgsConstructor;
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 leonardo.labutilities.qualitylabpro.repositories.UserRepository;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Service
Expand All @@ -15,7 +15,7 @@ public class AuthenticationService implements UserDetailsService {

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userRepository.getReferenceByUsername(username);
var user = this.userRepository.getReferenceOneByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found: " + username);
}
Expand Down
Loading

0 comments on commit b7e4bfc

Please sign in to comment.