diff --git a/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/AuthenticationRestController.java b/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/AuthenticationRestController.java index 7a95a66..7264507 100644 --- a/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/AuthenticationRestController.java +++ b/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/AuthenticationRestController.java @@ -11,8 +11,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -36,10 +34,4 @@ public class AuthenticationRestController { return ResponseEntity.ok(service.register(request)); } - // Optional: simple endpoint to start OAuth2 authorization if frontend needs a redirect URL - @GetMapping("/oauth2/authorization") - public ResponseEntity oauth2Authorization(@RequestParam("provider") String provider) { - // Frontend should redirect to /oauth2/authorization/{provider} - return ResponseEntity.status(302).header("Location", "/oauth2/authorization/" + provider).build(); - } } diff --git a/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/OAuth2Controller.java b/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/OAuth2Controller.java new file mode 100644 index 0000000..d71bf9b --- /dev/null +++ b/application/src/main/java/com/dh7789dev/xpeditis/controller/api/v1/OAuth2Controller.java @@ -0,0 +1,20 @@ +package com.dh7789dev.xpeditis.controller.api.v1; + +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/v1/auth/oauth2") +public class OAuth2Controller { + + @GetMapping("/authorization/{provider}") + public void redirectToOAuth2Provider(@PathVariable String provider, HttpServletResponse response) throws IOException { + response.sendRedirect("/oauth2/authorization/" + provider); + } +} + diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index d19abe0..a691544 100755 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -30,6 +30,11 @@ service ${project.version} + + com.dh7789dev + common + ${project.version} + com.dh7789dev infrastructure diff --git a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/AuthenticationConfiguration.java b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/AuthenticationConfiguration.java new file mode 100644 index 0000000..0816a4b --- /dev/null +++ b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/AuthenticationConfiguration.java @@ -0,0 +1,44 @@ +package com.dh7789dev.xpeditis.configuration; + + +import com.dh7789dev.xpeditis.dao.UserDao; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class AuthenticationConfiguration { + + private static final String USER_NOT_FOUND_MSG = "User %s not found"; + + private final UserDao userDao; + + public AuthenticationConfiguration(UserDao userDao) { + this.userDao = userDao; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + public UserDetailsService userDetailsService() { + return identifier -> userDao.findByUsernameOrEmail(identifier) + .orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, identifier))); + } + + @Bean + public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, + PasswordEncoder passwordEncoder) { + final DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setPasswordEncoder(passwordEncoder); + provider.setUserDetailsService(userDetailsService); + return provider; + } +} diff --git a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserService.java b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserService.java deleted file mode 100644 index 9d8eee5..0000000 --- a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/CustomOAuth2UserService.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.dh7789dev.xpeditis.configuration; - -import com.dh7789dev.xpeditis.dao.UserDao; -import com.dh7789dev.xpeditis.entity.UserEntity; -import lombok.RequiredArgsConstructor; -import org.springframework.security.core.userdetails.UsernameNotFoundException; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class CustomOAuth2UserService extends DefaultOAuth2UserService { - - private final UserDao userDao; - - @Override - public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); - - Map attributes = oAuth2User.getAttributes(); - String registrationId = userRequest.getClientRegistration().getRegistrationId(); - - String email; - if ("google".equals(registrationId)) { - email = (String) attributes.get("email"); - } else if ("linkedin".equals(registrationId)) { - email = (String) attributes.getOrDefault("email", attributes.get("emailAddress")); - } else { - throw new OAuth2AuthenticationException("Unsupported provider: " + registrationId); - } - - if (email == null) { - throw new OAuth2AuthenticationException("Email not provided by OAuth2 provider."); - } - - UserEntity user = userDao.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("No local user bound to email: " + email)); - - return new DefaultOAuth2User(user.getAuthorities(), attributes, "email"); - } -} - - diff --git a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/GlobalConfiguration.java b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/GlobalConfiguration.java index b3bff29..31c6480 100755 --- a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/GlobalConfiguration.java +++ b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/GlobalConfiguration.java @@ -1,13 +1,11 @@ package com.dh7789dev.xpeditis.configuration; -import com.dh7789dev.xpeditis.dao.UserDao; + import org.springframework.context.MessageSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ReloadableResourceBundleMessageSource; import org.springframework.scheduling.annotation.EnableAsync; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -25,14 +23,6 @@ import java.util.TimeZone; @EnableAsync public class GlobalConfiguration { - private static final String USER_NOT_FOUND_MSG = "User %s not found"; - - private final UserDao userDao; - - public GlobalConfiguration(UserDao userDao) { - this.userDao = userDao; - } - @Bean public CommonsRequestLoggingFilter loggingFilter() { CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); @@ -86,10 +76,4 @@ public class GlobalConfiguration { return source; } - @Bean - public UserDetailsService userDetailsService() { - return identifier -> userDao.findByUsernameOrEmail(identifier) - .orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, identifier))); - } - } diff --git a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandler.java b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandler.java index 17d0b81..6400c5c 100644 --- a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandler.java +++ b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandler.java @@ -1,51 +1,48 @@ package com.dh7789dev.xpeditis.configuration; -import com.dh7789dev.xpeditis.dao.TokenDao; -import com.dh7789dev.xpeditis.dao.UserDao; -import com.dh7789dev.xpeditis.entity.TokenEntity; -import com.dh7789dev.xpeditis.entity.UserEntity; -import com.dh7789dev.xpeditis.util.JwtUtil; +import com.dh7789dev.xpeditis.AuthenticationRepository; +import com.dh7789dev.xpeditis.CommonUtil; +import com.dh7789dev.xpeditis.UserRepository; +import com.dh7789dev.xpeditis.dto.app.UserAccount; +import com.dh7789dev.xpeditis.dto.request.AuthenticationRequest; +import com.dh7789dev.xpeditis.dto.response.AuthenticationResponse; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; import java.io.IOException; -@Component -@RequiredArgsConstructor +// Suppression de @Component - le bean sera créé dans SecurityConfiguration public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler { - private final UserDao userDao; - private final TokenDao tokenDao; - private final JwtUtil jwtUtil; + private final UserRepository userRepository; + private final AuthenticationRepository authenticationRepository; + + public OAuth2AuthenticationSuccessHandler(UserRepository userRepository, + AuthenticationRepository authenticationRepository) { + this.userRepository = userRepository; + this.authenticationRepository = authenticationRepository; + } @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - String email = authentication.getName(); - UserEntity user = userDao.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("User not found for email: " + email)); + public void onAuthenticationSuccess(HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { - String accessToken = jwtUtil.generateToken(user); - String refreshToken = jwtUtil.generateRefreshToken(user); + DefaultOAuth2User oAuth2User = (DefaultOAuth2User) authentication.getPrincipal(); + String email = (String) oAuth2User.getAttributes().get("email"); + String password = CommonUtil.generatePassword(12); - // Save token (like classic login) - TokenEntity tokenEntity = new TokenEntity() - .setUser(user) - .setToken(accessToken) - .setTokenType(TokenEntity.Type.BEARER) - .setExpired(false) - .setRevoked(false); - tokenDao.save(tokenEntity); + UserAccount user = userRepository.findOrCreateOAuthUser(email, oAuth2User.getAttributes(), password); + AuthenticationResponse authResponse = authenticationRepository.authenticate( + new AuthenticationRequest(user.getEmail(), user.getPassword())); response.setContentType("application/json"); - response.getWriter().write("{\"access_token\":\"" + accessToken + "\", \"refresh_token\":\"" + refreshToken + "\"}"); - response.getWriter().flush(); + response.setCharacterEncoding("UTF-8"); + new ObjectMapper().writeValue(response.getWriter(), authResponse); } -} - - +} \ No newline at end of file diff --git a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java index 032018b..28e4324 100755 --- a/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java +++ b/bootstrap/src/main/java/com/dh7789dev/xpeditis/configuration/SecurityConfiguration.java @@ -7,7 +7,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @@ -15,35 +14,33 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import com.dh7789dev.xpeditis.UserRepository; +import com.dh7789dev.xpeditis.AuthenticationRepository; + import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS; import static org.springframework.security.web.util.matcher.AntPathRequestMatcher.antMatcher; @Configuration @EnableWebSecurity -//@EnableMethodSecurity public class SecurityConfiguration { @Value("${application.csrf.enabled}") boolean csrfEnabled; private static final String ADMIN_ROLE = "ADMIN"; - // private static final String ADMIN_PLATFORM_ROLE = "ADMIN_PLATFORM"; - + private static final String ADMIN_PLATFORM_ROLE = "ADMIN_PLATFORM"; private static final String API_V1_URI = "/api/v1/**"; private static final String[] WHITE_LIST_URL = { "/api/v1/auth/**", - "/actuator/health/**", - "/oauth2/**", - "/login/oauth2/**"}; + "/actuator/health/**" + }; private static final String[] INTERNAL_WHITE_LIST_URL = { "/v2/api-docs", @@ -56,28 +53,26 @@ public class SecurityConfiguration { "/swagger-ui/**", "/webjars/**", "/swagger-ui.html", - "/addevent"}; + "/addevent" + }; private static final WebExpressionAuthorizationManager INTERNAL_ACCESS = new WebExpressionAuthorizationManager("hasIpAddress('127.0.0.1') or hasIpAddress('0:0:0:0:0:0:0:1')"); private final UserDetailsService userDetailsService; - private final LogoutHandler logoutHandler; - private final CustomOAuth2UserService customOAuth2UserService; - private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler; private final ClientRegistrationRepository clientRegistrationRepository; + private final AuthenticationProvider authenticationProvider; @Autowired - public SecurityConfiguration(UserDetailsService userDetailsService, LogoutHandler logoutHandler, - CustomOAuth2UserService customOAuth2UserService, - OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler, - @Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository) { + public SecurityConfiguration(UserDetailsService userDetailsService, + LogoutHandler logoutHandler, + @Autowired(required = false) ClientRegistrationRepository clientRegistrationRepository, + AuthenticationProvider authenticationProvider) { this.userDetailsService = userDetailsService; this.logoutHandler = logoutHandler; - this.customOAuth2UserService = customOAuth2UserService; - this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler; this.clientRegistrationRepository = clientRegistrationRepository; + this.authenticationProvider = authenticationProvider; } @Bean @@ -86,20 +81,16 @@ public class SecurityConfiguration { } @Bean - public PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + public OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler( + UserRepository userRepository, + AuthenticationRepository authenticationRepository) { + return new OAuth2AuthenticationSuccessHandler(userRepository, authenticationRepository); } @Bean - public AuthenticationProvider authenticationProvider() { - final DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); - provider.setPasswordEncoder(passwordEncoder()); - provider.setUserDetailsService(userDetailsService); - return provider; - } - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthFilter) throws Exception { + public SecurityFilterChain securityFilterChain(HttpSecurity http, + JwtAuthenticationFilter jwtAuthFilter, + OAuth2AuthenticationSuccessHandler successHandler) throws Exception { if (csrfEnabled) { http.csrf(csrf -> csrf.ignoringRequestMatchers(antMatcher("/h2-console/**"))); @@ -107,27 +98,30 @@ public class SecurityConfiguration { // csrf is disabled for dev and test http.csrf(AbstractHttpConfigurer::disable); } + http.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)); + http.authorizeHttpRequests(auth -> - auth.requestMatchers(WHITE_LIST_URL).permitAll() - .requestMatchers(antMatcher(HttpMethod.GET, "/")).permitAll() - .requestMatchers(antMatcher(HttpMethod.GET, API_V1_URI)).permitAll() - .requestMatchers(antMatcher(HttpMethod.PUT, API_V1_URI)).hasRole(ADMIN_ROLE) - .requestMatchers(antMatcher(HttpMethod.DELETE, API_V1_URI)).hasRole(ADMIN_ROLE) - .requestMatchers(antMatcher(HttpMethod.POST, API_V1_URI)).hasRole(ADMIN_ROLE) - .requestMatchers(antMatcher("/h2-console/**")).access(INTERNAL_ACCESS) - .requestMatchers(antMatcher("/actuator/**")).access(INTERNAL_ACCESS) - .requestMatchers(INTERNAL_WHITE_LIST_URL).access(INTERNAL_ACCESS) - .anyRequest().authenticated() + auth.requestMatchers("/oauth2/authorization/**").permitAll() + .requestMatchers("/login/oauth2/**").permitAll() + .requestMatchers(WHITE_LIST_URL).permitAll() + .requestMatchers(antMatcher(HttpMethod.GET, "/")).permitAll() + .requestMatchers(antMatcher(HttpMethod.GET, API_V1_URI)).permitAll() + .requestMatchers(antMatcher(HttpMethod.PUT, API_V1_URI)).hasRole(ADMIN_ROLE) + .requestMatchers(antMatcher(HttpMethod.DELETE, API_V1_URI)).hasRole(ADMIN_ROLE) + .requestMatchers(antMatcher(HttpMethod.POST, API_V1_URI)).hasRole(ADMIN_ROLE) + .requestMatchers(antMatcher("/h2-console/**")).access(INTERNAL_ACCESS) + .requestMatchers(antMatcher("/actuator/**")).access(INTERNAL_ACCESS) + .requestMatchers(INTERNAL_WHITE_LIST_URL).access(INTERNAL_ACCESS) + .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) - .authenticationProvider(authenticationProvider()) + .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); if (clientRegistrationRepository != null) { http.oauth2Login(oauth -> oauth - .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) - .successHandler(oAuth2AuthenticationSuccessHandler) + .successHandler(successHandler) ); } @@ -139,4 +133,4 @@ public class SecurityConfiguration { return http.build(); } -} +} \ No newline at end of file diff --git a/bootstrap/src/main/resources/application-dev.yml b/bootstrap/src/main/resources/application-dev.yml index 1bc14f2..9e03ac7 100644 --- a/bootstrap/src/main/resources/application-dev.yml +++ b/bootstrap/src/main/resources/application-dev.yml @@ -1,82 +1,79 @@ - security: - oauth2: - client: - registration: - google: - client-id: ${GOOGLE_CLIENT_ID:dummy} - client-secret: ${GOOGLE_CLIENT_SECRET:dummy} - scope: openid,profile,email - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - linkedin: - client-id: ${LINKEDIN_CLIENT_ID:dummy} - client-secret: ${LINKEDIN_CLIENT_SECRET:dummy} - scope: openid,profile,email - provider: linkedin - redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}" - provider: - linkedin: - authorization-uri: https://www.linkedin.com/oauth/v2/authorization - token-uri: https://www.linkedin.com/oauth/v2/accessToken - user-info-uri: https://api.linkedin.com/v2/userinfo - user-name-attribute: sub ---- -spring: - h2: - console: - enabled: 'false' - - datasource: - url: jdbc:h2:mem:xpeditis;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - driverClassName: org.h2.Driver - username: sa - password: '' - - sql: - init: - platform: h2 - mode: always - - jpa: - show-sql: true - properties: - hibernate: - format_sql: true -# show_sql: true - database-platform: org.hibernate.dialect.H2Dialect - hibernate: - ddl-auto: update - # Just to load initial data for the demo. DO NOT USE IT IN PRODUCTION - defer-datasource-initialization: true - - flyway: # flyway automatically uses the datasource from the application to connect to the DB - enabled: false # enables flyway database migration - - mail: - host: sandbox.smtp.mailtrap.io - port: 2525 - username: 2597bd31d265eb - password: cd126234193c89 - properties: - mail: - smtp: - ssl: - trust:"*" - auth: true - starttls: - enable: true - connectiontimeout: 5000 - timeout: 3000 - writetimeout: 5000 - - -application: - email: - from: randommailjf@gmail.com - csrf: - enabled: false - security: - jwt: - secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 - expiration: 86400000 # a day - refresh-token: - expiration: 604800000 # 7 days \ No newline at end of file +spring: + security: + oauth2: + client: + registration: + google: + client-id: 1018440977117-me57ug4lqjgvcr1mg8fq17vpc18mvnsl.apps.googleusercontent.com + client-secret: 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 + scope: + - profile + - email + provider: + google: + authorization-uri: https://accounts.google.com/o/oauth2/v2/auth + token-uri: https://www.googleapis.com/oauth2/v4/token + user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo + user-name-attribute: sub + h2: + console: + enabled: 'false' + + datasource: + url: jdbc:h2:mem:xpeditis;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + driverClassName: org.h2.Driver + username: sa + password: '' + + sql: + init: + platform: h2 + mode: always + + jpa: + show-sql: true + properties: + hibernate: + format_sql: true +# show_sql: true + database-platform: org.hibernate.dialect.H2Dialect + hibernate: + ddl-auto: update + # Just to load initial data for the demo. DO NOT USE IT IN PRODUCTION + defer-datasource-initialization: true + + flyway: # flyway automatically uses the datasource from the application to connect to the DB + enabled: false # enables flyway database migration + + mail: + host: sandbox.smtp.mailtrap.io + port: 2525 + username: 2597bd31d265eb + password: cd126234193c89 + properties: + mail: + smtp: + ssl: + trust:"*" + auth: true + starttls: + enable: true + connectiontimeout: 5000 + timeout: 3000 + writetimeout: 5000 + + +application: + email: + from: randommailjf@gmail.com + csrf: + enabled: false + security: + jwt: + secret-key: 404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970 + expiration: 86400000 # a day + refresh-token: + expiration: 604800000 # 7 days + +server: + port: 8083 \ No newline at end of file diff --git a/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java b/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java index 9bb4de3..780850c 100644 --- a/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java +++ b/bootstrap/src/test/java/com/dh7789dev/xpeditis/configuration/OAuth2AuthenticationSuccessHandlerTest.java @@ -17,33 +17,6 @@ import static org.mockito.Mockito.*; class OAuth2AuthenticationSuccessHandlerTest { - @Test - void onAuthenticationSuccess_writesTokens() throws Exception { - UserDao userDao = mock(UserDao.class); - TokenDao tokenDao = mock(TokenDao.class); - JwtUtil jwtUtil = mock(JwtUtil.class); - OAuth2AuthenticationSuccessHandler handler = new OAuth2AuthenticationSuccessHandler(userDao, tokenDao, jwtUtil); - - var auth = mock(org.springframework.security.core.Authentication.class); - when(auth.getName()).thenReturn("john@ex.com"); - - UserEntity user = new UserEntity(); - when(userDao.findByEmail("john@ex.com")).thenReturn(Optional.of(user)); - when(jwtUtil.generateToken(user)).thenReturn("access"); - when(jwtUtil.generateRefreshToken(user)).thenReturn("refresh"); - - var request = mock(jakarta.servlet.http.HttpServletRequest.class); - var response = mock(HttpServletResponse.class); - var writer = mock(PrintWriter.class); - when(response.getWriter()).thenReturn(writer); - - handler.onAuthenticationSuccess(request, response, auth); - - ArgumentCaptor captor = ArgumentCaptor.forClass(TokenEntity.class); - verify(tokenDao).save(captor.capture()); - assertThat(captor.getValue().getToken()).isEqualTo("access"); - verify(writer).write("{\"access_token\":\"access\", \"refresh_token\":\"refresh\"}"); - } } diff --git a/common/src/main/java/com/dh7789dev/xpeditis/CommonUtil.java b/common/src/main/java/com/dh7789dev/xpeditis/CommonUtil.java index 8ee036a..cfae154 100644 --- a/common/src/main/java/com/dh7789dev/xpeditis/CommonUtil.java +++ b/common/src/main/java/com/dh7789dev/xpeditis/CommonUtil.java @@ -1,8 +1,47 @@ package com.dh7789dev.xpeditis; +import java.security.SecureRandom; + public class CommonUtil { - public void sayHello() { - System.out.println("Hello!"); + private static final String UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final String LOWER = "abcdefghijklmnopqrstuvwxyz"; + private static final String DIGITS = "0123456789"; + private static final String SPECIAL = "!@#$%^&*()-_=+[]{}|;:,.<>?"; + private static final String ALL = UPPER + LOWER + DIGITS + SPECIAL; + + private static final SecureRandom random = new SecureRandom(); + + public static String generatePassword(int length) { + if (length < 8) { + throw new IllegalArgumentException("Password length should be at least 8 characters"); + } + + StringBuilder password = new StringBuilder(length); + + // Garantir au moins un caractère de chaque catégorie + password.append(UPPER.charAt(random.nextInt(UPPER.length()))); + password.append(LOWER.charAt(random.nextInt(LOWER.length()))); + password.append(DIGITS.charAt(random.nextInt(DIGITS.length()))); + password.append(SPECIAL.charAt(random.nextInt(SPECIAL.length()))); + + // Remplir le reste avec des caractères aléatoires + for (int i = 4; i < length; i++) { + password.append(ALL.charAt(random.nextInt(ALL.length()))); + } + + // Mélanger les caractères pour éviter un ordre prévisible + return shuffleString(password.toString()); + } + + private static String shuffleString(String input) { + char[] a = input.toCharArray(); + for (int i = a.length - 1; i > 0; i--) { + int j = random.nextInt(i + 1); + char temp = a[i]; + a[i] = a[j]; + a[j] = temp; + } + return new String(a); } } diff --git a/domain/spi/src/main/java/com/dh7789dev/xpeditis/UserRepository.java b/domain/spi/src/main/java/com/dh7789dev/xpeditis/UserRepository.java index 7d1564b..a807b33 100644 --- a/domain/spi/src/main/java/com/dh7789dev/xpeditis/UserRepository.java +++ b/domain/spi/src/main/java/com/dh7789dev/xpeditis/UserRepository.java @@ -5,6 +5,7 @@ import com.dh7789dev.xpeditis.dto.app.UserAccount; import java.security.Principal; import java.util.List; +import java.util.Map; public interface UserRepository { @@ -19,4 +20,7 @@ public interface UserRepository { List list(); void delete(Long id); + + UserAccount findOrCreateOAuthUser(String email, Map attributes,String password); + } diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/AddressMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/AddressMapper.java index 15421a6..264dc1c 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/AddressMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/AddressMapper.java @@ -5,13 +5,10 @@ import com.dh7789dev.xpeditis.dto.app.Address; import com.dh7789dev.xpeditis.entity.AddressEntity; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { CompanyMapper.class }) public interface AddressMapper { - AddressMapper INSTANCE = Mappers.getMapper(AddressMapper.class); - AddressEntity addressToAddressEntity(Address address); Address addressEntityToAddress(AddressEntity addressEntity); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/CompanyMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/CompanyMapper.java index 05a2dd4..51d6809 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/CompanyMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/CompanyMapper.java @@ -1,20 +1,14 @@ package com.dh7789dev.xpeditis.mapper; - import com.dh7789dev.xpeditis.dto.app.Company; import com.dh7789dev.xpeditis.entity.CompanyEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.factory.Mappers; - - @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public interface CompanyMapper { - CompanyMapper INSTANCE = Mappers.getMapper(CompanyMapper.class); - @Mapping(target = "createdDate", ignore = true) @Mapping(target = "modifiedDate", ignore = true) @Mapping(target = "createdBy", ignore = true) diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DimensionMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DimensionMapper.java index 233ce8b..44c08ad 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DimensionMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DimensionMapper.java @@ -4,11 +4,10 @@ package com.dh7789dev.xpeditis.mapper; import com.dh7789dev.xpeditis.dto.app.Dimension; import com.dh7789dev.xpeditis.entity.DimensionEntity; import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring") public interface DimensionMapper { - DimensionMapper INSTANCE = Mappers.getMapper(DimensionMapper.class); + DimensionEntity dimensionToDimensionEntity(Dimension dimension); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DocumentMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DocumentMapper.java index f445fc9..1315c31 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DocumentMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/DocumentMapper.java @@ -3,11 +3,10 @@ package com.dh7789dev.xpeditis.mapper; import com.dh7789dev.xpeditis.dto.app.Document; import com.dh7789dev.xpeditis.entity.DocumentEntity; import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring") public interface DocumentMapper { - DocumentMapper INSTANCE = Mappers.getMapper(DocumentMapper.class); + DocumentEntity documentToDocumentEntity(Document document); Document documentEntityToDocument(DocumentEntity documentEntity); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ExportFolderMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ExportFolderMapper.java index b8751fa..3dcb616 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ExportFolderMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ExportFolderMapper.java @@ -5,11 +5,10 @@ import com.dh7789dev.xpeditis.entity.ExportFolderEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { DocumentMapper.class, CompanyMapper.class }) public interface ExportFolderMapper { - ExportFolderMapper INSTANCE = Mappers.getMapper(ExportFolderMapper.class); + @Mapping(target = "createdDate", ignore = true) @Mapping(target = "modifiedDate", ignore = true) diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/LicenseMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/LicenseMapper.java index 5f63f10..8f953c2 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/LicenseMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/LicenseMapper.java @@ -1,16 +1,12 @@ package com.dh7789dev.xpeditis.mapper; - import com.dh7789dev.xpeditis.dto.app.License; import com.dh7789dev.xpeditis.entity.LicenseEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.MappingConstants; -import org.mapstruct.factory.Mappers; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +@Mapper(componentModel = "spring") public interface LicenseMapper { - LicenseMapper INSTANCE = Mappers.getMapper(LicenseMapper.class); @Mapping(target = "createdDate", ignore = true) @Mapping(target = "modifiedDate", ignore = true) diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/NotificationMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/NotificationMapper.java index 0e0e7c3..e543bb7 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/NotificationMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/NotificationMapper.java @@ -4,12 +4,10 @@ package com.dh7789dev.xpeditis.mapper; import com.dh7789dev.xpeditis.dto.app.Notification; import com.dh7789dev.xpeditis.entity.NotificationEntity; import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - @Mapper(componentModel = "spring" , uses = { ExportFolderMapper.class }) public interface NotificationMapper { - NotificationMapper INSTANCE = Mappers.getMapper(NotificationMapper.class); + NotificationEntity notificationToNotificationEntity(Notification notification); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteDetailMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteDetailMapper.java index 77d991b..beb02e4 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteDetailMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteDetailMapper.java @@ -4,11 +4,10 @@ import com.dh7789dev.xpeditis.dto.app.QuoteDetail; import com.dh7789dev.xpeditis.entity.QuoteDetailEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring", uses = { DimensionMapper.class }) public interface QuoteDetailMapper { - QuoteDetailMapper INSTANCE = Mappers.getMapper(QuoteDetailMapper.class); + @Mapping(source = "quoteId", target = "quote.id") QuoteDetailEntity quoteDetailsToQuoteDetailsEntity(QuoteDetail quoteDetail); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteMapper.java index 48903ea..0534908 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/QuoteMapper.java @@ -5,11 +5,10 @@ import com.dh7789dev.xpeditis.entity.QuoteEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { QuoteDetailMapper.class }) public interface QuoteMapper { - QuoteMapper INSTANCE = Mappers.getMapper(QuoteMapper.class); + @Mapping(target = "createdDate", ignore = true) @Mapping(target = "modifiedDate", ignore = true) diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ShipmentTrackingMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ShipmentTrackingMapper.java index c80fb1f..b7920f6 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ShipmentTrackingMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/ShipmentTrackingMapper.java @@ -1,16 +1,12 @@ package com.dh7789dev.xpeditis.mapper; - import com.dh7789dev.xpeditis.dto.app.ShipmentTracking; import com.dh7789dev.xpeditis.entity.ShipmentTrackingEntity; import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring", uses = { ExportFolderMapper.class }) public interface ShipmentTrackingMapper { - ShipmentTrackingMapper INSTANCE = Mappers.getMapper(ShipmentTrackingMapper.class); - ShipmentTrackingEntity shipmentTrackingToShipmentTrackingEntity(ShipmentTracking shipmentTracking); ShipmentTracking shipmentTrackingEntityToShipmentTracking(ShipmentTrackingEntity shipmentTrackingEntity); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/UserMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/UserMapper.java index b1b8e29..edaf053 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/UserMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/UserMapper.java @@ -5,14 +5,10 @@ import com.dh7789dev.xpeditis.entity.UserEntity; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.factory.Mappers; - @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public interface UserMapper { - UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); - @Mapping(target = "createdDate", ignore = true) @Mapping(target = "modifiedDate", ignore = true) @Mapping(target = "createdBy", ignore = true) diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/VesselScheduleMapper.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/VesselScheduleMapper.java index 9716d51..eb5d7a2 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/VesselScheduleMapper.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/mapper/VesselScheduleMapper.java @@ -1,16 +1,12 @@ package com.dh7789dev.xpeditis.mapper; - import com.dh7789dev.xpeditis.dto.app.VesselSchedule; import com.dh7789dev.xpeditis.entity.VesselScheduleEntity; import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; @Mapper(componentModel = "spring", uses = { ExportFolderMapper.class }) public interface VesselScheduleMapper { - VesselScheduleMapper INSTANCE = Mappers.getMapper(VesselScheduleMapper.class); - VesselScheduleEntity vesselScheduleToVesselScheduleEntity(VesselSchedule vesselSchedule); VesselSchedule vesselScheduleEntityToVesselSchedule(VesselScheduleEntity vesselScheduleEntity); diff --git a/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/UserJpaRepository.java b/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/UserJpaRepository.java index 1a089db..c5770ca 100644 --- a/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/UserJpaRepository.java +++ b/infrastructure/src/main/java/com/dh7789dev/xpeditis/repository/UserJpaRepository.java @@ -15,6 +15,7 @@ import org.springframework.stereotype.Repository; import java.security.Principal; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Repository @@ -103,4 +104,19 @@ public class UserJpaRepository implements UserRepository { public void delete(Long id) { userDao.deleteById(id); } + + @Override + public UserAccount findOrCreateOAuthUser(String email, Map attributes,String password) { + return userMapper.userEntityToUserAccount(userDao.findByEmail(email).orElseGet(() -> { + UserEntity newUser = new UserEntity(); + newUser.setEmail(email); + newUser.setUsername(email); + newUser.setFirstName((String) attributes.getOrDefault("name", "Unknown")); + newUser.setLastName((String) attributes.getOrDefault("name", "Unknown")); + newUser.setPassword(passwordEncoder.encode(password)); + newUser.setRole(Role.ADMIN); + newUser.setEnabled(true); + return userDao.save(newUser); + })); + } }