From 760bcb5245cfe5afe9ca632f11309897f25a164b Mon Sep 17 00:00:00 2001 From: Hai Phuc Nguyen Date: Sat, 21 Dec 2024 11:31:49 -0800 Subject: [PATCH 1/2] Update --- .../java/io/flowinquiry/FlowInquiryApp.java | 5 +- .../config/AsyncConfiguration.java | 3 +- .../config/SecurityJwtConfiguration.java | 8 ++- .../StaticResourcesWebConfiguration.java | 4 +- .../usermanagement/service/UserService.java | 10 ++-- .../main/resources/config/application-dev.yml | 4 +- .../StaticResourcesWebConfigurerTest.java | 5 +- .../io/flowinquiry/service/UserServiceIT.java | 6 +-- tools/platform/build.gradle | 1 + .../io/flowinquiry/platform/utils/Random.java | 54 +++++++++++++++++++ 10 files changed, 74 insertions(+), 26 deletions(-) create mode 100644 tools/platform/src/main/java/io/flowinquiry/platform/utils/Random.java diff --git a/server/src/main/java/io/flowinquiry/FlowInquiryApp.java b/server/src/main/java/io/flowinquiry/FlowInquiryApp.java index 78862018..43322b84 100644 --- a/server/src/main/java/io/flowinquiry/FlowInquiryApp.java +++ b/server/src/main/java/io/flowinquiry/FlowInquiryApp.java @@ -15,6 +15,7 @@ import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; +import java.util.Map; import java.util.Optional; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; @@ -28,7 +29,6 @@ import org.springframework.core.annotation.Order; import org.springframework.core.env.Environment; import org.springframework.transaction.annotation.Transactional; -import tech.jhipster.config.DefaultProfileUtil; @SpringBootApplication @EnableConfigurationProperties({ @@ -85,7 +85,8 @@ public static void main(String[] args) { SpringApplication app = new SpringApplication(FlowInquiryApp.class); - DefaultProfileUtil.addDefaultProfile(app); + app.setDefaultProperties( + Map.of("spring.profiles.default", FlowInquiryProfiles.SPRING_PROFILE_DEVELOPMENT)); Environment env = app.run(args).getEnvironment(); logApplicationStartup(env); } diff --git a/server/src/main/java/io/flowinquiry/config/AsyncConfiguration.java b/server/src/main/java/io/flowinquiry/config/AsyncConfiguration.java index 10f76809..90a390f7 100644 --- a/server/src/main/java/io/flowinquiry/config/AsyncConfiguration.java +++ b/server/src/main/java/io/flowinquiry/config/AsyncConfiguration.java @@ -14,7 +14,6 @@ import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.security.task.DelegatingSecurityContextTaskExecutor; -import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor; @Configuration @EnableAsync @@ -38,7 +37,7 @@ public Executor getAsyncExecutor() { executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix()); - return new ExceptionHandlingAsyncTaskExecutor(executor); + return executor; } @Bean(name = "auditLogExecutor") diff --git a/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java b/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java index abb9bcca..95233b21 100644 --- a/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java +++ b/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java @@ -12,10 +12,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; +import org.springframework.security.oauth2.jwt.*; @Configuration public class SecurityJwtConfiguration { @@ -31,7 +28,8 @@ public JwtDecoder jwtDecoder(SecurityMetersService metersService) { NimbusJwtDecoder.withSecretKey(getSecretKey()).macAlgorithm(JWT_ALGORITHM).build(); return token -> { try { - return jwtDecoder.decode(token); + // return jwtDecoder.decode(token); + throw new JwtException("Jwt expired at"); } catch (Exception e) { if (e.getMessage().contains("Invalid signature")) { metersService.trackTokenInvalidSignature(); diff --git a/server/src/main/java/io/flowinquiry/config/StaticResourcesWebConfiguration.java b/server/src/main/java/io/flowinquiry/config/StaticResourcesWebConfiguration.java index 989f28dc..11c56e9f 100644 --- a/server/src/main/java/io/flowinquiry/config/StaticResourcesWebConfiguration.java +++ b/server/src/main/java/io/flowinquiry/config/StaticResourcesWebConfiguration.java @@ -43,10 +43,10 @@ protected void initializeResourceHandler( } protected CacheControl getCacheControl() { - return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic(); + return CacheControl.maxAge(getHttpCacheProperty(), TimeUnit.DAYS).cachePublic(); } - private int getJHipsterHttpCacheProperty() { + private int getHttpCacheProperty() { return flowInquiryProperties.getHttp().getCache().getTimeToLiveInDays(); } } diff --git a/server/src/main/java/io/flowinquiry/modules/usermanagement/service/UserService.java b/server/src/main/java/io/flowinquiry/modules/usermanagement/service/UserService.java index 4abfd7c5..c5c39bd2 100644 --- a/server/src/main/java/io/flowinquiry/modules/usermanagement/service/UserService.java +++ b/server/src/main/java/io/flowinquiry/modules/usermanagement/service/UserService.java @@ -12,6 +12,7 @@ import io.flowinquiry.modules.usermanagement.service.dto.UserKey; import io.flowinquiry.modules.usermanagement.service.event.DeleteUserEvent; import io.flowinquiry.modules.usermanagement.service.mapper.UserMapper; +import io.flowinquiry.platform.utils.Random; import io.flowinquiry.query.QueryDTO; import io.flowinquiry.security.Constants; import io.flowinquiry.security.SecurityUtils; @@ -30,7 +31,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import tech.jhipster.security.RandomUtil; /** Service class for managing users. */ @Service @@ -99,7 +99,7 @@ public Optional requestPasswordReset(String mail) { .filter(user -> Objects.equals(user.getStatus(), UserStatus.ACTIVE)) .map( user -> { - user.setResetKey(RandomUtil.generateResetKey()); + user.setResetKey(Random.generateResetKey()); user.setResetDate(Instant.now()); return user; }) @@ -130,7 +130,7 @@ public User registerUser(UserDTO userDTO, String password) { // new user is not active newUser.setStatus(UserStatus.PENDING); // new user gets registration key - newUser.setActivationKey(RandomUtil.generateActivationKey()); + newUser.setActivationKey(Random.generateActivationKey()); Set authorities = new HashSet<>(); authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add); newUser.setAuthorities(authorities); @@ -161,9 +161,9 @@ public UserDTO createUser(UserDTO userDTO) { } else { user.setLangKey(userDTO.getLangKey()); } - String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword()); + String encryptedPassword = passwordEncoder.encode(Random.generatePassword()); user.setPassword(encryptedPassword); - user.setResetKey(RandomUtil.generateResetKey()); + user.setResetKey(Random.generateResetKey()); user.setResetDate(Instant.now()); user.setStatus(UserStatus.PENDING); diff --git a/server/src/main/resources/config/application-dev.yml b/server/src/main/resources/config/application-dev.yml index 33003213..c8b3aa97 100644 --- a/server/src/main/resources/config/application-dev.yml +++ b/server/src/main/resources/config/application-dev.yml @@ -48,11 +48,9 @@ flowinquiry: cors: # Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+) allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000,http://localhost:9060,https://localhost:9060' - # Enable CORS when running in GitHub Codespaces - allowed-origin-patterns: 'https://*.githubpreview.dev' allowed-methods: '*' allowed-headers: '*' - exposed-headers: 'Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params' + exposed-headers: 'Authorization,Link,X-Total-Count' allow-credentials: true max-age: 1800 security: diff --git a/server/src/test/java/io/flowinquiry/config/StaticResourcesWebConfigurerTest.java b/server/src/test/java/io/flowinquiry/config/StaticResourcesWebConfigurerTest.java index 73611fe4..d29d36ed 100644 --- a/server/src/test/java/io/flowinquiry/config/StaticResourcesWebConfigurerTest.java +++ b/server/src/test/java/io/flowinquiry/config/StaticResourcesWebConfigurerTest.java @@ -13,7 +13,6 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import tech.jhipster.config.JHipsterDefaults; class StaticResourcesWebConfigurerTest { @@ -62,9 +61,7 @@ void shouldInitializeResourceHandlerWithCacheControlAndLocations() { @Test void shouldCreateCacheControlBasedOnJhipsterDefaultProperties() { - CacheControl cacheExpected = - CacheControl.maxAge(JHipsterDefaults.Http.Cache.timeToLiveInDays, TimeUnit.DAYS) - .cachePublic(); + CacheControl cacheExpected = CacheControl.maxAge(1461, TimeUnit.DAYS).cachePublic(); assertThat(staticResourcesWebConfiguration.getCacheControl()) .extracting(CacheControl::getHeaderValue) .isEqualTo(cacheExpected.getHeaderValue()); diff --git a/server/src/test/java/io/flowinquiry/service/UserServiceIT.java b/server/src/test/java/io/flowinquiry/service/UserServiceIT.java index 7445d62b..2053a593 100644 --- a/server/src/test/java/io/flowinquiry/service/UserServiceIT.java +++ b/server/src/test/java/io/flowinquiry/service/UserServiceIT.java @@ -10,6 +10,7 @@ import io.flowinquiry.modules.usermanagement.service.UserService; import io.flowinquiry.modules.usermanagement.service.dto.ResourcePermissionDTO; import io.flowinquiry.modules.usermanagement.service.dto.UserDTO; +import io.flowinquiry.platform.utils.Random; import java.time.Instant; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; @@ -25,7 +26,6 @@ import org.springframework.data.auditing.DateTimeProvider; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.transaction.annotation.Transactional; -import tech.jhipster.security.RandomUtil; /** Integration tests for {@link UserService}. */ @IntegrationTest @@ -110,7 +110,7 @@ void assertThatOnlyActivatedUserCanRequestPasswordReset() { @Transactional void assertThatResetKeyMustNotBeOlderThan24Hours() { Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); - String resetKey = RandomUtil.generateResetKey(); + String resetKey = Random.generateResetKey(); user.setStatus(UserStatus.ACTIVE); user.setResetDate(daysAgo); user.setResetKey(resetKey); @@ -142,7 +142,7 @@ void assertThatResetKeyMustBeValid() { void assertThatUserCanResetPassword() { String oldPassword = user.getPassword(); Instant daysAgo = Instant.now().minus(2, ChronoUnit.HOURS); - String resetKey = RandomUtil.generateResetKey(); + String resetKey = Random.generateResetKey(); user.setStatus(UserStatus.ACTIVE); user.setResetDate(daysAgo); user.setResetKey(resetKey); diff --git a/tools/platform/build.gradle b/tools/platform/build.gradle index 6548ca0f..91947784 100644 --- a/tools/platform/build.gradle +++ b/tools/platform/build.gradle @@ -10,6 +10,7 @@ repositories { dependencies { implementation(libs.bundles.logback) + implementation("org.apache.commons:commons-lang3:3.17.0") testImplementation platform(libs.junit.bom) testImplementation(libs.bundles.junit) diff --git a/tools/platform/src/main/java/io/flowinquiry/platform/utils/Random.java b/tools/platform/src/main/java/io/flowinquiry/platform/utils/Random.java new file mode 100644 index 00000000..7eaa3d65 --- /dev/null +++ b/tools/platform/src/main/java/io/flowinquiry/platform/utils/Random.java @@ -0,0 +1,54 @@ +package io.flowinquiry.platform.utils; + +import org.apache.commons.lang3.RandomStringUtils; + +import java.security.SecureRandom; + +public class Random { + private static final int DEF_COUNT = 20; + + private static final SecureRandom SECURE_RANDOM; + + static { + SECURE_RANDOM = new SecureRandom(); + SECURE_RANDOM.nextBytes(new byte[64]); + } + + private Random() {} + + /** + *

generateRandomAlphanumericString.

+ * + * @return a {@link java.lang.String} object. + */ + public static String generateRandomAlphanumericString() { + return RandomStringUtils.random(DEF_COUNT, 0, 0, true, true, null, SECURE_RANDOM); + } + + /** + * Generate a password. + * + * @return the generated password. + */ + public static String generatePassword() { + return generateRandomAlphanumericString(); + } + + /** + * Generate an activation key. + * + * @return the generated activation key. + */ + public static String generateActivationKey() { + return generateRandomAlphanumericString(); + } + + /** + * Generate a reset key. + * + * @return the generated reset key. + */ + public static String generateResetKey() { + return generateRandomAlphanumericString(); + } +} From 276b5901ab291b225e71de6403dfd26cb1871068 Mon Sep 17 00:00:00 2001 From: Hai Phuc Nguyen Date: Sat, 21 Dec 2024 17:14:42 -0800 Subject: [PATCH 2/2] Update --- ...omBearerTokenAuthenticationEntryPoint.java | 40 +++++++++++++++++++ .../config/SecurityConfiguration.java | 3 +- .../config/SecurityJwtConfiguration.java | 3 +- .../main/resources/config/application-dev.yml | 3 +- 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 server/src/main/java/io/flowinquiry/config/CustomBearerTokenAuthenticationEntryPoint.java diff --git a/server/src/main/java/io/flowinquiry/config/CustomBearerTokenAuthenticationEntryPoint.java b/server/src/main/java/io/flowinquiry/config/CustomBearerTokenAuthenticationEntryPoint.java new file mode 100644 index 00000000..673087bd --- /dev/null +++ b/server/src/main/java/io/flowinquiry/config/CustomBearerTokenAuthenticationEntryPoint.java @@ -0,0 +1,40 @@ +package io.flowinquiry.config; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.http.HttpStatus; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; +import org.springframework.security.web.AuthenticationEntryPoint; + +/** Custom handler for token authentication */ +public class CustomBearerTokenAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private final BearerTokenAuthenticationEntryPoint delegate = + new BearerTokenAuthenticationEntryPoint(); + + @Override + public void commence( + HttpServletRequest request, + HttpServletResponse response, + org.springframework.security.core.AuthenticationException authException) + throws IOException { + + // Add CORS headers + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Allow-Credentials", "true"); + response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + response.addHeader("Access-Control-Allow-Headers", "Authorization, Content-Type"); + + // Delegate to the default implementation + delegate.commence(request, response, authException); + + // Optional: Add additional details in the response body + if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) { + response.setContentType("application/json"); + response.getWriter() + .write( + "{\"error\": \"Unauthorized\", \"message\": \"Token expired or invalid.\"}"); + } + } +} diff --git a/server/src/main/java/io/flowinquiry/config/SecurityConfiguration.java b/server/src/main/java/io/flowinquiry/config/SecurityConfiguration.java index be24a6ce..cc1b30ea 100644 --- a/server/src/main/java/io/flowinquiry/config/SecurityConfiguration.java +++ b/server/src/main/java/io/flowinquiry/config/SecurityConfiguration.java @@ -13,7 +13,6 @@ 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.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; @@ -102,7 +101,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Buil exceptions -> exceptions .authenticationEntryPoint( - new BearerTokenAuthenticationEntryPoint()) + new CustomBearerTokenAuthenticationEntryPoint()) .accessDeniedHandler(new BearerTokenAccessDeniedHandler())); return http.build(); } diff --git a/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java b/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java index 95233b21..4bc90049 100644 --- a/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java +++ b/server/src/main/java/io/flowinquiry/config/SecurityJwtConfiguration.java @@ -28,8 +28,7 @@ public JwtDecoder jwtDecoder(SecurityMetersService metersService) { NimbusJwtDecoder.withSecretKey(getSecretKey()).macAlgorithm(JWT_ALGORITHM).build(); return token -> { try { - // return jwtDecoder.decode(token); - throw new JwtException("Jwt expired at"); + return jwtDecoder.decode(token); } catch (Exception e) { if (e.getMessage().contains("Invalid signature")) { metersService.trackTokenInvalidSignature(); diff --git a/server/src/main/resources/config/application-dev.yml b/server/src/main/resources/config/application-dev.yml index c8b3aa97..b6e76cbb 100644 --- a/server/src/main/resources/config/application-dev.yml +++ b/server/src/main/resources/config/application-dev.yml @@ -46,8 +46,7 @@ flowinquiry: base_url: http://127.0.0.1:3000 # CORS is only enabled by default with the "dev" profile cors: - # Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+) - allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000,http://localhost:9060,https://localhost:9060' + allowed-origins: 'http://localhost:3000,http://localhost:8080' allowed-methods: '*' allowed-headers: '*' exposed-headers: 'Authorization,Link,X-Total-Count'