diff --git a/pom.xml b/pom.xml
index acc4df5..56ecef0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,12 @@
spring-boot-starter-data-jpa
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
com.mysql
mysql-connector-j
diff --git a/src/main/java/com/printhub/printhub/config/PasswordEncoderConfig.java b/src/main/java/com/printhub/printhub/config/PasswordEncoderConfig.java
new file mode 100644
index 0000000..4152b06
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/config/PasswordEncoderConfig.java
@@ -0,0 +1,15 @@
+package com.printhub.printhub.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class PasswordEncoderConfig {
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
diff --git a/src/main/java/com/printhub/printhub/config/SecurityConfig.java b/src/main/java/com/printhub/printhub/config/SecurityConfig.java
new file mode 100644
index 0000000..fdb9f53
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/config/SecurityConfig.java
@@ -0,0 +1,50 @@
+package com.printhub.printhub.config;
+
+import com.printhub.printhub.service.configuration.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.*;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.authentication.configuration.*;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.*;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Autowired
+ private UserService userService;
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(csrf -> csrf.disable())
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/login", "/register", "/assets/**", "/api/lang", "/api/lang/**").permitAll()
+ .anyRequest().authenticated())
+ .userDetailsService(userService)
+ .formLogin(login -> login
+ .loginPage("/login")
+ .loginProcessingUrl("/login")
+ .defaultSuccessUrl("/", true)
+ .failureUrl("/login?error=true")
+ .permitAll())
+ .logout(logout -> logout
+ .logoutUrl("/logout")
+ .logoutSuccessHandler((request, response, authentication) -> {
+ String lang = request.getParameter("lang");
+ if (lang == null || lang.isBlank()) lang = "es";
+ response.sendRedirect("/login?logout&lang=" + lang);
+ }));
+
+
+ return http.build();
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig)
+ throws Exception {
+ return authConfig.getAuthenticationManager();
+ }
+}
diff --git a/src/main/java/com/printhub/printhub/controller/config/CurrentUserAdvice.java b/src/main/java/com/printhub/printhub/controller/config/CurrentUserAdvice.java
new file mode 100644
index 0000000..1edd804
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/controller/config/CurrentUserAdvice.java
@@ -0,0 +1,25 @@
+package com.printhub.printhub.controller.config;
+
+import com.printhub.printhub.entity.configuration.User;
+import com.printhub.printhub.repository.configuration.UserRepository;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.beans.factory.annotation.Autowired;
+
+@ControllerAdvice
+public class CurrentUserAdvice {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @ModelAttribute("currentUser")
+ public User getCurrentUser() {
+ Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+ if (principal instanceof UserDetails userDetails) {
+ return userRepository.findByUsername(userDetails.getUsername()).orElse(null);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/printhub/printhub/controller/configuration/AuthController.java b/src/main/java/com/printhub/printhub/controller/configuration/AuthController.java
new file mode 100644
index 0000000..0b1454f
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/controller/configuration/AuthController.java
@@ -0,0 +1,48 @@
+package com.printhub.printhub.controller.configuration;
+
+import com.printhub.printhub.service.configuration.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.stereotype.Controller;
+
+import com.printhub.printhub.entity.configuration.User;
+import com.printhub.printhub.repository.configuration.UserRepository;
+
+@Controller
+public class AuthController {
+
+ private UserService userService;
+ private UserRepository userRepository;
+
+ @Autowired
+ public AuthController(UserRepository userRepository, UserService userService) {
+ this.userRepository = userRepository;
+ this.userService = userService;
+ }
+
+ @PostMapping("/register")
+ public String register(@RequestParam String username,
+ @RequestParam String password,
+ @RequestParam(defaultValue = "USER") String role) {
+ userService.register(username, password, role);
+ return "Usuario registrado correctamente.";
+ }
+
+ @GetMapping("/login")
+ public String login() {
+ return "printhub/login";
+ }
+
+ @ModelAttribute("currentUser")
+ public User currentUser() {
+ Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+
+ if (principal instanceof org.springframework.security.core.userdetails.User springUser) {
+ return userRepository.findByUsername(springUser.getUsername())
+ .orElse(null);
+ }
+
+ return null;
+ }
+}
diff --git a/src/main/java/com/printhub/printhub/controller/configuration/PrintersController.java b/src/main/java/com/printhub/printhub/controller/configuration/PrintersController.java
index fa52eaf..dc1de7f 100644
--- a/src/main/java/com/printhub/printhub/controller/configuration/PrintersController.java
+++ b/src/main/java/com/printhub/printhub/controller/configuration/PrintersController.java
@@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
-import com.printhub.printhub.model.Breadcrum;
+import com.printhub.printhub.helper.Breadcrum;
import com.printhub.printhub.service.configuration.PrinterService;
import com.printhub.printhub.utils.datatables.DataTableRequest;
diff --git a/src/main/java/com/printhub/printhub/entity/configuration/Customer.java b/src/main/java/com/printhub/printhub/entity/configuration/Customer.java
new file mode 100644
index 0000000..91b8cdf
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/entity/configuration/Customer.java
@@ -0,0 +1,68 @@
+package com.printhub.printhub.entity.configuration;
+
+import jakarta.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "customers")
+public class Customer {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ private String name;
+ private String alias;
+ private String cif;
+ private String address;
+ private String city;
+ private String zipCode;
+ private String country;
+ private String phone;
+ private String email;
+
+ private LocalDateTime createdAt;
+ private LocalDateTime updatedAt;
+ private LocalDateTime deletedAt;
+
+ // Getters y Setters
+
+ public Long getId() { return id; }
+ public void setId(Long id) { this.id = id; }
+
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+
+ public String getAlias() { return alias; }
+ public void setAlias(String alias) { this.alias = alias; }
+
+ public String getCif() { return cif; }
+ public void setCif(String cif) { this.cif = cif; }
+
+ public String getAddress() { return address; }
+ public void setAddress(String address) { this.address = address; }
+
+ public String getCity() { return city; }
+ public void setCity(String city) { this.city = city; }
+
+ public String getZipCode() { return zipCode; }
+ public void setZipCode(String zipCode) { this.zipCode = zipCode; }
+
+ public String getCountry() { return country; }
+ public void setCountry(String country) { this.country = country; }
+
+ public String getPhone() { return phone; }
+ public void setPhone(String phone) { this.phone = phone; }
+
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+
+ public LocalDateTime getCreatedAt() { return createdAt; }
+ public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
+
+ public LocalDateTime getUpdatedAt() { return updatedAt; }
+ public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
+
+ public LocalDateTime getDeletedAt() { return deletedAt; }
+ public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
+}
diff --git a/src/main/java/com/printhub/printhub/model/configuration/Printer.java b/src/main/java/com/printhub/printhub/entity/configuration/Printer.java
similarity index 90%
rename from src/main/java/com/printhub/printhub/model/configuration/Printer.java
rename to src/main/java/com/printhub/printhub/entity/configuration/Printer.java
index 47cc4e6..97ec6b6 100644
--- a/src/main/java/com/printhub/printhub/model/configuration/Printer.java
+++ b/src/main/java/com/printhub/printhub/entity/configuration/Printer.java
@@ -1,4 +1,4 @@
-package com.printhub.printhub.model.configuration;
+package com.printhub.printhub.entity.configuration;
import jakarta.persistence.*;
diff --git a/src/main/java/com/printhub/printhub/entity/configuration/User.java b/src/main/java/com/printhub/printhub/entity/configuration/User.java
new file mode 100644
index 0000000..0f74fda
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/entity/configuration/User.java
@@ -0,0 +1,96 @@
+package com.printhub.printhub.entity.configuration;
+
+import jakarta.persistence.*;
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "users")
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(nullable = false, unique = true)
+ private String username;
+
+ @Column(nullable = false)
+ private String password;
+
+ @Column(nullable = false)
+ private String role; // Ej: ROLE_USER, ROLE_ADMIN
+
+ @Column(nullable = false)
+ private boolean enabled = true;
+
+ // Campos adicionales
+ private String firstName;
+ private String lastName;
+
+ @Column(columnDefinition = "TEXT")
+ private String comments;
+
+ private LocalDateTime lastActive;
+
+ private LocalDateTime createdAt;
+ private LocalDateTime updatedAt;
+ private LocalDateTime deletedAt;
+
+ @ManyToOne
+ @JoinColumn(name = "customer_id")
+ private Customer customer;
+
+ // Getters y Setters
+
+ public Long getId() { return id; }
+
+ public void setId(Long id) { this.id = id; }
+
+ public String getUsername() { return username; }
+
+ public void setUsername(String username) { this.username = username; }
+
+ public String getPassword() { return password; }
+
+ public void setPassword(String password) { this.password = password; }
+
+ public String getRole() { return role; }
+
+ public void setRole(String role) { this.role = role; }
+
+ public boolean isEnabled() { return enabled; }
+
+ public void setEnabled(boolean enabled) { this.enabled = enabled; }
+
+ public String getFirstName() { return firstName; }
+
+ public void setFirstName(String firstName) { this.firstName = firstName; }
+
+ public String getLastName() { return lastName; }
+
+ public void setLastName(String lastName) { this.lastName = lastName; }
+
+ public String getComments() { return comments; }
+
+ public void setComments(String comments) { this.comments = comments; }
+
+ public LocalDateTime getLastActive() { return lastActive; }
+
+ public void setLastActive(LocalDateTime lastActive) { this.lastActive = lastActive; }
+
+ public LocalDateTime getCreatedAt() { return createdAt; }
+
+ public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
+
+ public LocalDateTime getUpdatedAt() { return updatedAt; }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
+
+ public LocalDateTime getDeletedAt() { return deletedAt; }
+
+ public void setDeletedAt(LocalDateTime deletedAt) { this.deletedAt = deletedAt; }
+
+ public Customer getCustomer() { return customer; }
+
+ public void setCustomer(Customer customer) { this.customer = customer; }
+}
\ No newline at end of file
diff --git a/src/main/java/com/printhub/printhub/model/Breadcrum.java b/src/main/java/com/printhub/printhub/helper/Breadcrum.java
similarity index 96%
rename from src/main/java/com/printhub/printhub/model/Breadcrum.java
rename to src/main/java/com/printhub/printhub/helper/Breadcrum.java
index d91fdf1..e3749c1 100644
--- a/src/main/java/com/printhub/printhub/model/Breadcrum.java
+++ b/src/main/java/com/printhub/printhub/helper/Breadcrum.java
@@ -1,4 +1,4 @@
-package com.printhub.printhub.model;
+package com.printhub.printhub.helper;
import java.util.ArrayList;
import java.util.List;
diff --git a/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java b/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java
index 1b6a174..f7faa43 100644
--- a/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java
+++ b/src/main/java/com/printhub/printhub/repository/configuration/PrinterRepository.java
@@ -1,9 +1,10 @@
package com.printhub.printhub.repository.configuration;
-import com.printhub.printhub.model.configuration.Printer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import com.printhub.printhub.entity.configuration.Printer;
+
import java.util.List;
public interface PrinterRepository extends JpaRepository, JpaSpecificationExecutor {
diff --git a/src/main/java/com/printhub/printhub/repository/configuration/UserRepository.java b/src/main/java/com/printhub/printhub/repository/configuration/UserRepository.java
new file mode 100644
index 0000000..3649789
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/repository/configuration/UserRepository.java
@@ -0,0 +1,11 @@
+package com.printhub.printhub.repository.configuration;
+
+import com.printhub.printhub.entity.configuration.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+
+
+public interface UserRepository extends JpaRepository {
+ Optional findByUsername(String username);
+}
diff --git a/src/main/java/com/printhub/printhub/security/AuthenticationSuccessListener.java b/src/main/java/com/printhub/printhub/security/AuthenticationSuccessListener.java
new file mode 100644
index 0000000..27092b5
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/security/AuthenticationSuccessListener.java
@@ -0,0 +1,30 @@
+package com.printhub.printhub.security;
+
+import com.printhub.printhub.entity.configuration.User;
+import com.printhub.printhub.repository.configuration.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Component
+public class AuthenticationSuccessListener implements ApplicationListener {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Override
+ public void onApplicationEvent(AuthenticationSuccessEvent event) {
+ Object principal = event.getAuthentication().getPrincipal();
+
+ if (principal instanceof UserDetails userDetails) {
+ userRepository.findByUsername(userDetails.getUsername()).ifPresent(user -> {
+ user.setLastActive(LocalDateTime.now());
+ userRepository.save(user);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java b/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java
index 44da2dc..447b4ee 100644
--- a/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java
+++ b/src/main/java/com/printhub/printhub/service/configuration/PrinterService.java
@@ -1,6 +1,6 @@
package com.printhub.printhub.service.configuration;
-import com.printhub.printhub.model.configuration.Printer;
+import com.printhub.printhub.entity.configuration.Printer;
import com.printhub.printhub.repository.configuration.PrinterRepository;
import com.printhub.printhub.specification.configuration.PrinterSpecification;
import com.printhub.printhub.utils.datatables.DataTableBuilder;
diff --git a/src/main/java/com/printhub/printhub/service/configuration/UserService.java b/src/main/java/com/printhub/printhub/service/configuration/UserService.java
new file mode 100644
index 0000000..d0d1c17
--- /dev/null
+++ b/src/main/java/com/printhub/printhub/service/configuration/UserService.java
@@ -0,0 +1,50 @@
+package com.printhub.printhub.service.configuration;
+
+import com.printhub.printhub.entity.configuration.User;
+import com.printhub.printhub.repository.configuration.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.*;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.Optional;
+
+@Service
+public class UserService implements UserDetailsService {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Autowired
+ private PasswordEncoder passwordEncoder;
+
+ public void register(String username, String password, String role) {
+ User user = new User();
+ user.setUsername(username);
+ user.setPassword(passwordEncoder.encode(password));
+ user.setRole("ROLE_" + role.toUpperCase());
+ user.setEnabled(true);
+ userRepository.save(user);
+ }
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ Optional userOpt = userRepository.findByUsername(username);
+ if (userOpt.isEmpty() || userOpt.get().getDeletedAt() != null) {
+ throw new UsernameNotFoundException("Usuario no encontrado o eliminado");
+ }
+
+ User user = userOpt.get();
+ return new org.springframework.security.core.userdetails.User(
+ user.getUsername(),
+ user.getPassword(),
+ user.isEnabled(),
+ true,
+ true,
+ user.getDeletedAt() == null,
+ Collections.singletonList(new SimpleGrantedAuthority(user.getRole()))
+ );
+ }
+}
diff --git a/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java b/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java
index 7707ce8..828063b 100644
--- a/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java
+++ b/src/main/java/com/printhub/printhub/specification/configuration/PrinterSpecification.java
@@ -1,6 +1,6 @@
package com.printhub.printhub.specification.configuration;
-import com.printhub.printhub.model.configuration.Printer;
+import com.printhub.printhub.entity.configuration.Printer;
import com.printhub.printhub.utils.datatables.DataTableRequest;
import org.springframework.data.jpa.domain.Specification;
diff --git a/src/main/resources/i18n/en/general.properties b/src/main/resources/i18n/en/general.properties
index 403dc26..9cf2ec2 100644
--- a/src/main/resources/i18n/en/general.properties
+++ b/src/main/resources/i18n/en/general.properties
@@ -1 +1,10 @@
-t-paginas=Pages
\ No newline at end of file
+# Breadcrumbs
+t-paginas=Pages
+
+# Roles
+t-role.ROLE_ADMIN=Administrator
+t-role.ROLE_USER=User
+
+# Topbar
+t-topbar-logout=Logout
+t-topbar-profile=Profile
\ No newline at end of file
diff --git a/src/main/resources/i18n/en/login.properties b/src/main/resources/i18n/en/login.properties
new file mode 100644
index 0000000..591094e
--- /dev/null
+++ b/src/main/resources/i18n/en/login.properties
@@ -0,0 +1,16 @@
+t-login-title=Welcome to Print Hub!
+t-login-subtitle=Sign in to continue
+t-login-username=Username
+t-login-password=Password
+t-login-button=Sign In
+
+t-login-password-recovery-message=Forgot your password?
+t-login-password-recovery=Recover Password
+
+t-login-signup-message=Don't have an account ?
+t-login-signup-signup=Sign Up
+
+t-login-logout=You have logged out successfully
+t-login-error=Incorrect username or password
+
+
diff --git a/src/main/resources/i18n/es/general.properties b/src/main/resources/i18n/es/general.properties
index 3c0d0aa..9cf5c47 100644
--- a/src/main/resources/i18n/es/general.properties
+++ b/src/main/resources/i18n/es/general.properties
@@ -1 +1,10 @@
-t-paginas=Páginas
\ No newline at end of file
+# Breadcrumbs
+t-paginas=Páginas
+
+# Roles
+t-role.ROLE_ADMIN=Administrador
+t-role.ROLE_USER=Usuario
+
+# Topbar
+t-topbar-logout=Cerrar sesión
+t-topbar-profile=Perfil
\ No newline at end of file
diff --git a/src/main/resources/i18n/es/login.properties b/src/main/resources/i18n/es/login.properties
new file mode 100644
index 0000000..7754ec9
--- /dev/null
+++ b/src/main/resources/i18n/es/login.properties
@@ -0,0 +1,14 @@
+t-login-title=¡Bienvenido a Print Hub!
+t-login-subtitle=Inicia sesión para continuar
+t-login-username=Nombre de usuario
+t-login-password=Contraseña
+t-login-button=Iniciar sesión
+
+t-login-password-recovery-message=¿Olvidaste tu contraseña?
+t-login-password-recovery=Recuperar contraseña
+
+t-login-signup-message=¿No tienes una cuenta?
+t-login-signup-signup=Registrarse
+
+t-login-logout=Has cerrado sesión correctamente
+t-login-error=Nombre de usuario o contraseña incorrectos
diff --git a/src/main/resources/templates/printhub/layout.html b/src/main/resources/templates/printhub/layout.html
index 1db50be..0d22117 100644
--- a/src/main/resources/templates/printhub/layout.html
+++ b/src/main/resources/templates/printhub/layout.html
@@ -49,6 +49,7 @@
+