diff --git a/Dockerfile.dev b/Dockerfile.dev
index d2eebdb..13a088f 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -1,70 +1,9 @@
FROM openjdk:21-jdk-slim
WORKDIR /app
-COPY pom.xml .
-COPY mvnw .
COPY .mvn .mvn
+COPY mvnw pom.xml ./
RUN chmod +x mvnw
-RUN ./mvnw dependency:go-offline
+RUN ./mvnw -T 4 dependency:go-offline
COPY src ./src
-EXPOSE 9091
-CMD ["./mvnw", "spring-boot:run"]
-
-# FROM openjdk:21-jdk-slim
-
-# # Instalar Maven
-# RUN apt-get update && apt-get install -y maven && rm -rf /var/lib/apt/lists/*
-
-# WORKDIR /app
-
-# # ✅ Instalar security-common primero
-# COPY security-common /app/security-common
-# WORKDIR /app/security-common
-# RUN mvn clean install -DskipTests
-
-# # ✅ Construir msvc-security
-# WORKDIR /app/msvc-security
-# COPY msvc-security/pom.xml .
-# COPY msvc-security/mvnw .
-# COPY msvc-security/.mvn .mvn
-# RUN chmod +x mvnw
-
-# COPY msvc-security/src ./src
-
-# EXPOSE 9091
-# CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.fork=false"]
-
-
-
-# FROM openjdk:21-jdk-slim
-
-# RUN apt-get update && \
-# apt-get install -y maven && \
-# rm -rf /var/lib/apt/lists/* && \
-# mvn --version
-
-# WORKDIR /app
-
-# COPY security-common/pom.xml /app/security-common/pom.xml
-# COPY msvc-security/pom.xml /app/msvc-security/pom.xml
-
-# WORKDIR /app/security-common
-# RUN mvn dependency:resolve || true
-
-# COPY security-common /app/security-common
-# RUN mvn clean install -DskipTests -q
-
-# WORKDIR /app/msvc-security
-# COPY msvc-security/mvnw .
-# COPY msvc-security/.mvn .mvn
-# RUN chmod +x mvnw
-
-# RUN ./mvnw dependency:go-offline -q || true
-
-# COPY msvc-security/src ./src
-
-# EXPOSE 9091
-# ENV SPRING_PROFILES_ACTIVE=dev
-
-# CMD ["./mvnw", "spring-boot:run", \
-# "-Dspring-boot.run.fork=false", \
-# "-Dspring-boot.run.jvmArguments=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5005"]
\ No newline at end of file
+EXPOSE 9091 5005
+CMD ["./mvnw", "spring-boot:run", "-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"]
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index f751108..37ccd51 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
UTF-8
-
+
org.springframework.boot
@@ -135,7 +135,10 @@
${mapstruct.version}
provided
-
+
+ org.springframework.kafka
+ spring-kafka
+
@@ -150,6 +153,10 @@
org.springframework.security
spring-security-oauth2-jose
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
diff --git a/src/main/java/com/security/config/Audit.java b/src/main/java/com/security/config/Audit.java
deleted file mode 100644
index 85bf1a0..0000000
--- a/src/main/java/com/security/config/Audit.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.security.config;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Embeddable;
-import jakarta.persistence.PrePersist;
-import jakarta.persistence.PreUpdate;
-
-import java.time.LocalDateTime;
-
-@Embeddable
-public class Audit {
- @Column(name = "created_at")
- private LocalDateTime createdAt;
- @Column(name = "update_at")
- private LocalDateTime updateAt;
-
- @PrePersist
- void prePersist() {
- createdAt = LocalDateTime.now();
- }
- @PreUpdate
- void preUpdate() {
- updateAt = LocalDateTime.now();
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/CommonPointcuts.java b/src/main/java/com/security/config/CommonPointcuts.java
index d7f3d95..7f37b67 100644
--- a/src/main/java/com/security/config/CommonPointcuts.java
+++ b/src/main/java/com/security/config/CommonPointcuts.java
@@ -9,8 +9,8 @@
@Component
public class CommonPointcuts {
@Pointcut("execution(* com.security.services.*.*(..))")
- public void greetingLoggerServices(){};
+ public void greetingLoggerServices(){}
@Pointcut("execution(* com.security.controllers.*.*(..))")
- public void greetingLoggerControllers(){};
+ public void greetingLoggerControllers(){}
}
diff --git a/src/main/java/com/security/config/DataInitializer.java b/src/main/java/com/security/config/DataInitializer.java
deleted file mode 100644
index 47bbb9a..0000000
--- a/src/main/java/com/security/config/DataInitializer.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.security.config;
-
-import com.security.Entity.RoleEntity;
-import com.security.Entity.UserEntity;
-import com.security.Repository.RoleRepository;
-import com.security.Repository.UserRepository;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.boot.CommandLineRunner;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Component;
-
-import java.util.Set;
-
-@Component
-@RequiredArgsConstructor
-@Slf4j
-public class DataInitializer implements CommandLineRunner {
-
- private final UserRepository userRepository;
- private final RoleRepository roleRepository;
- private final PasswordEncoder passwordEncoder;
-
-
-
- @Override
- public void run(String... args) throws Exception {
- RoleEntity adminRole = roleRepository.findByName("ADMIN")
- .orElseGet(() -> {
- log.info("Creating ADMIN role");
- return roleRepository.save(RoleEntity.builder()
- .name("ADMIN")
- .description("Administrator role")
- .build());
- });
-
- RoleEntity userRole = roleRepository.findByName("USER")
- .orElseGet(() -> {
- log.info("Creating USER role");
- return roleRepository.save(RoleEntity.builder()
- .name("USER")
- .description("User role")
- .build());
- });
-
- RoleEntity trainerRole = roleRepository.findByName("TRAINER")
- .orElseGet(() -> {
- log.info("Creating TRAINER role");
- return roleRepository.save(RoleEntity.builder()
- .name("TRAINER")
- .description("Trainer role")
- .build());
- });
-
- if (!userRepository.existsByUsername("admin")) {
- String encodedPassword = passwordEncoder.encode("admin123");
- log.info("Creating admin user with encoded password length: {}", encodedPassword.length());
-
- UserEntity admin = UserEntity.builder()
- .username("admin")
- .email("admin@fitdesk.com")
- .password(encodedPassword)
- .firstName("Admin")
- .lastName("User")
- .roles(Set.of(adminRole))
- .build();
- userRepository.save(admin);
- log.info("Admin user created successfully");
- } else {
- log.info("Admin user already exists");
- }
-
- if (!userRepository.existsByUsername("user")) {
- String encodedPassword = passwordEncoder.encode("user123");
- log.info("Creating regular user");
-
- UserEntity user = UserEntity.builder()
- .username("user")
- .email("user@fitdesk.com")
- .password(encodedPassword)
- .firstName("Regular")
- .lastName("User")
- .roles(Set.of(userRole))
- .build();
- userRepository.save(user);
- log.info("Regular user created successfully");
- }
-
- if (!userRepository.existsByUsername("trainer")) {
- String encodedPassword = passwordEncoder.encode("trainer123");
- log.info("Creating trainer user");
-
- UserEntity trainer = UserEntity.builder()
- .username("trainer")
- .email("trainer@fitdesk.com")
- .password(encodedPassword)
- .firstName("Gym")
- .lastName("Trainer")
- .roles(Set.of(trainerRole))
- .build();
- userRepository.save(trainer);
- log.info("Trainer user created successfully");
- }
- }
-}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/RoleInitializer.java b/src/main/java/com/security/config/RoleInitializer.java
new file mode 100644
index 0000000..e7cbf95
--- /dev/null
+++ b/src/main/java/com/security/config/RoleInitializer.java
@@ -0,0 +1,34 @@
+package com.security.config;
+
+import com.security.entity.RoleEntity;
+import com.security.repository.RoleRepository;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class RoleInitializer implements CommandLineRunner {
+ private final RoleRepository roleRepository;
+
+ @Override
+ public void run(String... args) throws Exception {
+ createRoleIfNotExists("ADMIN", "Administrator role");
+ createRoleIfNotExists("USER", "User role");
+ createRoleIfNotExists("TRAINER", "Trainer role");
+ }
+
+ private void createRoleIfNotExists(String name, String description) {
+ roleRepository.findByName(name)
+ .orElseGet(() -> {
+ log.info("Creando {} role", name);
+ return roleRepository.save(RoleEntity.builder()
+ .name(name)
+ .description(description)
+ .build());
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/SecurityConfig.java b/src/main/java/com/security/config/SecurityConfig.java
deleted file mode 100644
index 3363df0..0000000
--- a/src/main/java/com/security/config/SecurityConfig.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package com.security.config;
-
-import com.nimbusds.jose.jwk.JWKSet;
-import com.nimbusds.jose.jwk.RSAKey;
-import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.proc.SecurityContext;
-import lombok.RequiredArgsConstructor;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.core.annotation.Order;
-import org.springframework.security.config.Customizer;
-import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
-import org.springframework.security.config.annotation.web.builders.HttpSecurity;
-import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
-import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.core.AuthorizationGrantType;
-import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
-import org.springframework.security.oauth2.core.oidc.OidcScopes;
-import org.springframework.security.oauth2.jwt.JwtDecoder;
-import org.springframework.security.oauth2.jwt.JwtEncoder;
-import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
-import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
-import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
-import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
-import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
-import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
-import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
-import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
-import org.springframework.security.web.SecurityFilterChain;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-
-
-import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
-
-@Configuration
-@EnableWebSecurity
-@EnableMethodSecurity
-@RequiredArgsConstructor
-public class SecurityConfig {
-
- private final AuthProperties authProperties;
-// @Value("${auth.client.redirect-uris}")
-// private String[] redirectUris;
-//
-// @Value("${auth.client.post-logout-redirect-uri}")
-// private String postLogoutRedirectUri;
-// @Value("${auth.server.issuer}")
-// private String issuerUri;
-
- @Bean
- @Order(1)
- public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
- OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
- OAuth2AuthorizationServerConfigurer.authorizationServer();
- http
- .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
- .with(authorizationServerConfigurer, (authorizationServer) ->
- authorizationServer
- .oidc(Customizer.withDefaults())
- )
- .oauth2ResourceServer(oauth2ResourceServer ->
- oauth2ResourceServer.jwt(Customizer.withDefaults())
- );
-
- return http.build();
- }
-
- @Bean
- @Order(2)
- public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http, CookieAuthenticationFilter cookieAuthenticationFilter) throws Exception {
- return http
- .authorizeHttpRequests(authorize -> authorize
- .requestMatchers(
- "/actuator/**",
- "/swagger-ui/**",
- "/v3/api-docs/**",
- "/auth/info",
- "/auth/status",
- "/auth/login",
- "/auth/refresh",
- "/saludo",
- "/"
- ).permitAll()
- .anyRequest().authenticated()
- )
- .addFilterBefore(cookieAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
- .oauth2ResourceServer(oauth2ResourceServer ->
- oauth2ResourceServer.jwt(Customizer.withDefaults())
- )
- .csrf(AbstractHttpConfigurer::disable)
- .build();
- }
-
- @Bean
- public RegisteredClientRepository registeredClientRepository() {
- RegisteredClient.Builder clientBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
- .clientId("gateway-client")
- .clientSecret(passwordEncoder().encode("gateway-secret"))
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
- .postLogoutRedirectUri(authProperties.getClient().getPostLogoutRedirectUri())
- .scope(OidcScopes.OPENID)
- .scope(OidcScopes.PROFILE)
- .scope(OidcScopes.EMAIL)
- .scope("read")
- .scope("write")
- .clientSettings(ClientSettings.builder()
- .requireAuthorizationConsent(false)
- .requireProofKey(false)
- .build())
- .tokenSettings(TokenSettings.builder()
- .accessTokenTimeToLive(Duration.ofMinutes(15))
- .refreshTokenTimeToLive(Duration.ofDays(7))
- .reuseRefreshTokens(false)
- .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
- .build());
-
- authProperties.getClient().getRedirectUris()
- .forEach(clientBuilder::redirectUri);
-
- return new InMemoryRegisteredClientRepository(clientBuilder.build());
- }
-
- @Bean
- public JWKSource jwkSource() {
- KeyPair keyPair = generateRsaKey();
- RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
- RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
- RSAKey rsaKey = new RSAKey.Builder(publicKey)
- .privateKey(privateKey)
- .keyID(UUID.randomUUID().toString())
- .build();
- JWKSet jwkSet = new JWKSet(rsaKey);
- return new ImmutableJWKSet<>(jwkSet);
- }
-
- private static KeyPair generateRsaKey() {
- KeyPair keyPair;
- try {
- KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- keyPairGenerator.initialize(2048);
- keyPair = keyPairGenerator.generateKeyPair();
- } catch (
- Exception ex) {
- throw new IllegalStateException(ex);
- }
- return keyPair;
- }
-
- @Bean
- public JwtDecoder jwtDecoder(JWKSource jwkSource) {
- return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
- }
-
- @Bean
- public JwtEncoder jwtEncoder(JWKSource jwkSource) {
- return new NimbusJwtEncoder(jwkSource);
- }
-
- @Bean
- public AuthorizationServerSettings authorizationServerSettings() {
- return AuthorizationServerSettings.builder()
- .issuer(authProperties.getServer().getIssuer())
- .authorizationEndpoint("/oauth2/authorize")
- .tokenEndpoint("/oauth2/token")
- .tokenIntrospectionEndpoint("/oauth2/introspect")
- .tokenRevocationEndpoint("/oauth2/revoke")
- .jwkSetEndpoint("/.well-known/jwks.json")
- .oidcLogoutEndpoint("/connect/logout")
- .oidcUserInfoEndpoint("/userinfo")
- .oidcClientRegistrationEndpoint("/connect/register")
- .build();
- }
-
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
-}
diff --git a/src/main/java/com/security/config/audit/Audit.java b/src/main/java/com/security/config/audit/Audit.java
new file mode 100644
index 0000000..c368bf8
--- /dev/null
+++ b/src/main/java/com/security/config/audit/Audit.java
@@ -0,0 +1,35 @@
+package com.security.config.audit;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Embeddable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.Instant;
+
+
+@Embeddable
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder
+public class Audit {
+
+ @Column(name = "created_by")
+ private String createdBy;
+
+ @Column(name = "created_at")
+ private Instant createdAt;
+
+ @Column(name = "updated_by")
+ private String updatedBy;
+
+ @Column(name = "updated_at")
+ private Instant updatedAt;
+
+ @Column(name = "status_reason")
+ private String statusReason;
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/audit/AuditListener.java b/src/main/java/com/security/config/audit/AuditListener.java
new file mode 100644
index 0000000..034504a
--- /dev/null
+++ b/src/main/java/com/security/config/audit/AuditListener.java
@@ -0,0 +1,78 @@
+package com.security.config.audit;
+
+import jakarta.persistence.PrePersist;
+import jakarta.persistence.PreUpdate;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.lang.reflect.Field;
+import java.time.Instant;
+
+public class AuditListener {
+
+ @PrePersist
+ public void onPrePersist(Object entity) {
+ applyAudit(entity, true);
+ }
+
+ @PreUpdate
+ public void onPreUpdate(Object entity) {
+ applyAudit(entity, false);
+ }
+
+ private void applyAudit(Object entity, boolean isCreate) {
+ try {
+ Field f = findAuditField(entity);
+ if (f == null)
+ return;
+ f.setAccessible(true);
+
+ Audit audit = (Audit) f.get(entity);
+ if (audit == null) {
+ audit = Audit.builder().build();
+ }
+
+ String user = currentUsername();
+ Instant now = Instant.now();
+
+ if (isCreate) {
+ if (audit.getCreatedAt() == null)
+ audit.setCreatedAt(now);
+ if (audit.getCreatedBy() == null)
+ audit.setCreatedBy(user);
+ }
+ // siempre actualizar updated* en create y update
+ audit.setUpdatedAt(now);
+ audit.setUpdatedBy(user);
+
+ f.set(entity, audit);
+ } catch (
+ Exception ignored) {
+ // no bloquear persist/update por auditoría
+ }
+ }
+
+ private Field findAuditField(Object entity) {
+ Class> cls = entity.getClass();
+ while (cls != null && cls != Object.class) {
+ try {
+ return cls.getDeclaredField("audit");
+ } catch (
+ NoSuchFieldException e) {
+ cls = cls.getSuperclass();
+ }
+ }
+ return null;
+ }
+
+ private String currentUsername() {
+ try {
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
+ if (auth != null && auth.getName() != null)
+ return auth.getName();
+ } catch (
+ Exception ignored) {
+ }
+ return "system";
+ }
+}
diff --git a/src/main/java/com/security/config/AuthProperties.java b/src/main/java/com/security/config/auth/AuthProperties.java
similarity index 95%
rename from src/main/java/com/security/config/AuthProperties.java
rename to src/main/java/com/security/config/auth/AuthProperties.java
index 68d548a..62cfd4a 100644
--- a/src/main/java/com/security/config/AuthProperties.java
+++ b/src/main/java/com/security/config/auth/AuthProperties.java
@@ -1,4 +1,4 @@
-package com.security.config;
+package com.security.config.auth;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
diff --git a/src/main/java/com/security/config/auth/AuthorizationServerConfig.java b/src/main/java/com/security/config/auth/AuthorizationServerConfig.java
new file mode 100644
index 0000000..66eb48d
--- /dev/null
+++ b/src/main/java/com/security/config/auth/AuthorizationServerConfig.java
@@ -0,0 +1,77 @@
+package com.security.config.auth;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
+import org.springframework.security.oauth2.core.oidc.OidcScopes;
+import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
+
+import java.time.Duration;
+import java.util.UUID;
+
+@Configuration
+@RequiredArgsConstructor
+public class AuthorizationServerConfig {
+
+ private final AuthProperties authProperties;
+ private final PasswordEncoder passwordEncoder;
+
+ @Bean
+ public RegisteredClientRepository registeredClientRepository() {
+ RegisteredClient.Builder clientBuilder = RegisteredClient.withId(UUID.randomUUID().toString())
+ .clientId("gateway-client")
+ .clientSecret(passwordEncoder.encode("gateway-secret"))
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
+ .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
+ .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
+ .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
+ .redirectUri("http://localhost:9090/login/oauth2/code/gateway-client")
+ .redirectUri("http://localhost:9090/authorized")
+ .postLogoutRedirectUri("http://localhost:9090/logout")
+ .scope(OidcScopes.OPENID)
+ .scope(OidcScopes.PROFILE)
+ .scope(OidcScopes.EMAIL)
+ .scope("read")
+ .scope("write")
+ .clientSettings(ClientSettings.builder()
+ .requireAuthorizationConsent(false)
+ .requireProofKey(false)
+ .build())
+ .tokenSettings(TokenSettings.builder()
+ .accessTokenTimeToLive(Duration.ofMinutes(15))
+ .refreshTokenTimeToLive(Duration.ofDays(7))
+ .reuseRefreshTokens(false)
+ .accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
+ .build());
+
+ authProperties.getClient().getRedirectUris().forEach(clientBuilder::redirectUri);
+ RegisteredClient client = clientBuilder.build();
+ return new InMemoryRegisteredClientRepository(client);
+ }
+
+
+ @Bean
+ public AuthorizationServerSettings authorizationServerSettings() {
+ return AuthorizationServerSettings.builder()
+ .issuer(authProperties.getServer().getIssuer())
+ .authorizationEndpoint("/oauth2/authorize")
+ .tokenEndpoint("/oauth2/token")
+ .tokenIntrospectionEndpoint("/oauth2/introspect")
+ .tokenRevocationEndpoint("/oauth2/revoke")
+ .jwkSetEndpoint("/.well-known/jwks.json")
+ .oidcLogoutEndpoint("/connect/logout")
+ .oidcUserInfoEndpoint("/userinfo")
+ .oidcClientRegistrationEndpoint("/connect/register")
+ .build();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/CookieAuthenticationFilter.java b/src/main/java/com/security/config/auth/CookieAuthenticationFilter.java
similarity index 91%
rename from src/main/java/com/security/config/CookieAuthenticationFilter.java
rename to src/main/java/com/security/config/auth/CookieAuthenticationFilter.java
index 3e1472a..719c609 100644
--- a/src/main/java/com/security/config/CookieAuthenticationFilter.java
+++ b/src/main/java/com/security/config/auth/CookieAuthenticationFilter.java
@@ -1,4 +1,4 @@
-package com.security.config;
+package com.security.config.auth;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -22,7 +22,8 @@ protected void doFilterInternal(HttpServletRequest request,
FilterChain filterChain) throws ServletException, IOException {
String token = extractTokenFromCookies(request);
-
+ log.debug("Token from cookies: {}", token);
+ log.debug("Authorization header: {}", request.getHeader("Authorization"));
if (token != null) {
HttpServletRequestWrapper wrappedRequest = new HttpServletRequestWrapper(request) {
@Override
diff --git a/src/main/java/com/security/config/auth/JwtConfig.java b/src/main/java/com/security/config/auth/JwtConfig.java
new file mode 100644
index 0000000..5ef5219
--- /dev/null
+++ b/src/main/java/com/security/config/auth/JwtConfig.java
@@ -0,0 +1,60 @@
+package com.security.config.auth;
+
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.proc.SecurityContext;
+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.NimbusJwtEncoder;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.util.UUID;
+
+@Configuration
+public class JwtConfig {
+
+ @Bean
+ public JWKSource jwkSource() {
+ KeyPair keyPair = generateRsaKey();
+ RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
+ RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
+ RSAKey rsaKey = new RSAKey.Builder(publicKey)
+ .privateKey(privateKey)
+ .keyID(UUID.randomUUID().toString())
+ .build();
+ JWKSet jwkSet = new JWKSet(rsaKey);
+ return new ImmutableJWKSet<>(jwkSet);
+ }
+
+ private static KeyPair generateRsaKey() {
+ KeyPair keyPair;
+ try {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ keyPairGenerator.initialize(2048);
+ keyPair = keyPairGenerator.generateKeyPair();
+ } catch (
+ Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+ return keyPair;
+ }
+
+ @Bean
+ public JwtDecoder jwtDecoder(JWKSource jwkSource) {
+ return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
+ }
+
+ @Bean
+ public JwtEncoder jwtEncoder(JWKSource jwkSource) {
+ return new NimbusJwtEncoder(jwkSource);
+ }
+
+}
diff --git a/src/main/java/com/security/config/auth/SecurityConfig.java b/src/main/java/com/security/config/auth/SecurityConfig.java
new file mode 100644
index 0000000..10b62e0
--- /dev/null
+++ b/src/main/java/com/security/config/auth/SecurityConfig.java
@@ -0,0 +1,153 @@
+package com.security.config.auth;
+
+import com.security.config.auth.oauth2.OAuth2AuthenticationSuccessHandler;
+import com.security.services.oauth2.CustomOAuth2UserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.oidc.user.OidcUser;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+@Configuration
+@EnableWebSecurity
+@EnableMethodSecurity
+@RequiredArgsConstructor
+public class SecurityConfig {
+
+ private final CustomOAuth2UserService customOAuth2UserService;
+
+ @Bean
+ @Order(1)
+ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+ OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
+ OAuth2AuthorizationServerConfigurer.authorizationServer();
+
+ http
+ .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher())
+ .with(authorizationServerConfigurer, (authorizationServer) ->
+ authorizationServer
+ .oidc(Customizer.withDefaults())
+ )
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer.jwt(Customizer.withDefaults())
+ );
+
+ return http.build();
+ }
+
+ @Bean
+ @Order(2)
+ public SecurityFilterChain defaultSecurityFilterChain(
+ HttpSecurity http,
+ CookieAuthenticationFilter cookieAuthenticationFilter,
+ OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler,
+ OAuth2UserService customOidcUserService,
+ AuthorizationRequestRepository authorizationRequestRepository) throws Exception {
+
+ return http
+ .authorizeHttpRequests(authorize -> authorize
+ .requestMatchers(
+ "/actuator/**",
+ "/swagger-ui/**",
+ "/v3/api-docs/**",
+ "/auth/info",
+ "/auth/status",
+ "/auth/login",
+ "/auth/refresh",
+ "/auth/register",
+ "/oauth2/**",
+ "/login/oauth2/**",
+ "/test/saludo",
+ "/test/notification",
+ "/"
+ ).permitAll()
+ .anyRequest().authenticated()
+ )
+ .oauth2Login(oauth2 ->
+ oauth2.authorizationEndpoint(authorization ->
+ authorization.authorizationRequestRepository(authorizationRequestRepository)
+ )
+ .userInfoEndpoint(userInfo -> userInfo
+ .userService(customOAuth2UserService)
+ .oidcUserService(customOidcUserService)
+ )
+ .successHandler(oAuth2AuthenticationSuccessHandler)
+ )
+ .oauth2Client(Customizer.withDefaults())
+ .addFilterBefore(cookieAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
+ .oauth2ResourceServer(oauth2ResourceServer ->
+ oauth2ResourceServer.jwt(Customizer.withDefaults())
+ )
+ .csrf(AbstractHttpConfigurer::disable)
+ .build();
+ }
+
+ @Bean
+ public JwtAuthenticationConverter jwtAuthenticationConverter() {
+ JwtGrantedAuthoritiesConverter scopeConverter = new JwtGrantedAuthoritiesConverter();
+ scopeConverter.setAuthorityPrefix("SCOPE_");
+
+ JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
+ converter.setJwtGrantedAuthoritiesConverter(jwt -> {
+ Collection authorities = new ArrayList<>();
+
+ Collection scopeAuth = scopeConverter.convert(jwt);
+ if (scopeAuth != null) {
+ authorities.addAll(scopeAuth);
+ }
+
+ Object claim = jwt.getClaims().get("authorities");
+ if (claim instanceof String authString) {
+ String[] parts = authString.trim().split("\\s+");
+ for (String part : parts) {
+ if (!part.isBlank()) {
+ authorities.add(new SimpleGrantedAuthority(part));
+ }
+ }
+ } else if (claim instanceof Collection> authCollection) {
+ authCollection.forEach(o -> {
+ if (o != null) {
+ authorities.add(new SimpleGrantedAuthority(o.toString()));
+ }
+ });
+ } else if (claim instanceof Map, ?> authMap) {
+ authMap.values().forEach(v -> {
+ if (v != null) {
+ authorities.add(new SimpleGrantedAuthority(v.toString()));
+ }
+ });
+ }
+
+ return authorities;
+ });
+
+ return converter;
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java b/src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java
new file mode 100644
index 0000000..f8db052
--- /dev/null
+++ b/src/main/java/com/security/config/auth/oauth2/OAuth2AuthenticationSuccessHandler.java
@@ -0,0 +1,89 @@
+package com.security.config.auth.oauth2;
+
+import com.security.entity.UserEntity;
+import com.security.services.AuthService;
+import com.security.services.CookieService;
+import com.security.services.oauth2.CustomOAuth2User;
+import com.security.services.oauth2.CustomOidcUser;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.io.IOException;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
+
+ private final AuthService authService;
+ private final CookieService cookieService;
+
+ @Override
+ public void onAuthenticationSuccess(
+ HttpServletRequest request,
+ HttpServletResponse response,
+ Authentication authentication) throws IOException {
+
+ Object principal = authentication.getPrincipal();
+ UserEntity user = null;
+
+ if (principal instanceof CustomOidcUser customOidcUser) {
+ user = customOidcUser.getUser();
+ log.info("✅ Usuario obtenido desde CustomOidcUser: {}", user.getEmail());
+ } else if (principal instanceof CustomOAuth2User customOAuth2User) {
+ user = customOAuth2User.getUser();
+ log.info("✅ Usuario obtenido desde CustomOAuth2User: {}", user.getEmail());
+ } else {
+ log.error("❌ Principal NO es CustomOAuth2User ni CustomOidcUser. Tipo: {}", principal.getClass().getName());
+ if (principal instanceof OAuth2User oauth2User) {
+ log.error("❌ Atributos: {}", oauth2User.getAttributes());
+ }
+
+ getRedirectStrategy().sendRedirect(request, response,
+ "http://localhost:5173/auth?error=oauth_user_service_failed");
+ return;
+ }
+
+ if (user == null) {
+ log.error("❌ Usuario es NULL después de obtenerlo del principal");
+ getRedirectStrategy().sendRedirect(request, response,
+ "http://localhost:5173/auth?error=oauth_user_null");
+ return;
+ }
+
+ try {
+ var loginResponse = authService.createTokensForOAuth2User(user);
+ cookieService.setSecureTokenCookies(response, loginResponse);
+
+ log.info("✅ Cookies establecidas para usuario OAuth2: {}", user.getEmail());
+
+ // ✅ Redirigir al frontend
+ String targetUrl = UriComponentsBuilder
+ .fromUriString("http://localhost:5173/auth/callback")
+ .queryParam("success", "true")
+ .build()
+ .toUriString();
+
+ if (response.isCommitted()) {
+ log.debug("⚠️ La respuesta ya fue enviada. No se puede redirigir a {}", targetUrl);
+ return;
+ }
+
+ clearAuthenticationAttributes(request);
+ getRedirectStrategy().sendRedirect(request, response, targetUrl);
+
+ } catch (
+ Exception e) {
+ log.error("❌ Error generando tokens para OAuth2", e);
+ getRedirectStrategy().sendRedirect(request, response,
+ "http://localhost:5173/auth?error=token_generation_failed");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/auth/oauth2/OAuth2Config.java b/src/main/java/com/security/config/auth/oauth2/OAuth2Config.java
new file mode 100644
index 0000000..0a8e079
--- /dev/null
+++ b/src/main/java/com/security/config/auth/oauth2/OAuth2Config.java
@@ -0,0 +1,54 @@
+package com.security.config.auth.oauth2;
+
+import com.security.services.AuthService;
+import com.security.services.CookieService;
+import com.security.services.oauth2.CustomOAuth2UserService;
+import com.security.services.oauth2.CustomOidcUser;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
+import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
+import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
+import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
+import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
+import org.springframework.security.oauth2.core.oidc.user.OidcUser;
+import org.springframework.security.oauth2.core.user.OAuth2User;
+
+
+@Configuration
+public class OAuth2Config {
+
+ @Bean
+ public OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler(
+ AuthService authService,
+ CookieService cookieService) {
+ return new OAuth2AuthenticationSuccessHandler(authService, cookieService);
+ }
+
+ @Bean
+ public OAuth2UserService customOidcUserService(
+ CustomOAuth2UserService customOAuth2UserService) {
+ return new OidcUserService() {
+ @Override
+ public OidcUser loadUser(OidcUserRequest userRequest) {
+ OAuth2UserRequest oauth2UserRequest = new OAuth2UserRequest(
+ userRequest.getClientRegistration(),
+ userRequest.getAccessToken(),
+ userRequest.getAdditionalParameters()
+ );
+ OAuth2User oauth2User = customOAuth2UserService.loadUser(oauth2UserRequest);
+ if (oauth2User instanceof com.security.services.oauth2.CustomOAuth2User customUser) {
+ return new CustomOidcUser(customUser, userRequest.getIdToken());
+ }
+ return super.loadUser(userRequest);
+ }
+ };
+ }
+
+ @Bean
+ public AuthorizationRequestRepository authorizationRequestRepository() {
+ return new HttpSessionOAuth2AuthorizationRequestRepository();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/security/config/kafka/KafkaProducerConfig.java b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java
new file mode 100644
index 0000000..697ba3c
--- /dev/null
+++ b/src/main/java/com/security/config/kafka/KafkaProducerConfig.java
@@ -0,0 +1,76 @@
+package com.security.config.kafka;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.producer.ProducerConfig;
+import org.apache.kafka.common.serialization.StringSerializer;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.kafka.config.TopicBuilder;
+import org.springframework.kafka.core.DefaultKafkaProducerFactory;
+import org.springframework.kafka.core.KafkaTemplate;
+import org.springframework.kafka.core.ProducerFactory;
+import org.springframework.kafka.support.serializer.JsonSerializer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+@Slf4j
+public class KafkaProducerConfig {
+
+ @Value("${spring.kafka.producer.bootstrap-servers}")
+ private String bootstrapServers;
+ @Value("${spring.kafka.producer.acks}")
+ private String acks;
+ @Value("${spring.kafka.producer.properties.delivery.timeout.ms}")
+ private String deliveryTimeout;
+ @Value("${spring.kafka.producer.properties.linger.ms}")
+ private String linger;
+ @Value("${spring.kafka.producer.properties.request.timeout.ms}")
+ private String requestTimeout;
+
+ @Value("${spring.kafka.producer.properties.enable.idempotence}")
+ private boolean idempotence;
+ @Value("${spring.kafka.producer.properties.max.in.flight.requests.per.connection:5}")
+ private Integer inflightRequests;
+
+ Map producerConfigs() {
+ Map config = new HashMap<>();
+
+ config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
+ config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
+ config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
+ config.put(ProducerConfig.ACKS_CONFIG, acks);
+ config.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, deliveryTimeout);
+ config.put(ProducerConfig.LINGER_MS_CONFIG, linger);
+ config.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG, requestTimeout);
+ config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, idempotence);
+ config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, inflightRequests);
+ config.put(JsonSerializer.TYPE_MAPPINGS, "NotificationEvent:com.security.events.notification.NotificationEvent,CreatedUserEvent:com.security.events.notification.CreatedUserEvent");
+ config.put(ProducerConfig.RETRIES_CONFIG, 10);
+ return config;
+ }
+
+ @Bean
+ ProducerFactory producerFactory() {
+ return new DefaultKafkaProducerFactory<>(producerConfigs());
+ }
+
+ @Bean
+ public KafkaTemplate kafkaTemplate() {
+ return new KafkaTemplate(producerFactory());
+ }
+
+ @Bean
+ NewTopic createNotificationTopic() {
+ return TopicBuilder
+ .name("user-created-event-topic")
+ .partitions(1)
+ .replicas(1)
+ .configs(Map.of("min.insync.replicas", "1"))
+ .build();
+ }
+
+}
diff --git a/src/main/java/com/security/controllers/AdminUserController.java b/src/main/java/com/security/controllers/AdminUserController.java
new file mode 100644
index 0000000..70b4e94
--- /dev/null
+++ b/src/main/java/com/security/controllers/AdminUserController.java
@@ -0,0 +1,72 @@
+package com.security.controllers;
+
+import com.security.dtos.auth.AuthResponseDTO;
+import com.security.dtos.autorization.RoleChangeRequestDTO;
+import com.security.dtos.autorization.RolesResponseDTO;
+import com.security.annotations.AdminAccess;
+import com.security.services.UserAccountService;
+import com.security.services.UserRoleService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/admin/users")
+@RequiredArgsConstructor
+@Tag(name = "Autorizacion", description = "Endpoints para manejo de roles")
+public class AdminUserController {
+
+ private final UserRoleService userRoleService;
+ private final UserAccountService userAccountService;
+
+ @Operation(summary = "Listar roles de usuario")
+ @GetMapping("/{id}/roles")
+ @AdminAccess
+ public ResponseEntity getRoles(@PathVariable UUID id) {
+ return ResponseEntity.ok(userRoleService.getUserRoles(id));
+ }
+
+
+ @Operation(summary = "Agregar un rol a un usuario")
+ @PostMapping("/{id}/roles")
+ @AdminAccess
+ public ResponseEntity addRole(@PathVariable UUID id, @Valid @RequestBody RoleChangeRequestDTO request) {
+ return ResponseEntity.ok(userRoleService.addRoleToUser(id, request.role()));
+ }
+
+ @Operation(summary = "Quitar un rol a un usuario")
+ @DeleteMapping("/{id}/roles")
+ @AdminAccess
+ public ResponseEntity deleteRole(@PathVariable UUID id, @RequestBody RoleChangeRequestDTO requestDTO) {
+ return ResponseEntity.ok(userRoleService.removeRoleFromUser(id, requestDTO.role()));
+ }
+
+
+ @Operation(summary = "Desactivar cuenta de usuario")
+ @PatchMapping("/{id}/deactivate")
+ @AdminAccess
+ public ResponseEntity deactivate(@PathVariable UUID id,
+ @RequestParam(required = false) String reason,
+ Authentication authentication) {
+ String admin = authentication != null ? authentication.getName() : "system";
+
+ return ResponseEntity.ok(userAccountService.deactivateUser(id, reason, admin));
+ }
+
+ @Operation(summary = "Activar cuenta de usuario")
+ @PatchMapping("/{id}/activate")
+ @AdminAccess
+ public ResponseEntity activate(@PathVariable UUID id,
+ Authentication authentication) {
+ String admin = authentication != null ? authentication.getName() : "system";
+
+ return ResponseEntity.ok(userAccountService.activateUser(id, admin));
+ }
+
+}
diff --git a/src/main/java/com/security/controllers/AuthController.java b/src/main/java/com/security/controllers/AuthController.java
index 9da0195..dac7ee2 100644
--- a/src/main/java/com/security/controllers/AuthController.java
+++ b/src/main/java/com/security/controllers/AuthController.java
@@ -1,12 +1,12 @@
package com.security.controllers;
-
-import com.security.DTOs.LoginRequestDTO;
-import com.security.DTOs.LoginResponseDTO;
+import com.security.dtos.auth.AuthResponseDTO;
+import com.security.dtos.auth.LoginRequestDTO;
+import com.security.dtos.auth.LoginResponseDTO;
+import com.security.dtos.auth.RegisterRequestDto;
import com.security.annotations.AuthenticatedAccess;
import com.security.services.AuthService;
import com.security.services.CookieService;
-import com.security.services.LoginResponseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
@@ -18,9 +18,12 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
+import org.springframework.web.server.ResponseStatusException;
+import java.time.Instant;
import java.util.Map;
+
@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
@@ -30,11 +33,11 @@ public class AuthController {
private final AuthService authService;
private final CookieService cookieService;
- private final LoginResponseService loginResponseService;
+// private final LoginResponseService loginResponseService;
@Operation(summary = "Iniciar sesión con email", description = "Autentica un usuario y establece cookies seguras")
@PostMapping("/login")
- public ResponseEntity