diff --git a/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java b/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java
index 2553a58..ab720c3 100644
--- a/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java
+++ b/src/main/java/com/imprimelibros/erp/config/InternationalizationConfig.java
@@ -22,7 +22,7 @@ public class InternationalizationConfig implements WebMvcConfigurer {
@Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
- slr.setDefaultLocale(Locale.forLanguageTag("es")); // idioma por defecto
+ slr.setDefaultLocale(Locale.forLanguageTag("es-ES")); // idioma por defecto
return slr;
}
diff --git a/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java b/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java
index 8558778..3645a9b 100644
--- a/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java
+++ b/src/main/java/com/imprimelibros/erp/config/SecurityConfig.java
@@ -6,6 +6,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -22,84 +24,107 @@ import com.imprimelibros.erp.users.UserDetailsImpl;
@Configuration
public class SecurityConfig {
- private final DataSource dataSource;
+ private final DataSource dataSource;
- public SecurityConfig(DataSource dataSource) {
- this.dataSource = dataSource;
- }
+ public SecurityConfig(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
- // UserDetailsService para autenticación por username
- @Bean
- public UserDetailsService userDetailsService(UserDao repo) {
- return username -> {
- User u = repo.findByUserNameAndEnabledTrue(username);
- if (u == null)
- throw new UsernameNotFoundException("No existe: " + username);
- return new UserDetailsImpl(u);
- };
- }
+ // ========== Beans base ==========
- @Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
- }
+ @Bean
+ public UserDetailsService userDetailsService(UserDao repo) {
+ return username -> {
+ User u = repo.findByUserNameAndEnabledTrue(username);
+ if (u == null) throw new UsernameNotFoundException("No existe: " + username);
+ return new UserDetailsImpl(u);
+ };
+ }
- // Repositorio de tokens persistentes (usa la tabla 'persistent_logins')
- @Bean
- public PersistentTokenRepository persistentTokenRepository() {
- JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
- repo.setDataSource(dataSource);
- // Descomenta una única vez si quieres que cree la tabla automáticamente:
- // repo.setCreateTableOnStartup(true);
- return repo;
- }
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
- @Bean
- public SecurityFilterChain securityFilterChain(HttpSecurity http, @Value("${security.rememberme.key}") String keyRememberMe) throws Exception {
- http
- .sessionManagement(session -> session
- .invalidSessionUrl("/login?expired")
- .maximumSessions(1) // opcional: limita sesiones concurrentes
- )
- // CSRF habilitado; ignoramos endpoints públicos del presupuesto (AJAX)
- .csrf(csrf -> csrf.ignoringRequestMatchers("/presupuesto/public/**"))
+ // Provider que soporta UsernamePasswordAuthenticationToken
+ @Bean
+ public AuthenticationProvider daoAuthenticationProvider(
+ UserDetailsService userDetailsService,
+ PasswordEncoder passwordEncoder) {
- .authorizeHttpRequests(auth -> auth
- .requestMatchers(
- "/", "/login",
- "/assets/**", "/css/**", "/js/**", "/images/**",
- "/public/**", "/presupuesto/public/**",
- "/error", "/favicon.ico")
- .permitAll()
- .anyRequest().authenticated())
+ DaoAuthenticationProvider p = new DaoAuthenticationProvider();
+ p.setUserDetailsService(userDetailsService);
+ p.setPasswordEncoder(passwordEncoder);
+ return p;
+ }
- .authorizeHttpRequests(configurer -> configurer
- .requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
- )
- .formLogin(login -> login
- .loginPage("/login").permitAll()
- .loginProcessingUrl("/login")
- .usernameParameter("username")
- .passwordParameter("password")
- .defaultSuccessUrl("/", true))
+ // Remember-me (tabla persistent_logins)
+ @Bean
+ public PersistentTokenRepository persistentTokenRepository() {
+ JdbcTokenRepositoryImpl repo = new JdbcTokenRepositoryImpl();
+ repo.setDataSource(dataSource);
+ // repo.setCreateTableOnStartup(true); // solo 1ª vez si necesitas crear la tabla
+ return repo;
+ }
- // ===== Remember Me =====
- .rememberMe(rm -> rm
- .key(keyRememberMe) // clave secreta
- .rememberMeParameter("remember-me") //
- .rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
- .tokenValiditySeconds(60 * 60 * 24 * 14) // 14 días
- .userDetailsService(userDetailsService(null)) // se inyecta el bean real
- // en runtime
- .tokenRepository(persistentTokenRepository()))
+ // ========== Filtro de seguridad ==========
+ @Bean
+ public SecurityFilterChain securityFilterChain(
+ HttpSecurity http,
+ @Value("${security.rememberme.key}") String keyRememberMe,
+ UserDetailsService userDetailsService,
+ PersistentTokenRepository tokenRepo,
+ AuthenticationProvider daoAuthenticationProvider
+ ) throws Exception {
- .logout(logout -> logout
- .logoutUrl("/logout")
- .logoutSuccessUrl("/")
- .invalidateHttpSession(true)
- .deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
- .permitAll());
+ http
+ // Registra explícitamente el provider para Username/Password
+ .authenticationProvider(daoAuthenticationProvider)
- return http.build();
- }
+ .sessionManagement(session -> session
+ .invalidSessionUrl("/login?expired")
+ .maximumSessions(1)
+ )
+
+ .csrf(csrf -> csrf
+ .ignoringRequestMatchers("/presupuesto/public/**")
+ )
+
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/", "/login",
+ "/assets/**", "/css/**", "/js/**", "/images/**",
+ "/public/**", "/presupuesto/public/**",
+ "/error", "/favicon.ico").permitAll()
+ .requestMatchers("/users/**").hasAnyRole("SUPERADMIN", "ADMIN")
+ .anyRequest().authenticated()
+ )
+
+ .formLogin(login -> login
+ .loginPage("/login").permitAll()
+ .loginProcessingUrl("/login")
+ .usernameParameter("username")
+ .passwordParameter("password")
+ .defaultSuccessUrl("/", true)
+ .failureUrl("/login?error") // útil para diagnosticar
+ )
+
+ .rememberMe(rm -> rm
+ .key(keyRememberMe)
+ .rememberMeParameter("remember-me")
+ .rememberMeCookieName("IMPRIMELIBROS_REMEMBER")
+ .tokenValiditySeconds(60 * 60 * 24 * 2)
+ .userDetailsService(userDetailsService)
+ .tokenRepository(tokenRepo)
+ )
+
+ .logout(logout -> logout
+ .logoutUrl("/logout")
+ .logoutSuccessUrl("/")
+ .invalidateHttpSession(true)
+ .deleteCookies("JSESSIONID", "IMPRIMELIBROS_REMEMBER")
+ .permitAll()
+ );
+
+ return http.build();
+ }
}
diff --git a/src/main/java/com/imprimelibros/erp/datatables/DataTable.java b/src/main/java/com/imprimelibros/erp/datatables/DataTable.java
index 27685bf..53a9a3d 100644
--- a/src/main/java/com/imprimelibros/erp/datatables/DataTable.java
+++ b/src/main/java/com/imprimelibros/erp/datatables/DataTable.java
@@ -12,68 +12,117 @@ import java.util.function.Function;
public class DataTable {
- public interface FilterHook extends BiConsumer, DataTablesRequest> {}
- public interface SpecBuilder { void add(Specification extra); }
+ public interface FilterHook extends BiConsumer, DataTablesRequest> {
+ }
+
+ public interface SpecBuilder {
+ void add(Specification extra);
+ }
private final JpaSpecificationExecutor repo;
private final Class entityClass;
private final DataTablesRequest dt;
private final List searchable;
- private final List>> adders = new ArrayList<>();
- private final List, Map>> editors = new ArrayList<>();
+ private final List>> adders = new ArrayList<>();
+ private final List, Map>> editors = new ArrayList<>();
private final List> filters = new ArrayList<>();
- private Specification baseSpec = (root,q,cb) -> cb.conjunction();
+ private Specification baseSpec = (root, q, cb) -> cb.conjunction();
private final ObjectMapper om = new ObjectMapper();
- private DataTable(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt, List searchable) {
- this.repo = repo; this.entityClass = entityClass; this.dt = dt; this.searchable = searchable;
+ private DataTable(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt,
+ List searchable) {
+ this.repo = repo;
+ this.entityClass = entityClass;
+ this.dt = dt;
+ this.searchable = searchable;
}
- public static DataTable of(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt, List searchable) {
+ public static DataTable of(JpaSpecificationExecutor repo, Class entityClass, DataTablesRequest dt,
+ List searchable) {
return new DataTable<>(repo, entityClass, dt, searchable);
}
/** Equivalente a tu $q->where(...): establece condición base */
- public DataTable where(Specification spec) { this.baseSpec = this.baseSpec.and(spec); return this; }
+ public DataTable where(Specification spec) {
+ this.baseSpec = this.baseSpec.and(spec);
+ return this;
+ }
/** add("campo", fn(entity)->valor|Map) */
public DataTable add(String field, Function fn) {
adders.add(entity -> {
- Map m = new HashMap<>();
+ Map m = new HashMap<>();
m.put(field, fn.apply(entity));
return m;
});
return this;
}
- /** add(fn(entity)->Map) para devolver objetos anidados como tu "logo" */
- public DataTable add(Function> fn) { adders.add(fn); return this; }
+ /**
+ * add(fn(entity)->Map) para devolver objetos anidados como tu
+ * "logo"
+ */
+ public DataTable add(Function> fn) {
+ adders.add(fn);
+ return this;
+ }
- /** edit("campo", fn(entity)->valor) sobreescribe un campo existente o lo crea si no existe */
+ /**
+ * edit("campo", fn(entity)->valor) sobreescribe un campo existente o lo crea si
+ * no existe
+ */
public DataTable edit(String field, Function fn) {
- editors.add(row -> { row.put(field, fn.apply((T)row.get("__entity"))); return row; });
+ editors.add(row -> {
+ row.put(field, fn.apply((T) row.get("__entity")));
+ return row;
+ });
return this;
}
/** filter((builder, req) -> builder.add(miExtraSpec(req))) */
- public DataTable filter(FilterHook hook) { filters.add(hook); return this; }
+ public DataTable filter(FilterHook hook) {
+ filters.add(hook);
+ return this;
+ }
- public DataTablesResponse