diff --git a/pom.xml b/pom.xml
index 98d0a43..aa0ac50 100644
--- a/pom.xml
+++ b/pom.xml
@@ -60,6 +60,16 @@
spring-dotenv
3.0.0
+
+ org.projectlombok
+ lombok
+ true
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+ 3.4.5
+
diff --git a/src/main/java/gtp/filesmanager/config/SecurityConfig.java b/src/main/java/gtp/filesmanager/config/SecurityConfig.java
new file mode 100644
index 0000000..eabc258
--- /dev/null
+++ b/src/main/java/gtp/filesmanager/config/SecurityConfig.java
@@ -0,0 +1,53 @@
+package gtp.filesmanager.config;
+
+import gtp.filesmanager.service.UsersDetailService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.Customizer;
+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;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Autowired
+ private UsersDetailService usersDetailService;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(config -> config.disable())
+ .authorizeHttpRequests(request -> request
+ .requestMatchers("/register","/login") //allow login and register url
+ .permitAll()
+ .anyRequest()
+ .authenticated()//
+ )
+ .httpBasic(Customizer.withDefaults())
+ .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
+ return http.build();
+ }
+
+ @Bean
+ public AuthenticationProvider authenticationProvider(){
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setPasswordEncoder(new BCryptPasswordEncoder(12));
+ authProvider.setUserDetailsService(usersDetailService);
+
+ return authProvider;
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
+ return configuration.getAuthenticationManager();
+ }
+}
diff --git a/src/main/java/gtp/filesmanager/controller/UserController.java b/src/main/java/gtp/filesmanager/controller/UserController.java
index e69de29..84806e7 100644
--- a/src/main/java/gtp/filesmanager/controller/UserController.java
+++ b/src/main/java/gtp/filesmanager/controller/UserController.java
@@ -0,0 +1,47 @@
+package gtp.filesmanager.controller;
+
+import gtp.filesmanager.dto.request.LoginRequest;
+import gtp.filesmanager.dto.request.RegisterRequest;
+import gtp.filesmanager.model.Users;
+import gtp.filesmanager.service.UserServiceImpl;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.logging.Logger;
+
+@RestController
+//@RequestMapping("/api/auth")
+public class UserController {
+ private final Logger logger = Logger.getLogger(UserController.class.getName());
+ private final UserServiceImpl userServiceImpl;
+
+ //inject UserServiceImpl via constructor
+ public UserController(UserServiceImpl userServiceImpl) {
+ this.userServiceImpl = userServiceImpl;
+ }
+
+ @PostMapping(path = "/register")
+ protected Object registerUser(@RequestBody RegisterRequest registerRequest){
+ logger.info("Registering user with name: " + registerRequest.getUserName());
+ if(registerRequest.getUserName() == null || registerRequest.getUserEmail() == null || registerRequest.getPassword() == null){
+ throw new RuntimeException("Invalid request");
+ }
+ try{
+ return userServiceImpl.registerUser(registerRequest);
+ }catch (RuntimeException e){
+ return e.getMessage();
+ }
+ }
+
+ @PostMapping(path = "/login")
+ protected Object loginUser(@RequestBody LoginRequest loginRequest){
+ logger.info("Logging in user with name: " + loginRequest.getUserName());
+ if(loginRequest.getUserName() == null || loginRequest.getPassword() == null){
+ throw new RuntimeException("Invalid request");
+ }
+ try{
+ return userServiceImpl.loginUser(loginRequest);
+ }catch (RuntimeException e){
+ return e.getMessage();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/dto/request/LoginRequest.java b/src/main/java/gtp/filesmanager/dto/request/LoginRequest.java
index e69de29..2c6defb 100644
--- a/src/main/java/gtp/filesmanager/dto/request/LoginRequest.java
+++ b/src/main/java/gtp/filesmanager/dto/request/LoginRequest.java
@@ -0,0 +1,11 @@
+package gtp.filesmanager.dto.request;
+
+import lombok.Getter;
+import org.antlr.v4.runtime.misc.NotNull;
+
+//DTO to accept when logging in
+@Getter
+public class LoginRequest {
+ private String userName;
+ private String password;
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/dto/request/RegisterRequest.java b/src/main/java/gtp/filesmanager/dto/request/RegisterRequest.java
index e69de29..4123582 100644
--- a/src/main/java/gtp/filesmanager/dto/request/RegisterRequest.java
+++ b/src/main/java/gtp/filesmanager/dto/request/RegisterRequest.java
@@ -0,0 +1,11 @@
+package gtp.filesmanager.dto.request;
+
+import lombok.Getter;
+
+@Getter
+public class RegisterRequest {
+
+ private String userName;
+ private String password;
+ private String userEmail;
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/dto/response/UserResponse.java b/src/main/java/gtp/filesmanager/dto/response/UserResponse.java
index e69de29..09c11b8 100644
--- a/src/main/java/gtp/filesmanager/dto/response/UserResponse.java
+++ b/src/main/java/gtp/filesmanager/dto/response/UserResponse.java
@@ -0,0 +1,11 @@
+package gtp.filesmanager.dto.response;
+
+import lombok.Getter;
+
+@Getter
+public class UserResponse {
+ private Integer Id;
+ private String userName;
+ private String userEmail;
+ private Boolean isActive;
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/model/User.java b/src/main/java/gtp/filesmanager/model/User.java
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/java/gtp/filesmanager/model/UserPrincipal.java b/src/main/java/gtp/filesmanager/model/UserPrincipal.java
new file mode 100644
index 0000000..9c041cb
--- /dev/null
+++ b/src/main/java/gtp/filesmanager/model/UserPrincipal.java
@@ -0,0 +1,53 @@
+package gtp.filesmanager.model;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+
+public class UserPrincipal implements UserDetails {
+
+ private final Users userEntity;
+
+ public UserPrincipal(Users userEntity){
+ this.userEntity = userEntity;
+ }
+
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ return List.of();
+ }
+
+ @Override
+ public String getPassword() {
+ return userEntity.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ return userEntity.getUserName();
+ }
+
+ @Override
+ public boolean isAccountNonExpired() {
+ return UserDetails.super.isAccountNonExpired();
+ }
+
+ @Override
+ public boolean isAccountNonLocked() {
+ return UserDetails.super.isAccountNonLocked();
+ }
+
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return UserDetails.super.isCredentialsNonExpired();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return UserDetails.super.isEnabled();
+ }
+}
diff --git a/src/main/java/gtp/filesmanager/model/Users.java b/src/main/java/gtp/filesmanager/model/Users.java
new file mode 100644
index 0000000..73e19c0
--- /dev/null
+++ b/src/main/java/gtp/filesmanager/model/Users.java
@@ -0,0 +1,39 @@
+package gtp.filesmanager.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.sql.Date;
+
+//create a user entity
+@Getter
+@Entity
+@Table(name = "users")
+public class Users {
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Integer id;
+
+ @Setter
+ @Column(unique = true,nullable = false)
+ private String userName; //unique for all
+
+ @Setter
+ @JsonIgnore //ignores this field when serializing the object to JSON
+ private String password;
+
+ @Setter
+ @Column(unique = true,nullable = false)
+ private String userEmail;
+
+ @Setter
+ private Boolean isActive;
+
+ @Setter
+ private Date createdAt;
+
+ @Setter
+ private Date updatedAt;
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/repository/UserRepository.java b/src/main/java/gtp/filesmanager/repository/UserRepository.java
index e69de29..ce760ad 100644
--- a/src/main/java/gtp/filesmanager/repository/UserRepository.java
+++ b/src/main/java/gtp/filesmanager/repository/UserRepository.java
@@ -0,0 +1,9 @@
+package gtp.filesmanager.repository;
+
+import gtp.filesmanager.model.Users;
+import org.springframework.data.repository.CrudRepository;
+
+public interface UserRepository extends CrudRepository {
+ public Users findByUserName(String userName);
+ public Users findByUserEmail(String userEmail);
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/service/UserService.java b/src/main/java/gtp/filesmanager/service/UserService.java
index e69de29..4c99bd4 100644
--- a/src/main/java/gtp/filesmanager/service/UserService.java
+++ b/src/main/java/gtp/filesmanager/service/UserService.java
@@ -0,0 +1,10 @@
+package gtp.filesmanager.service;
+
+import gtp.filesmanager.dto.request.LoginRequest;
+import gtp.filesmanager.dto.request.RegisterRequest;
+import gtp.filesmanager.model.Users;
+
+public interface UserService {
+ abstract Users registerUser(RegisterRequest registerRequest);
+ abstract Users loginUser(LoginRequest loginRequest);
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/service/UserServiceImpl.java b/src/main/java/gtp/filesmanager/service/UserServiceImpl.java
new file mode 100644
index 0000000..676d5bb
--- /dev/null
+++ b/src/main/java/gtp/filesmanager/service/UserServiceImpl.java
@@ -0,0 +1,67 @@
+package gtp.filesmanager.service;
+
+import gtp.filesmanager.dto.request.LoginRequest;
+import gtp.filesmanager.dto.request.RegisterRequest;
+import gtp.filesmanager.model.Users;
+import gtp.filesmanager.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UserServiceImpl implements UserService { // Removed 'abstract' as this should be a concrete implementation
+
+ @Autowired
+ private UserRepository userRepository;
+
+ private BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(12); // Renamed from 'encoder' for clarity
+
+ @Autowired
+ private AuthenticationManager authManager;
+
+
+ @Override
+ public Users registerUser(RegisterRequest registerRequest) {
+ // Check if a user already exists
+ if (userRepository.findByUserName(registerRequest.getUserName()) != null) {
+ throw new RuntimeException("Username already exists");
+ }
+
+ if (userRepository.findByUserEmail(registerRequest.getUserEmail()) != null) {
+ throw new RuntimeException("Email already registered");
+ }
+
+ // Create a new user entity
+ Users newUser = new Users();
+ newUser.setUserName(registerRequest.getUserName());
+ newUser.setUserEmail(registerRequest.getUserEmail());
+ newUser.setPassword(passwordEncoder.encode(registerRequest.getPassword())); // Encode password
+
+ return userRepository.save(newUser);
+ }
+
+ @Override
+ public Users loginUser(LoginRequest loginRequest){
+ Users user = userRepository.findByUserName(loginRequest.getUserName());
+ if(user == null){
+ throw new RuntimeException("Invalid username");
+ }
+
+ //Authenticate user
+ Authentication authentication = authManager
+ .authenticate(new UsernamePasswordAuthenticationToken(
+ loginRequest.getUserName(), loginRequest.getPassword()
+ ));
+
+ //check user authentication is successful
+ if (authentication.isAuthenticated()){
+ return user;
+ }else{
+ throw new RuntimeException("Invalid password");
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/gtp/filesmanager/service/UsersDetailService.java b/src/main/java/gtp/filesmanager/service/UsersDetailService.java
new file mode 100644
index 0000000..7e7c192
--- /dev/null
+++ b/src/main/java/gtp/filesmanager/service/UsersDetailService.java
@@ -0,0 +1,24 @@
+package gtp.filesmanager.service;
+
+import gtp.filesmanager.model.UserPrincipal;
+import gtp.filesmanager.model.Users;
+import gtp.filesmanager.repository.UserRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+@Service
+public class UsersDetailService implements UserDetailsService {
+
+ @Autowired
+ private UserRepository userRepository;
+
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ Users userEntity = userRepository.findByUserName(username);
+
+ return new UserPrincipal(userEntity);
+ }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 6d83714..d8aa98a 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -4,6 +4,7 @@ spring.datasource.username=${POSTGRES_USER}
spring.datasource.password=${POSTGRES_PASSWORD}
spring.datasource.driver-class-name=org.postgresql.Driver
+spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.show-sql=true
\ No newline at end of file