From 52fbab5d36669eecf97cbb78c74fcd639a7d2c80 Mon Sep 17 00:00:00 2001
From: Samuel Gunda <samuel.gunda@kosickaakademia.sk>
Date: Wed, 8 May 2024 13:32:57 +0200
Subject: [PATCH 1/4] tryout of implementing jwt token auth

---
 pom.xml                                       |  81 ++++---
 .../bootcamp/controllers/AuthController.java  | 206 ++++++++++--------
 .../controllers/StudentController.java        |  71 +++---
 .../bootcamp/controllers/TestController.java  |  35 +++
 .../bootcamp/controllers/UserController.java  |  64 ------
 .../gunda/bootcamp/entities/AuthCheck.java    |  13 --
 .../bootcamp/entities/LogoutRequest.java      |  16 --
 .../bootcamp/entities/UpdatePassword.java     |  15 --
 .../bootcamp/entities/UpdateStudent.java      |   4 -
 .../gunda/bootcamp/entities/UpdateUser.java   |  14 --
 .../kasv/gunda/bootcamp/entities/User.java    |  38 ----
 .../exceptions/BlacklistedJwtException.java   |   8 +
 .../exceptions/JwtTokenException.java         |   8 +
 .../com/kasv/gunda/bootcamp/models/ERole.java |   7 +
 .../com/kasv/gunda/bootcamp/models/Role.java  |  39 ++++
 .../{entities => models}/Student.java         |   2 +-
 .../com/kasv/gunda/bootcamp/models/User.java  |  53 +++++
 .../request}/LoginRequest.java                |  13 +-
 .../payload/request/SignupRequest.java        |  29 +++
 .../payload/response/MessageResponse.java     |  17 ++
 .../payload/response/UserResponse.java        |  45 ++++
 .../bootcamp/repositories/RoleRepository.java |  13 ++
 .../repositories/StudentRepository.java       |   2 +-
 .../bootcamp/repositories/UserRepository.java |  17 +-
 .../bootcamp/security/WebSecurityConfig.java  |  79 +++++++
 .../security/jwt/AuthEntryPointJwt.java       |  25 +++
 .../security/jwt/AuthTokenFilter.java         |  58 +++++
 .../gunda/bootcamp/security/jwt/JwtUtils.java |  86 ++++++++
 .../security/services/UserDetailsImpl.java    | 102 +++++++++
 .../services/UserDetailsServiceImpl.java      |  26 +++
 .../gunda/bootcamp/services/AuthService.java  | 162 --------------
 .../bootcamp/services/StudentService.java     | 168 +++++++-------
 .../gunda/bootcamp/services/UserService.java  |  83 -------
 .../utilities/RandomPasswordGenerator.java    |  13 --
 .../bootcamp/utilities/TokenFunctions.java    |  57 -----
 .../bootcamp/utilities/TokenGenerator.java    |  24 --
 .../utilities/UserTimeoutFunctions.java       |  59 -----
 src/main/resources/application.properties     |   8 +-
 .../bootcamp/BootcampApplicationTests.java    |   5 -
 .../repositories/StudentRepositoryTests.java  |  96 --------
 .../repositories/UserRepositoryTests.java     |  89 --------
 .../bootcamp/services/AuthServicesTests.java  |   5 -
 42 files changed, 939 insertions(+), 1016 deletions(-)
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/controllers/TestController.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/controllers/UserController.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/entities/AuthCheck.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/entities/LogoutRequest.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/entities/UpdatePassword.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/entities/UpdateStudent.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/entities/UpdateUser.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/entities/User.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/models/ERole.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/models/Role.java
 rename src/main/java/com/kasv/gunda/bootcamp/{entities => models}/Student.java (92%)
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/models/User.java
 rename src/main/java/com/kasv/gunda/bootcamp/{entities => payload/request}/LoginRequest.java (50%)
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/request/SignupRequest.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/repositories/RoleRepository.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsServiceImpl.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/services/AuthService.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/services/UserService.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/utilities/RandomPasswordGenerator.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/utilities/TokenFunctions.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/utilities/TokenGenerator.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/utilities/UserTimeoutFunctions.java
 delete mode 100644 src/test/java/com/kasv/gunda/bootcamp/repositories/StudentRepositoryTests.java
 delete mode 100644 src/test/java/com/kasv/gunda/bootcamp/repositories/UserRepositoryTests.java
 delete mode 100644 src/test/java/com/kasv/gunda/bootcamp/services/AuthServicesTests.java

diff --git a/pom.xml b/pom.xml
index 63b55d5..2cb45c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -17,21 +17,47 @@
         <java.version>21</java.version>
     </properties>
     <dependencies>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-rest</artifactId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-web</artifactId>
+            <artifactId>spring-boot-starter-security</artifactId>
         </dependency>
 
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-devtools</artifactId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+            <version>0.11.5</version>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+            <version>0.11.5</version>
+            <scope>runtime</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+            <version>0.11.5</version>
             <scope>runtime</scope>
-            <optional>true</optional>
         </dependency>
+
         <dependency>
             <groupId>org.mariadb.jdbc</groupId>
             <artifactId>mariadb-java-client</artifactId>
@@ -40,57 +66,40 @@
         <dependency>
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
-            <optional>true</optional>
+            <scope>provided</scope>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-test</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.security</groupId>
-            <artifactId>spring-security-test</artifactId>
-            <scope>test</scope>
+            <artifactId>spring-boot-starter-mail</artifactId>
         </dependency>
+
         <dependency>
             <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-jpa</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework</groupId>
-            <artifactId>spring-web</artifactId>
-            <version>6.1.4</version>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.gson</groupId>
-            <artifactId>gson</artifactId>
-            <version>2.10.1</version>
+            <artifactId>spring-boot-devtools</artifactId>
+            <scope>runtime</scope>
+            <optional>true</optional>
         </dependency>
+
+
         <dependency>
             <groupId>org.springdoc</groupId>
             <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
             <version>2.1.0</version>
         </dependency>
+
         <dependency>
             <groupId>io.springfox</groupId>
             <artifactId>springfox-spring-web</artifactId>
             <version>3.0.0</version>
         </dependency>
+
         <dependency>
-            <groupId>org.hibernate.orm</groupId>
-            <artifactId>hibernate-core</artifactId>
-            <version>6.4.4.Final</version>
-        </dependency>
-        <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-mail</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.h2database</groupId>
-            <artifactId>h2</artifactId>
-            <version>2.1.214</version>
-            <scope>test</scope>
+            <groupId>com.google.code.gson</groupId>
+            <artifactId>gson</artifactId>
+            <version>2.10.1</version>
         </dependency>
+
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
index 68a6a84..3b142f7 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
@@ -1,119 +1,151 @@
 package com.kasv.gunda.bootcamp.controllers;
 
-import com.google.gson.Gson;
-import com.kasv.gunda.bootcamp.entities.AuthCheck;
-import com.kasv.gunda.bootcamp.entities.LoginRequest;
-import com.kasv.gunda.bootcamp.entities.LogoutRequest;
-import com.kasv.gunda.bootcamp.entities.User;
-import com.kasv.gunda.bootcamp.services.AuthService;
+import com.kasv.gunda.bootcamp.models.ERole;
+import com.kasv.gunda.bootcamp.models.Role;
+import com.kasv.gunda.bootcamp.models.User;
+import com.kasv.gunda.bootcamp.payload.request.LoginRequest;
+import com.kasv.gunda.bootcamp.payload.request.SignupRequest;
+import com.kasv.gunda.bootcamp.payload.response.MessageResponse;
+import com.kasv.gunda.bootcamp.payload.response.UserResponse;
+import com.kasv.gunda.bootcamp.repositories.RoleRepository;
+import com.kasv.gunda.bootcamp.repositories.UserRepository;
+import com.kasv.gunda.bootcamp.security.jwt.JwtUtils;
+import com.kasv.gunda.bootcamp.security.services.UserDetailsImpl;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.validation.Valid;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseCookie;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
+@CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
 @RestController
-@RequestMapping("/api")
-@CrossOrigin
+@RequestMapping("/api/auth")
 public class AuthController {
+    @Autowired
+    AuthenticationManager authenticationManager;
 
-    private final AuthService authService;
-    public AuthController(AuthService authService) { this.authService = authService; }
+    @Autowired
+    UserRepository userRepository;
 
-    @PostMapping("/login")
-    public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) {
+    @Autowired
+    RoleRepository roleRepository;
 
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-        System.out.println(loginRequest.getUsername() + " " + loginRequest.getPassword());
-        if (loginRequest.getUsername() == null ||
-                loginRequest.getPassword() == null ||
-                loginRequest.getUsername().isEmpty() ||
-                loginRequest.getPassword().isEmpty()) {
+    @Autowired
+    PasswordEncoder encoder;
 
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
+    @Autowired
+    JwtUtils jwtUtils;
 
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
+    @PostMapping("/signin")
+    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
+    public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
 
-        return this.authService.login(loginRequest);
-    }
+        Authentication authentication = authenticationManager
+                .authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
 
-    @PostMapping("/isAuthenticated")
-    public ResponseEntity<String> isAuthenticated(@RequestBody AuthCheck authCheck) {
+        SecurityContextHolder.getContext().setAuthentication(authentication);
 
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
+        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();;
 
-        if (authCheck.getUsername() == null ||
-                authCheck.getUsername().isEmpty() ||
-                authCheck.getToken() == null ||
-                authCheck.getToken().isEmpty()) {
+        ResponseCookie cookie = jwtUtils.generateJwtCookie(userDetails);
 
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
+        List<String> roles = userDetails.getAuthorities().stream()
+                .map(GrantedAuthority::getAuthority)
+                .collect(Collectors.toList());
 
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        return this.authService.isAuthenticated(authCheck);
+        System.out.println("User logged");
+        return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString())
+                .body(new UserResponse(userDetails.getId(),
+                        userDetails.getUsername(),
+                        userDetails.getEmail(),
+                        roles));
     }
 
-    @PostMapping("/logout")
-    public ResponseEntity<String> logout(@RequestBody LogoutRequest logoutRequest) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (logoutRequest.getUsername() == null ||
-                logoutRequest.getToken() == null ||
-                logoutRequest.getUsername().isEmpty() ||
-                logoutRequest.getToken().isEmpty()) {
-
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
+    @PostMapping("/signup")
+    public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
+        if (userRepository.existsByUsername(signUpRequest.getUsername())) {
+            return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!"));
         }
 
-        return this.authService.logout(logoutRequest);
-    }
-
-    @PostMapping("/register")
-    public ResponseEntity<String> register(@RequestBody User user) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (user.getUsername() == null ||
-                user.getUsername().isEmpty() ||
-                user.getPassword() == null ||
-                user.getPassword().isEmpty() ||
-                user.getEmail() == null ||
-                user.getEmail().isEmpty()) {
-
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
+        if (userRepository.existsByEmail(signUpRequest.getEmail())) {
+            return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!"));
         }
 
-        return this.authService.register(user);
-    }
-
-    @PostMapping("/forgotPassword")
-    public ResponseEntity<String> forgotPassword(@RequestBody User user) {
+        User user = new User(signUpRequest.getUsername(),
+                signUpRequest.getEmail(),
+                encoder.encode(signUpRequest.getPassword()));
+
+        Set<String> strRoles = signUpRequest.getRole();
+        Set<Role> roles = new HashSet<>();
+
+        if (strRoles == null) {
+            Role userRole = roleRepository.findByName(ERole.ROLE_USER)
+                    .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
+            roles.add(userRole);
+        } else {
+            strRoles.forEach(role -> {
+                switch (role) {
+                    case "admin":
+                        Role adminRole = roleRepository.findByName(ERole.ROLE_ADMIN)
+                                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
+                        roles.add(adminRole);
+
+                        break;
+                    case "mod":
+                        Role modRole = roleRepository.findByName(ERole.ROLE_MODERATOR)
+                                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
+                        roles.add(modRole);
+
+                        break;
+                    default:
+                        Role userRole = roleRepository.findByName(ERole.ROLE_USER)
+                                .orElseThrow(() -> new RuntimeException("Error: Role is not found."));
+                        roles.add(userRole);
+                }
+            });
+        }
 
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
+        user.setRoles(roles);
+        userRepository.save(user);
 
-        if (user.getUsername() == null ||
-                user.getUsername().isEmpty() ||
-                user.getEmail() == null ||
-                user.getEmail().isEmpty()) {
+        return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
+    }
 
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
+    @PostMapping("/signout")
+    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
+    public ResponseEntity<?> logoutUser() {
+        ResponseCookie cookie = jwtUtils.getCleanJwtCookie();
+        return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString())
+                .body(new MessageResponse("You've been signed out!"));
+    }
 
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
+    @PostMapping("/validate")
+    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
+    public ResponseEntity<?> validateUser(HttpServletRequest request) {
+        String token = jwtUtils.getJwtFromCookies(request);
+
+        if (token != null) {
+            if (jwtUtils.validateJwtToken(token)) {
+                System.out.println("Valid token!");
+                return ResponseEntity.ok(new MessageResponse("Valid token!"));
+            } else {
+                return ResponseEntity.badRequest().body(new MessageResponse("Error: Invalid token!"));
+            }
+        } else {
+            return ResponseEntity.badRequest().body(new MessageResponse("Error: Missing token in cookie!"));
         }
-
-        return this.authService.forgotPassword(user);
     }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java
index 0d1e1f3..b37e6d1 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java
@@ -1,13 +1,16 @@
 package com.kasv.gunda.bootcamp.controllers;
 
 import com.google.gson.Gson;
-import com.kasv.gunda.bootcamp.entities.AuthCheck;
-import com.kasv.gunda.bootcamp.entities.LogoutRequest;
-import com.kasv.gunda.bootcamp.entities.Student;
+import com.kasv.gunda.bootcamp.models.Student;
+import com.kasv.gunda.bootcamp.payload.response.MessageResponse;
 import com.kasv.gunda.bootcamp.services.StudentService;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
+import com.kasv.gunda.bootcamp.security.jwt.JwtUtils;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -17,37 +20,32 @@ import java.util.Map;
 public class StudentController {
     private final StudentService studentService;
 
+    @Autowired
+    JwtUtils jwtUtils;
+
     public StudentController(StudentService studentService) {
         this.studentService = studentService;
     }
 
-    @PostMapping("/students")
-    public String getAllStudents(@RequestBody(required = false) AuthCheck authCheck) {
-
-        return studentService.getAllStudents(authCheck);
-
+    /* Returns all students,
+     while admin can view all students,
+     students can only view their first name
+     and first letter of their last name */
+    @GetMapping("/students")
+    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
+    public String getAllStudents(HttpServletRequest request) {
+//        studentService.getAllStudents(request);
+        System.out.println(request.getHeader("Authorization"));
+        System.out.println(Arrays.toString(request.getCookies()));
+        return "All students";
     }
 
     @PostMapping("/student/{id}")
-    public ResponseEntity<String> getStudentById(@PathVariable Long id, @RequestBody AuthCheck authCheck) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (authCheck.getUsername() == null ||
-                authCheck.getToken() == null ||
-                authCheck.getUsername().isEmpty() ||
-                authCheck.getToken().isEmpty() ||
-                id == null) {
-
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        return studentService.getStudentById(id, authCheck);
+    public ResponseEntity<String> getStudentById(@PathVariable Long id) {
+        return null;
     }
 
+    /* Registers a new student */
     @PostMapping("/student/register")
     public ResponseEntity<String> registerStudent(@RequestBody Student student) {
 
@@ -64,20 +62,19 @@ public class StudentController {
         return studentService.registerStudent(student);
     }
 
+    /* Updates student data */
     @PutMapping("/student/update/{id}")
-    public ResponseEntity<String> updateLastName(@PathVariable Long id, @RequestBody LogoutRequest details) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if(details.getLastName() == null || details.getLastName().isEmpty() || id == null
-                || details.getToken() == null || details.getToken().isEmpty()) {
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        return studentService.updateLastName(id, details);
+    public ResponseEntity<String> updateStudent( @PathVariable Long id, @RequestBody Student student) {
+        return null;
     }
 
+    /* Returns the count of all students */
+    @GetMapping("/students/count")
+    public ResponseEntity<String> getStudentsCount() {
 
+        Gson gson = new Gson();
+        Map<String, Integer> jsonResponse = new HashMap<>();
+        jsonResponse.put("count", studentService.getStudentsCount());
+        return ResponseEntity.ok(gson.toJson(jsonResponse));
+    }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/TestController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/TestController.java
new file mode 100644
index 0000000..cad1353
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/TestController.java
@@ -0,0 +1,35 @@
+package com.kasv.gunda.bootcamp.controllers;
+
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@CrossOrigin(origins = "*", maxAge = 3600)
+@RestController
+@RequestMapping("/api/test")
+public class TestController {
+    @GetMapping("/all")
+    public String allAccess() {
+        return "Public Content.";
+    }
+
+    @GetMapping("/user")
+    @PreAuthorize("hasRole('USER') or hasRole('MODERATOR') or hasRole('ADMIN')")
+    public String userAccess() {
+        return "User Content.";
+    }
+
+    @GetMapping("/mod")
+    @PreAuthorize("hasRole('MODERATOR')")
+    public String moderatorAccess() {
+        return "Moderator Board.";
+    }
+
+    @GetMapping("/admin")
+    @PreAuthorize("hasRole('ADMIN')")
+    public String adminAccess() {
+        return "Admin Board.";
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/UserController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/UserController.java
deleted file mode 100644
index 1bbb0ad..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/UserController.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package com.kasv.gunda.bootcamp.controllers;
-
-import com.google.gson.Gson;
-import com.kasv.gunda.bootcamp.entities.UpdatePassword;
-
-import com.kasv.gunda.bootcamp.repositories.UserRepository;
-import com.kasv.gunda.bootcamp.services.UserService;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.HashMap;
-import java.util.Map;
-
-
-@CrossOrigin
-@RestController
-//@RequestMapping("/api/users")
-public class UserController {
-    private final UserRepository userRepository;
-    private final UserService userService;
-
-    public UserController(UserRepository userRepository, UserService userService) {
-        this.userRepository = userRepository;
-        this.userService = userService;
-    }
-
-    @GetMapping("/exist/{username}")
-    public ResponseEntity<String> byUsername(@PathVariable String username) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (userRepository.existsByUsername(username)) {
-            jsonResponse.put("message", "User with username " + username + " exists");
-            return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-        } else {
-            jsonResponse.put("message", "User with username " + username + " does not exist");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-        }
-    }
-
-    @PutMapping("/admin/password")
-    public ResponseEntity<String> changePassword(@RequestBody UpdatePassword request) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (request.getToken() == null || request.getToken().isEmpty()
-                || request.getNewPassword() == null || request.getNewPassword().isEmpty()
-                || request.getOldPassword() == null || request.getOldPassword().isEmpty()
-                || request.getUsername() == null || request.getUsername().isEmpty()) {
-
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        return userService.changePassword(request);
-    }
-
-    @GetMapping("/info")
-    public ResponseEntity<String> getAllUsersAndStudents() {
-        return userService.getAllUsersAndStudents();
-    }
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/AuthCheck.java b/src/main/java/com/kasv/gunda/bootcamp/entities/AuthCheck.java
deleted file mode 100644
index 74864ba..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/AuthCheck.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.kasv.gunda.bootcamp.entities;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class AuthCheck {
-
-    private String token;
-    private String username;
-
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/LogoutRequest.java b/src/main/java/com/kasv/gunda/bootcamp/entities/LogoutRequest.java
deleted file mode 100644
index 85d17ec..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/LogoutRequest.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.kasv.gunda.bootcamp.entities;
-
-import lombok.Getter;
-import lombok.Setter;
-
-import java.util.Collection;
-
-@Getter
-@Setter
-public class LogoutRequest {
-
-    private String token;
-    private String username;
-    private String lastName;
-
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/UpdatePassword.java b/src/main/java/com/kasv/gunda/bootcamp/entities/UpdatePassword.java
deleted file mode 100644
index 8e083f6..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/UpdatePassword.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.kasv.gunda.bootcamp.entities;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class UpdatePassword {
-
-    private String token;
-    private String username;
-    private String newPassword;
-    private String oldPassword;
-
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/UpdateStudent.java b/src/main/java/com/kasv/gunda/bootcamp/entities/UpdateStudent.java
deleted file mode 100644
index 4f44fb0..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/UpdateStudent.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.kasv.gunda.bootcamp.entities;
-
-public class UpdateStudent {
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/UpdateUser.java b/src/main/java/com/kasv/gunda/bootcamp/entities/UpdateUser.java
deleted file mode 100644
index 55878b9..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/UpdateUser.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.kasv.gunda.bootcamp.entities;
-
-import lombok.Getter;
-import lombok.Setter;
-
-@Getter
-@Setter
-public class UpdateUser {
-
-    String token;
-    String email;
-    String username;
-
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/User.java b/src/main/java/com/kasv/gunda/bootcamp/entities/User.java
deleted file mode 100644
index bf74486..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/User.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package com.kasv.gunda.bootcamp.entities;
-
-import jakarta.persistence.*;
-import lombok.*;
-
-@Entity
-@Builder
-@AllArgsConstructor
-@NoArgsConstructor
-@Setter
-@Getter
-@Table(name = "users")
-public class User {
-
-    @GeneratedValue(strategy = GenerationType.IDENTITY)
-    @Id
-    @Column(name = "id")
-    private int id;
-
-    @Column(name = "username")
-    private String username;
-
-    @Column(name = "password")
-    private String password;
-
-    @Column(name = "email")
-    private String email;
-
-    // ToString method
-    @Override
-    public String toString() {
-        return "User{" +
-                "id=" + id +
-                ", username='" + username + '\'' +
-                ", email='" + email + '\'' +
-                '}';
-    }
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java b/src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java
new file mode 100644
index 0000000..274bae5
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java
@@ -0,0 +1,8 @@
+package com.kasv.gunda.bootcamp.exceptions;
+
+public class BlacklistedJwtException extends RuntimeException {
+
+    public BlacklistedJwtException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java b/src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java
new file mode 100644
index 0000000..57dc924
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java
@@ -0,0 +1,8 @@
+package com.kasv.gunda.bootcamp.exceptions;
+
+public class JwtTokenException extends RuntimeException {
+
+    public JwtTokenException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/ERole.java b/src/main/java/com/kasv/gunda/bootcamp/models/ERole.java
new file mode 100644
index 0000000..9373df4
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/ERole.java
@@ -0,0 +1,7 @@
+package com.kasv.gunda.bootcamp.models;
+
+public enum ERole {
+    ROLE_USER,
+    ROLE_MODERATOR,
+    ROLE_ADMIN
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/Role.java b/src/main/java/com/kasv/gunda/bootcamp/models/Role.java
new file mode 100644
index 0000000..76a449b
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/Role.java
@@ -0,0 +1,39 @@
+package com.kasv.gunda.bootcamp.models;
+
+import jakarta.persistence.*;
+
+@Entity
+@Table(name = "roles")
+public class Role {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Integer id;
+
+    @Enumerated(EnumType.STRING)
+    @Column(length = 20)
+    private ERole name;
+
+    public Role() {
+
+    }
+
+    public Role(ERole name) {
+        this.name = name;
+    }
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public ERole getName() {
+        return name;
+    }
+
+    public void setName(ERole name) {
+        this.name = name;
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/Student.java b/src/main/java/com/kasv/gunda/bootcamp/models/Student.java
similarity index 92%
rename from src/main/java/com/kasv/gunda/bootcamp/entities/Student.java
rename to src/main/java/com/kasv/gunda/bootcamp/models/Student.java
index 27d1ab7..dd6c6cb 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/Student.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/Student.java
@@ -1,4 +1,4 @@
-package com.kasv.gunda.bootcamp.entities;
+package com.kasv.gunda.bootcamp.models;
 
 import jakarta.persistence.Entity;
 import jakarta.persistence.GeneratedValue;
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/User.java b/src/main/java/com/kasv/gunda/bootcamp/models/User.java
new file mode 100644
index 0000000..06eb89e
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/User.java
@@ -0,0 +1,53 @@
+package com.kasv.gunda.bootcamp.models;
+
+import jakarta.persistence.*;
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@Entity
+@Getter
+@Setter
+@Table(name = "users",
+        uniqueConstraints = {
+                @UniqueConstraint(columnNames = "username"),
+                @UniqueConstraint(columnNames = "email")
+        })
+public class User {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @NotBlank
+    @Size(max = 20)
+    private String username;
+
+    @NotBlank
+    @Size(max = 50)
+    @Email
+    private String email;
+
+    @NotBlank
+    @Size(max = 120)
+    private String password;
+
+    @ManyToMany(fetch = FetchType.LAZY)
+    @JoinTable(name = "user_roles",
+            joinColumns = @JoinColumn(name = "user_id"),
+            inverseJoinColumns = @JoinColumn(name = "role_id"))
+    private Set<Role> roles = new HashSet<>();
+
+    public User() {
+    }
+
+    public User(String username, String email, String password) {
+        this.username = username;
+        this.email = email;
+        this.password = password;
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/entities/LoginRequest.java b/src/main/java/com/kasv/gunda/bootcamp/payload/request/LoginRequest.java
similarity index 50%
rename from src/main/java/com/kasv/gunda/bootcamp/entities/LoginRequest.java
rename to src/main/java/com/kasv/gunda/bootcamp/payload/request/LoginRequest.java
index 69c9f53..e948529 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/entities/LoginRequest.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/request/LoginRequest.java
@@ -1,15 +1,16 @@
-package com.kasv.gunda.bootcamp.entities;
+package com.kasv.gunda.bootcamp.payload.request;
 
-import lombok.AllArgsConstructor;
+import jakarta.validation.constraints.NotBlank;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 
-@Getter
 @Setter
-@AllArgsConstructor
-@NoArgsConstructor
+@Getter
 public class LoginRequest {
+    @NotBlank
     private String username;
+
+    @NotBlank
     private String password;
+
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/request/SignupRequest.java b/src/main/java/com/kasv/gunda/bootcamp/payload/request/SignupRequest.java
new file mode 100644
index 0000000..0e8c2e6
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/request/SignupRequest.java
@@ -0,0 +1,29 @@
+package com.kasv.gunda.bootcamp.payload.request;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Set;
+
+@Setter
+@Getter
+public class SignupRequest {
+    @NotBlank
+    @Size(min = 3, max = 20)
+    private String username;
+
+    @NotBlank
+    @Size(max = 50)
+    @Email
+    private String email;
+
+    private Set<String> role;
+
+    @NotBlank
+    @Size(min = 6, max = 40)
+    private String password;
+
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java b/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java
new file mode 100644
index 0000000..f6ff002
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java
@@ -0,0 +1,17 @@
+package com.kasv.gunda.bootcamp.payload.response;
+
+public class MessageResponse {
+    private String message;
+
+    public MessageResponse(String message) {
+        this.message = message;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java b/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java
new file mode 100644
index 0000000..4132972
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java
@@ -0,0 +1,45 @@
+package com.kasv.gunda.bootcamp.payload.response;
+
+import java.util.List;
+
+public class UserResponse {
+    private Long id;
+    private String username;
+    private String email;
+    private List<String> roles;
+
+    public UserResponse(Long id, String username, String email, List<String> roles) {
+        this.id = id;
+        this.username = username;
+        this.email = email;
+        this.roles = roles;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    public void setEmail(String email) {
+        this.email = email;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public List<String> getRoles() {
+        return roles;
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/RoleRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/RoleRepository.java
new file mode 100644
index 0000000..0ff898b
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/RoleRepository.java
@@ -0,0 +1,13 @@
+package com.kasv.gunda.bootcamp.repositories;
+
+import com.kasv.gunda.bootcamp.models.ERole;
+import com.kasv.gunda.bootcamp.models.Role;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface RoleRepository extends JpaRepository<Role, Long> {
+    Optional<Role> findByName(ERole name);
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRepository.java
index af26fe4..ca1af92 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRepository.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRepository.java
@@ -1,7 +1,7 @@
 package com.kasv.gunda.bootcamp.repositories;
 
 
-import com.kasv.gunda.bootcamp.entities.Student;
+import com.kasv.gunda.bootcamp.models.Student;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.stereotype.Repository;
 
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
index 978ac6d..3033044 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
@@ -1,20 +1,19 @@
 package com.kasv.gunda.bootcamp.repositories;
 
-import com.kasv.gunda.bootcamp.entities.User;
+import com.kasv.gunda.bootcamp.models.ERole;
+import com.kasv.gunda.bootcamp.models.User;
 import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
-import org.springframework.data.repository.query.Param;
 import org.springframework.stereotype.Repository;
 
-@Repository
-public interface UserRepository extends JpaRepository<User, String> {
+import java.util.Optional;
 
-    User findByUsername(String username);
+@Repository
+public interface UserRepository extends JpaRepository<User, Long> {
+    Optional<User> findByUsername(String username);
 
     Boolean existsByUsername(String username);
 
-    @Query("SELECT u.id FROM User u WHERE u.username = :username")
-    Integer findIdByUsername(@Param("username") String username);
+    Boolean existsByEmail(String email);
 
-    boolean existsByEmail(String email);
+    ERole findRoleByUsername(String username);
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java b/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
new file mode 100644
index 0000000..5c83c89
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
@@ -0,0 +1,79 @@
+package com.kasv.gunda.bootcamp.security;
+
+import com.kasv.gunda.bootcamp.security.jwt.AuthEntryPointJwt;
+import com.kasv.gunda.bootcamp.security.jwt.AuthTokenFilter;
+import com.kasv.gunda.bootcamp.security.services.UserDetailsServiceImpl;
+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.dao.DaoAuthenticationProvider;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+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.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+
+import java.util.Arrays;
+
+@Configuration
+@EnableMethodSecurity(prePostEnabled = true)
+public class WebSecurityConfig {
+    @Autowired
+    UserDetailsServiceImpl userDetailsService;
+
+    @Autowired
+    private AuthEntryPointJwt unauthorizedHandler;
+
+    @Bean
+    public AuthTokenFilter authenticationJwtTokenFilter() {
+        return new AuthTokenFilter();
+    }
+
+    @Bean
+    public DaoAuthenticationProvider authenticationProvider() {
+        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+
+        authProvider.setUserDetailsService(userDetailsService);
+        authProvider.setPasswordEncoder(passwordEncoder());
+
+        return authProvider;
+    }
+
+    @Bean
+    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
+        return authConfig.getAuthenticationManager();
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        return new BCryptPasswordEncoder();
+    }
+
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+        http.csrf(AbstractHttpConfigurer::disable)
+                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
+                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                .authorizeHttpRequests(auth ->
+                        auth.requestMatchers("/api/auth/**").permitAll()
+                                .requestMatchers("/api/test/all").permitAll()
+                                .requestMatchers("/api/students/count").permitAll()
+                                .anyRequest().authenticated()
+                );
+
+        http.authenticationProvider(authenticationProvider());
+
+        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+
+        return http.build();
+    }
+
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java
new file mode 100644
index 0000000..85cbcc5
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java
@@ -0,0 +1,25 @@
+package com.kasv.gunda.bootcamp.security.jwt;
+
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+
+@Component
+public class AuthEntryPointJwt implements AuthenticationEntryPoint {
+
+    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
+
+    @Override
+    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
+            throws IOException, ServletException {
+        logger.error("Unauthorized error: {}", authException.getMessage());
+        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java
new file mode 100644
index 0000000..d165a18
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java
@@ -0,0 +1,58 @@
+package com.kasv.gunda.bootcamp.security.jwt;
+
+import com.kasv.gunda.bootcamp.security.services.UserDetailsServiceImpl;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+public class AuthTokenFilter extends OncePerRequestFilter {
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    @Autowired
+    private UserDetailsServiceImpl userDetailsService;
+
+    private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);
+
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
+            throws ServletException, IOException {
+        try {
+            String jwt = parseJwt(request);
+            if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
+                String username = jwtUtils.getUserNameFromJwtToken(jwt);
+
+                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+
+                UsernamePasswordAuthenticationToken authentication =
+                        new UsernamePasswordAuthenticationToken(userDetails,
+                                null,
+                                userDetails.getAuthorities());
+
+                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+            }
+        }  catch (Exception e) {
+            logger.error("Cannot set user authentication: {}", e);
+        }
+
+        filterChain.doFilter(request, response);
+    }
+
+    private String parseJwt(HttpServletRequest request) {
+        String jwt = jwtUtils.getJwtFromCookies(request);
+        return jwt;
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
new file mode 100644
index 0000000..3f63397
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
@@ -0,0 +1,86 @@
+package com.kasv.gunda.bootcamp.security.jwt;
+
+import com.kasv.gunda.bootcamp.security.services.UserDetailsImpl;
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.ResponseCookie;
+import org.springframework.stereotype.Component;
+import org.springframework.web.util.WebUtils;
+
+import java.security.Key;
+import java.util.Date;
+
+@Component
+public class JwtUtils {
+    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
+
+    @Value("${gunda.bootcamp.app.jwtSecret}")
+    private String jwtSecret;
+
+    @Value("${gunda.bootcamp.app.jwtExpirationMs}")
+    private int jwtExpirationMs;
+
+    @Value("${gunda.bootcamp.app.jwtCookieName}")
+    private String jwtCookie;
+
+    public String getJwtFromCookies(HttpServletRequest request) {
+        Cookie cookie = WebUtils.getCookie(request, jwtCookie);
+        if (cookie != null) {
+            return cookie.getValue();
+        } else {
+            return null;
+        }
+    }
+
+    public ResponseCookie generateJwtCookie(UserDetailsImpl userPrincipal) {
+        String jwt = generateTokenFromUsername(userPrincipal.getUsername());
+        ResponseCookie cookie = ResponseCookie.from(jwtCookie, jwt).path("/api").maxAge(24 * 60 * 60).httpOnly(true).sameSite("None").secure(true).build();
+        return cookie;
+    }
+
+    public ResponseCookie getCleanJwtCookie() {
+        ResponseCookie cookie = ResponseCookie.from(jwtCookie, null).path("/api").build();
+        return cookie;
+    }
+
+    public String getUserNameFromJwtToken(String token) {
+        return Jwts.parserBuilder().setSigningKey(key()).build()
+                .parseClaimsJws(token).getBody().getSubject();
+    }
+
+    private Key key() {
+        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
+    }
+
+    public boolean validateJwtToken(String authToken) {
+        try {
+            Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken);
+            return true;
+        } catch (MalformedJwtException e) {
+            logger.error("Invalid JWT token: {}", e.getMessage());
+        } catch (ExpiredJwtException e) {
+            logger.error("JWT token is expired: {}", e.getMessage());
+        } catch (UnsupportedJwtException e) {
+            logger.error("JWT token is unsupported: {}", e.getMessage());
+        } catch (IllegalArgumentException e) {
+            logger.error("JWT claims string is empty: {}", e.getMessage());
+        }
+
+        return false;
+    }
+
+    public String generateTokenFromUsername(String username) {
+        return Jwts.builder()
+                .setSubject(username)
+                .setIssuedAt(new Date())
+                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
+                .signWith(key(), SignatureAlgorithm.HS256)
+                .compact();
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java b/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java
new file mode 100644
index 0000000..5efcfd3
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java
@@ -0,0 +1,102 @@
+package com.kasv.gunda.bootcamp.security.services;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.kasv.gunda.bootcamp.models.User;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class UserDetailsImpl implements UserDetails {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    private String username;
+
+    private String email;
+
+    @JsonIgnore
+    private String password;
+
+    private Collection<? extends GrantedAuthority> authorities;
+
+    public UserDetailsImpl(Long id, String username, String email, String password,
+                           Collection<? extends GrantedAuthority> authorities) {
+        this.id = id;
+        this.username = username;
+        this.email = email;
+        this.password = password;
+        this.authorities = authorities;
+    }
+
+    public static UserDetailsImpl build(User user) {
+        List<GrantedAuthority> authorities = user.getRoles().stream()
+                .map(role -> new SimpleGrantedAuthority(role.getName().name()))
+                .collect(Collectors.toList());
+
+        return new UserDetailsImpl(
+                user.getId(),
+                user.getUsername(),
+                user.getEmail(),
+                user.getPassword(),
+                authorities);
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return authorities;
+    }
+
+    public Long getId() {
+        return id;
+    }
+
+    public String getEmail() {
+        return email;
+    }
+
+    @Override
+    public String getPassword() {
+        return password;
+    }
+
+    @Override
+    public String getUsername() {
+        return username;
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+        UserDetailsImpl user = (UserDetailsImpl) o;
+        return Objects.equals(id, user.id);
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsServiceImpl.java b/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsServiceImpl.java
new file mode 100644
index 0000000..0968a03
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsServiceImpl.java
@@ -0,0 +1,26 @@
+package com.kasv.gunda.bootcamp.security.services;
+
+import com.kasv.gunda.bootcamp.models.User;
+import com.kasv.gunda.bootcamp.repositories.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;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+public class UserDetailsServiceImpl implements UserDetailsService {
+    @Autowired
+    UserRepository userRepository;
+
+    @Override
+    @Transactional
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        User user = userRepository.findByUsername(username)
+                .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));
+
+        return UserDetailsImpl.build(user);
+    }
+
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/services/AuthService.java b/src/main/java/com/kasv/gunda/bootcamp/services/AuthService.java
deleted file mode 100644
index 3b3d8f6..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/services/AuthService.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package com.kasv.gunda.bootcamp.services;
-
-import com.google.gson.Gson;
-import com.kasv.gunda.bootcamp.entities.AuthCheck;
-import com.kasv.gunda.bootcamp.entities.LoginRequest;
-import com.kasv.gunda.bootcamp.entities.LogoutRequest;
-import com.kasv.gunda.bootcamp.entities.User;
-import com.kasv.gunda.bootcamp.utilities.*;
-import com.kasv.gunda.bootcamp.repositories.UserRepository;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@Service
-public class AuthService {
-
-    private final UserRepository userRepository;
-    private final EmailFunctions emailFunctions;
-    private TokenFunctions tokenFunctions = TokenFunctions.getInstance();
-    private UserTimeoutFunctions userTimeoutFunctions = UserTimeoutFunctions.getInstance();
-
-    public AuthService(UserRepository userRepository, EmailFunctions emailFunctions) {
-        this.userRepository = userRepository;
-        this.emailFunctions = emailFunctions;
-    }
-
-    public ResponseEntity<String> login(LoginRequest loginRequest) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        User userFromDb = userRepository.findByUsername(loginRequest.getUsername());
-
-        if (userFromDb != null) {
-            if (userFromDb.getPassword().equals(loginRequest.getPassword()) && userTimeoutFunctions.isUserOnTimeout(userFromDb.getId()) == false){
-
-                String token = TokenGenerator.generateToken();
-                tokenFunctions.storeToken((long) userFromDb.getId(), token);
-
-                jsonResponse.put("token", token);
-                jsonResponse.put("username", userFromDb.getUsername());
-
-                userTimeoutFunctions.resetBadPasswordsCount(userFromDb.getId());
-                return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-
-            } else {
-                if (userTimeoutFunctions.isUserOnTimeout(userFromDb.getId())) {
-                    jsonResponse.put("error", "Your account is locked. Please try again in 5 seconds.");
-                    return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-                }
-                if (userTimeoutFunctions.getBadPasswordsCount(userFromDb.getId()) < 3) {
-                    userTimeoutFunctions.incrementCount((long) userFromDb.getId());
-                }
-                if (userTimeoutFunctions.getBadPasswordsCount(userFromDb.getId()) >= 3 ) {
-                    userTimeoutFunctions.setUserTimeout( userFromDb.getId());
-                    jsonResponse.put("error", "Your account has been locked. Please try again in 5 seconds.");
-                    return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-                }
-                jsonResponse.put(
-                        "error",
-                        "Invalid credentials. Please provide valid credentials. " +
-                        userTimeoutFunctions.getBadPasswordsCount(userFromDb.getId()) +
-                        "/3 failed attempts."
-                );
-                return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-            }
-        } else {
-            jsonResponse.put("error", "User with username " + loginRequest.getUsername() + " does not exist. Please provide valid credentials.");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-        }
-    }
-
-    public ResponseEntity<String> logout(LogoutRequest logoutRequest) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        long userId = (long) userRepository.findIdByUsername(logoutRequest.getUsername());
-
-        if (tokenFunctions.isTokenExists(userId)) {
-            if (tokenFunctions.isTokenValid(logoutRequest.getToken())) {
-                tokenFunctions.removeToken(userId);
-                jsonResponse.put("message", "User logged out successfully");
-                return ResponseEntity.status(200).body("{}");
-            } else {
-                jsonResponse.put("error", "Invalid token. Please provide valid token.");
-                return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-            }
-        } else {
-            jsonResponse.put("error", "User  " + logoutRequest.getUsername() + " is not logged in.");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-        }
-    }
-
-    public ResponseEntity<String> forgotPassword(User user) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        User userFromDb = userRepository.findByUsername(user.getUsername());
-
-        if (userFromDb != null) {
-            if (userFromDb.getEmail().equals(user.getEmail())) {
-                String newPassword = RandomPasswordGenerator.generateCode();
-                userFromDb.setPassword(newPassword);
-                userRepository.save(userFromDb);
-                emailFunctions.sendNewPassword(userFromDb.getEmail(), newPassword);
-                jsonResponse.put("message", "New password has been sent to your email.");
-                return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-            } else {
-                jsonResponse.put("error", "Invalid email. Please provide valid email.");
-                return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-            }
-        } else {
-            jsonResponse.put("error", "User with username " + user.getUsername() + " does not exist.");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-        }
-    }
-
-    public ResponseEntity<String> isAuthenticated(AuthCheck authCheck) {
-
-            Gson gson = new Gson();
-            Map<String, String> jsonResponse = new HashMap<>();
-
-            long userId = (long) userRepository.findIdByUsername(authCheck.getUsername());
-
-            if (tokenFunctions.isTokenExists(userId)) {
-                if (tokenFunctions.isTokenValid(authCheck.getToken())) {
-                    jsonResponse.put("message", "User is authenticated.");
-                    return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-                } else {
-                    jsonResponse.put("error", "Invalid token. Please provide valid token.");
-                    return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-                }
-            } else {
-                jsonResponse.put("error", "User is not authenticated.");
-                return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-            }
-    }
-
-    public ResponseEntity<String> register(User user) {
-
-            Gson gson = new Gson();
-            Map<String, String> jsonResponse = new HashMap<>();
-
-            if (userRepository.existsByUsername(user.getUsername())) {
-                jsonResponse.put("error", "User with username " + user.getUsername() + " already exists.");
-                return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-            }
-
-            if (userRepository.existsByEmail(user.getEmail())) {
-                jsonResponse.put("error", "User with email " + user.getEmail() + " already exists.");
-                return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-            }
-
-            userRepository.save(user);
-            jsonResponse.put("message", "User registered successfully.");
-            return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-    }
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java b/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java
index 5cbd01a..79769c3 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java
@@ -1,15 +1,13 @@
 package com.kasv.gunda.bootcamp.services;
 
 import com.google.gson.Gson;
-import com.kasv.gunda.bootcamp.entities.AuthCheck;
-import com.kasv.gunda.bootcamp.entities.LogoutRequest;
-import com.kasv.gunda.bootcamp.entities.Student;
+import com.kasv.gunda.bootcamp.models.Student;
 import com.kasv.gunda.bootcamp.repositories.StudentRepository;
-import com.kasv.gunda.bootcamp.repositories.UserRepository;
-import com.kasv.gunda.bootcamp.utilities.TokenFunctions;
+import com.kasv.gunda.bootcamp.security.jwt.JwtUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Service;
-import org.springframework.web.bind.annotation.CrossOrigin;
 
 import java.util.HashMap;
 import java.util.List;
@@ -18,65 +16,73 @@ import java.util.Map;
 @Service
 public class StudentService {
 
-    private TokenFunctions tokenFunctions = TokenFunctions.getInstance();
+    private final JwtUtils jwtUtils;
     private final StudentRepository studentRepository;
-    private final UserRepository userRepository;
 
-    public StudentService(StudentRepository studentRepository, UserRepository userRepository) {
-        this.studentRepository = studentRepository;
-        this.userRepository = userRepository;
-    }
-
-    public String getAllStudents(AuthCheck authCheck) {
-
-        Gson gson = new Gson();
-
-        List<Student> students = studentRepository.findAll();
-
-
-        if (authCheck.getToken() != null || !authCheck.getToken().isEmpty() ||
-                authCheck.getUsername() != null || !authCheck.getUsername().isEmpty()) {
-
-            if (!tokenFunctions.isTokenValid(authCheck.getToken())) {
 
-                for (Student student : students) {
-                    student.setDob(null);
-                    student.setLastName(student.getLastName().substring(0, 1));
-                }
 
-            }
-        } else {
-            for (Student student : students) {
-                student.setDob(null);
-                student.setLastName(student.getLastName().substring(0, 1));
-            }
-        }
-        return gson.toJson(students);
+    public StudentService(JwtUtils jwtUtils, StudentRepository studentRepository) {
+        this.jwtUtils = jwtUtils;
+        this.studentRepository = studentRepository;
     }
 
-    public ResponseEntity<String> getStudentById(Long id, AuthCheck authCheck) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        long userId = (long) userRepository.findIdByUsername(authCheck.getUsername());
-
-        if (tokenFunctions.isTokenExists(userId)) {
-            if (!tokenFunctions.isTokenValid(authCheck.getToken())) {
-                jsonResponse.put("error", "Invalid token. Please provide valid token.");
-                return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-            }
-        } else {
-            jsonResponse.put("error", "Invalid token. Please provide valid token.");
-            return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-        }
-
-        if (!studentRepository.existsById(id)) {
-            jsonResponse.put("error", "Student with id " + id + " not found.");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-        }
-
-        return ResponseEntity.status(200).body(gson.toJson(studentRepository.findById(id)));
+//    public String getAllStudents(HttpServletRequest request) {
+//
+//        String token = jwtUtils.getJwtFromCookies(request);
+//        String username = jwtUtils.getUserNameFromJwtToken(token);
+//
+//
+//
+//
+//        List<Student> students = studentRepository.findAll();
+//
+//
+//        if () {
+//
+//            if (!tokenFunctions.isTokenValid(authCheck.getToken())) {
+//
+//                for (Student student : students) {
+//                    student.setDob(null);
+//                    student.setLastName(student.getLastName().substring(0, 1));
+//                }
+//
+//            }
+//        } else {
+//            for (Student student : students) {
+//                student.setDob(null);
+//                student.setLastName(student.getLastName().substring(0, 1));
+//            }
+//        }
+//        return gson.toJson(students);
+//    }
+
+//    public ResponseEntity<String> getStudentById(Long id, AuthCheck authCheck) {
+//
+//        Gson gson = new Gson();
+//        Map<String, String> jsonResponse = new HashMap<>();
+//
+//        long userId = (long) userRepository.findIdByUsername(authCheck.getUsername());
+//
+//        if (tokenFunctions.isTokenExists(userId)) {
+//            if (!tokenFunctions.isTokenValid(authCheck.getToken())) {
+//                jsonResponse.put("error", "Invalid token. Please provide valid token.");
+//                return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
+//            }
+//        } else {
+//            jsonResponse.put("error", "Invalid token. Please provide valid token.");
+//            return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
+//        }
+//
+//        if (!studentRepository.existsById(id)) {
+//            jsonResponse.put("error", "Student with id " + id + " not found.");
+//            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
+//        }
+//
+//        return ResponseEntity.status(200).body(gson.toJson(studentRepository.findById(id)));
+//    }
+
+    public int getStudentsCount() {
+        return (int) studentRepository.count();
     }
 
     public ResponseEntity<String> registerStudent(Student student) {
@@ -107,26 +113,26 @@ public class StudentService {
     }
 
 
-    public ResponseEntity<String> updateLastName(Long id, LogoutRequest details) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (!tokenFunctions.isTokenValid(details.getToken())) {
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        if (!studentRepository.existsById(id)) {
-            jsonResponse.put("error", "Student with id " + id + " not found.");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-        }
-
-        Student student = studentRepository.findById(id);
-        student.setLastName(details.getLastName());
-
-        studentRepository.save(student);
-
-        return ResponseEntity.status(200).body("{}");
-    }
+//    public ResponseEntity<String> updateLastName(Long id, LogoutRequest details) {
+//
+//        Gson gson = new Gson();
+//        Map<String, String> jsonResponse = new HashMap<>();
+//
+//        if (!tokenFunctions.isTokenValid(details.getToken())) {
+//            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
+//            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
+//        }
+//
+//        if (!studentRepository.existsById(id)) {
+//            jsonResponse.put("error", "Student with id " + id + " not found.");
+//            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
+//        }
+//
+//        Student student = studentRepository.findById(id);
+//        student.setLastName(details.getLastName());
+//
+//        studentRepository.save(student);
+//
+//        return ResponseEntity.status(200).body("{}");
+//    }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/services/UserService.java b/src/main/java/com/kasv/gunda/bootcamp/services/UserService.java
deleted file mode 100644
index cef5310..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/services/UserService.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.kasv.gunda.bootcamp.services;
-
-import com.google.gson.Gson;
-import com.kasv.gunda.bootcamp.controllers.AuthController;
-import com.kasv.gunda.bootcamp.entities.LogoutRequest;
-import com.kasv.gunda.bootcamp.entities.UpdatePassword;
-import com.kasv.gunda.bootcamp.entities.User;
-import com.kasv.gunda.bootcamp.repositories.StudentRepository;
-import com.kasv.gunda.bootcamp.repositories.UserRepository;
-import com.kasv.gunda.bootcamp.utilities.TokenFunctions;
-import org.springframework.http.ResponseEntity;
-import org.springframework.stereotype.Service;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@Service
-public class UserService {
-    private final UserRepository userRepository;
-
-    private final StudentRepository studentRepository;
-    private final AuthController authController;
-
-    private TokenFunctions tokenFunctions = TokenFunctions.getInstance();
-
-    public UserService(UserRepository userRepository, StudentRepository studentRepository, AuthController authController) {
-        this.userRepository = userRepository;
-        this.studentRepository = studentRepository;
-        this.authController = authController;
-    }
-
-    public ResponseEntity<String> changePassword(UpdatePassword request) {
-
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (!userRepository.existsByUsername(request.getUsername())) {
-            jsonResponse.put("error", "User with username " + request.getUsername() + " does not exist.");
-            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-
-        }
-
-        if (!tokenFunctions.isTokenValid(request.getToken())) {
-            jsonResponse.put("error", "Invalid token. Please provide a valid token.");
-            return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-        }
-
-        User userFromDb = userRepository.findByUsername(request.getUsername());
-
-        if (userFromDb.getPassword().equals(request.getOldPassword())) {
-            if (request.getOldPassword().equals(request.getNewPassword())) {
-                jsonResponse.put("error", "New password cannot be the same as the old password.");
-                return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-
-            }
-            userFromDb.setPassword(request.getNewPassword());
-            userRepository.save(userFromDb);
-
-            LogoutRequest logoutRequest = new LogoutRequest();
-            logoutRequest.setToken(request.getToken());
-            logoutRequest.setUsername(request.getUsername());
-            authController.logout(logoutRequest);
-
-        } else {
-            jsonResponse.put("error", "Invalid old password. Please provide a valid old password.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        jsonResponse.put("message", "Password changed successfully.");
-        return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-
-    }
-
-    public ResponseEntity<String> getAllUsersAndStudents() {
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        jsonResponse.put("users", gson.toJson(userRepository.findAll().size()));
-        jsonResponse.put("students", gson.toJson(studentRepository.findAll().size()));
-
-        return ResponseEntity.status(200).body(gson.toJson(jsonResponse));
-    }
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/utilities/RandomPasswordGenerator.java b/src/main/java/com/kasv/gunda/bootcamp/utilities/RandomPasswordGenerator.java
deleted file mode 100644
index 3d2976e..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/utilities/RandomPasswordGenerator.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.kasv.gunda.bootcamp.utilities;
-
-public class RandomPasswordGenerator {
-    public static String generateCode() {
-        String randomChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-        StringBuilder code = new StringBuilder();
-        for (int i = 0; i < 10; i++) {
-            int randomIndex = (int) (randomChars.length() * Math.random());
-            code.append(randomChars.charAt(randomIndex));
-        }
-        return code.toString();
-    }
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/utilities/TokenFunctions.java b/src/main/java/com/kasv/gunda/bootcamp/utilities/TokenFunctions.java
deleted file mode 100644
index 4d17ac9..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/utilities/TokenFunctions.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.kasv.gunda.bootcamp.utilities;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class TokenFunctions {
-
-    private final Map<Long, String> userTokens;
-    private static TokenFunctions instance;
-
-    private TokenFunctions() {
-        userTokens = new ConcurrentHashMap<>();
-    }
-
-    public static synchronized TokenFunctions getInstance() {
-        if (instance == null) {
-            instance = new TokenFunctions();
-        }
-        return instance;
-    }
-
-    public void storeToken(Long userId, String token) {
-        userTokens.put(userId, token);
-        System.out.println(userTokens);
-    }
-
-    public boolean isTokenValid(String token) {
-        return userTokens.containsValue(token);
-    }
-
-    public void removeToken(Long userId) {
-        userTokens.remove(userId);
-        System.out.println(userTokens);
-    }
-
-    public String getToken(Long userId) {
-        return userTokens.get(userId);
-    }
-
-    public boolean isTokenExists(Long userId) {
-        return userTokens.containsKey(userId);
-    }
-
-    public void clearTokens() {
-        userTokens.clear();
-    }
-
-    public long getUserId(String token) {
-        for (Map.Entry<Long, String> entry : userTokens.entrySet()) {
-            if (entry.getValue().equals(token)) {
-                return entry.getKey();
-            }
-        }
-        return -1;
-    }
-
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/utilities/TokenGenerator.java b/src/main/java/com/kasv/gunda/bootcamp/utilities/TokenGenerator.java
deleted file mode 100644
index 3b34f1b..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/utilities/TokenGenerator.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.kasv.gunda.bootcamp.utilities;
-
-import java.security.SecureRandom;
-
-
-public class TokenGenerator {
-
-    private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-
-    private static final int TOKEN_LENGTH = 40;
-
-    public static String generateToken() {
-        SecureRandom random = new SecureRandom();
-        StringBuilder token = new StringBuilder(TOKEN_LENGTH);
-
-        for (int i = 0; i < TOKEN_LENGTH; i++) {
-            token.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
-        }
-
-        return token.toString();
-    }
-
-
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/utilities/UserTimeoutFunctions.java b/src/main/java/com/kasv/gunda/bootcamp/utilities/UserTimeoutFunctions.java
deleted file mode 100644
index f6bf01a..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/utilities/UserTimeoutFunctions.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.kasv.gunda.bootcamp.utilities;
-
-import org.springframework.stereotype.Component;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-@Component
-public class UserTimeoutFunctions {
-
-    private final Map<Long, Integer> badPasswordsCount;
-    private final Map<Long, Long> timeoutMap;
-    private static UserTimeoutFunctions instance;
-
-    private UserTimeoutFunctions() {
-        badPasswordsCount = new ConcurrentHashMap<>();
-        timeoutMap = new ConcurrentHashMap<>();
-    }
-
-    public static synchronized UserTimeoutFunctions getInstance() {
-        if (instance == null) {
-            instance = new UserTimeoutFunctions();
-        }
-        return instance;
-    }
-
-    public void incrementCount(Long userId) {
-        badPasswordsCount.put(userId, badPasswordsCount.getOrDefault(userId, 0) + 1);
-        System.out.println(badPasswordsCount);
-    }
-
-    public int getBadPasswordsCount(long id) {
-        return Integer.parseInt(badPasswordsCount.getOrDefault(id, 0).toString());
-    }
-
-    public void resetBadPasswordsCount(long id) {
-        badPasswordsCount.put(id, 0);
-        System.out.println(badPasswordsCount);
-    }
-
-    public void setUserTimeout(long id) {
-        long start = System.currentTimeMillis();
-        long end = start + 5 * 1000;
-        timeoutMap.put(id, end);
-        resetBadPasswordsCount(id);
-    }
-
-    public boolean isUserOnTimeout(long id) {
-        if (timeoutMap.containsKey(id)) {
-            long currentTime = System.currentTimeMillis();
-            if (currentTime < timeoutMap.get(id)) {
-                return true;
-            } else {
-                timeoutMap.remove(id);
-            }
-        }
-        return false;
-    }
-}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index a561132..c23118f 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -3,7 +3,13 @@ spring.application.name=Bootcamp
 spring.datasource.url=jdbc:mariadb://localhost:3307/bootcamp
 spring.datasource.username=root
 spring.datasource.password=
-spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
+
+spring.jpa.hibernate.ddl-auto=update
+
+# App Properties
+gunda.bootcamp.app.jwtCookieName= Authorization
+gunda.bootcamp.app.jwtSecret= 15c8374b1a7b790f6888693fa0d2710cfa46bfa2b67b5a80c7aedf4109853c7c
+gunda.bootcamp.app.jwtExpirationMs= 20000
 
 spring.mail.host=smtp.gmail.com
 spring.mail.port=587
diff --git a/src/test/java/com/kasv/gunda/bootcamp/BootcampApplicationTests.java b/src/test/java/com/kasv/gunda/bootcamp/BootcampApplicationTests.java
index f913b6e..ba3819c 100644
--- a/src/test/java/com/kasv/gunda/bootcamp/BootcampApplicationTests.java
+++ b/src/test/java/com/kasv/gunda/bootcamp/BootcampApplicationTests.java
@@ -1,9 +1,4 @@
 package com.kasv.gunda.bootcamp;
 
-
-import org.springframework.boot.test.context.SpringBootTest;
-
-@SpringBootTest
 class BootcampApplicationTests {
-
 }
diff --git a/src/test/java/com/kasv/gunda/bootcamp/repositories/StudentRepositoryTests.java b/src/test/java/com/kasv/gunda/bootcamp/repositories/StudentRepositoryTests.java
deleted file mode 100644
index 16b81f7..0000000
--- a/src/test/java/com/kasv/gunda/bootcamp/repositories/StudentRepositoryTests.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package com.kasv.gunda.bootcamp.repositories;
-
-import com.kasv.gunda.bootcamp.entities.Student;
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-
-import java.util.Date;
-import java.util.List;
-
-@DataJpaTest
-@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
-public class StudentRepositoryTests {
-
-    @Autowired
-    private StudentRepository studentRepository;
-
-    @Test
-    public void StudentRepository_SaveAll_ReturnSavedStudents() {
-
-        // Arrange
-        Student student = Student.builder()
-                .firstName("Branči")
-                .lastName("Kováč")
-                .dob(new Date()).build();
-
-        // Act
-        Student savedStudent = studentRepository.save(student);
-
-        // Assert
-        Assertions.assertThat(savedStudent).isNotNull();
-        Assertions.assertThat(savedStudent.getId()).isGreaterThan(0);
-    }
-
-    @Test
-    public void findAll_ReturnsAllStudents() {
-        // Arrange
-        Student student1 = Student.builder().firstName("John").lastName("Doe").dob(new Date()).build();
-        Student student2 = Student.builder().firstName("Jane").lastName("Doe").dob(new Date()).build();
-        studentRepository.save(student1);
-        studentRepository.save(student2);
-
-        // Act
-        List<Student> students = studentRepository.findAll();
-
-        // Assert
-        Assertions.assertThat(students).hasSize(2);
-    }
-
-    @Test
-    public void existsById_ReturnsTrue_WhenStudentExists() {
-        // Arrange
-        Student student = Student.builder().firstName("John").lastName("Doe").dob(new Date()).build();
-        Student savedStudent = studentRepository.save(student);
-
-        // Act
-        boolean exists = studentRepository.existsById(savedStudent.getId());
-
-        // Assert
-        Assertions.assertThat(exists).isTrue();
-    }
-
-    @Test
-    public void existsById_ReturnsFalse_WhenStudentDoesNotExist() {
-        // Act
-        boolean exists = studentRepository.existsById(999L); // Assuming this ID does not exist
-
-        // Assert
-        Assertions.assertThat(exists).isFalse();
-    }
-
-    @Test
-    public void findById_ReturnsStudent_WhenStudentExists() {
-        // Arrange
-        Student student = Student.builder().firstName("John").lastName("Doe").dob(new Date()).build();
-        Student savedStudent = studentRepository.save(student);
-
-        // Act
-        Student foundStudent = studentRepository.findById(savedStudent.getId());
-
-        // Assert
-        Assertions.assertThat(foundStudent).isEqualTo(savedStudent);
-    }
-
-    @Test
-    public void findById_ReturnsNull_WhenStudentDoesNotExist() {
-        // Act
-        Student foundStudent = studentRepository.findById(999L); // Assuming this ID does not exist
-
-        // Assert
-        Assertions.assertThat(foundStudent).isNull();
-    }
-}
diff --git a/src/test/java/com/kasv/gunda/bootcamp/repositories/UserRepositoryTests.java b/src/test/java/com/kasv/gunda/bootcamp/repositories/UserRepositoryTests.java
deleted file mode 100644
index e65497f..0000000
--- a/src/test/java/com/kasv/gunda/bootcamp/repositories/UserRepositoryTests.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.kasv.gunda.bootcamp.repositories;
-
-import com.kasv.gunda.bootcamp.entities.User;
-import org.assertj.core.api.Assertions;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
-import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
-import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
-
-@DataJpaTest
-@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
-public class UserRepositoryTests {
-
-    @Autowired
-    private UserRepository userRepository;
-
-    @Test
-    public void UserRepository_SaveAll_ReturnSavedUsers() {
-
-        // Arrange
-        User user = User.builder()
-                .username("Braňo")
-                .email("mail@test.com")
-                .password("password")
-                .build();
-
-        // Act
-        User savedUser = userRepository.save(user);
-
-        // Assert
-        Assertions.assertThat(savedUser).isNotNull();
-        Assertions.assertThat(savedUser.getId()).isGreaterThan(0);
-    }
-
-    @Test
-    public void UserRepository_ExistsByUsername_ReturnsTrueIfUserExists() {
-        // Arrange
-        User user = User.builder()
-                .username("Braňo")
-                .email("mail@test.com")
-                .password("password")
-                .build();
-        userRepository.save(user);
-
-        // Act
-        Boolean exists = userRepository.existsByUsername(user.getUsername());
-
-        // Assert
-        Assertions.assertThat(exists).isTrue();
-    }
-
-    @Test
-    public void UserRepository_FindByUsername_ReturnsUserIfExists() {
-        // Arrange
-        User user = User.builder()
-                .username("Braňo")
-                .email("mail@test.com")
-                .password("password")
-                .build();
-        userRepository.save(user);
-
-        // Act
-        User foundUser = userRepository.findByUsername(user.getUsername());
-
-        // Assert
-        Assertions.assertThat(foundUser).isNotNull();
-        Assertions.assertThat(foundUser.getUsername()).isEqualTo(user.getUsername());
-    }
-
-    @Test
-    public void UserRepository_FindIdByUsername_ReturnsUserIdIfExists() {
-        // Arrange
-        User user = User.builder()
-                .username("Braňo")
-                .email("mail@test.com")
-                .password("password")
-                .build();
-        User savedUser = userRepository.save(user);
-
-        // Act
-        Integer foundId = userRepository.findIdByUsername(user.getUsername());
-
-        // Assert
-        Assertions.assertThat(foundId).isNotNull();
-        Assertions.assertThat(foundId).isEqualTo(savedUser.getId());
-    }
-
-}
diff --git a/src/test/java/com/kasv/gunda/bootcamp/services/AuthServicesTests.java b/src/test/java/com/kasv/gunda/bootcamp/services/AuthServicesTests.java
deleted file mode 100644
index f0f952b..0000000
--- a/src/test/java/com/kasv/gunda/bootcamp/services/AuthServicesTests.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.kasv.gunda.bootcamp.services;
-
-public class AuthServicesTests {
-
-}
-- 
GitLab


From d9e7bbfc85966a007b145d32cf53a8675489d02f Mon Sep 17 00:00:00 2001
From: Samuel Gunda <samuel.gunda@kosickaakademia.sk>
Date: Thu, 9 May 2024 12:38:21 +0200
Subject: [PATCH 2/4] refresh token addition

---
 .../bootcamp/controllers/AuthController.java  | 76 +++++++++----------
 .../exceptions/BlacklistedJwtException.java   |  8 --
 .../exceptions/JwtTokenException.java         |  8 --
 .../exceptions/TokenExceptionHandler.java     | 25 ++++++
 .../exceptions/TokenRefreshException.java     | 14 ++++
 .../gunda/bootcamp/models/RefreshToken.java   | 27 +++++++
 .../payload/request/TokenRefreshRequest.java  | 12 +++
 .../payload/response/JwtResponse.java         | 45 +++++++++++
 .../payload/response/MessageResponse.java     | 12 ++-
 .../response/TokenRefreshResponse.java        | 18 +++++
 .../payload/response/UserResponse.java        | 32 ++------
 .../repositories/RefreshTokenRepository.java  | 18 +++++
 .../bootcamp/repositories/UserRepository.java |  2 -
 .../bootcamp/security/WebSecurityConfig.java  | 32 +++++++-
 .../security/jwt/AuthEntryPointJwt.java       | 22 +++++-
 .../security/jwt/AuthTokenFilter.java         | 22 +++---
 .../gunda/bootcamp/security/jwt/JwtUtils.java | 55 +++++---------
 .../services/RefreshTokenService.java         | 55 ++++++++++++++
 .../security/services/UserDetailsImpl.java    | 13 ++--
 .../bootcamp/utilities/ErrorMessage.java      | 32 ++++++++
 src/main/resources/application.properties     |  6 +-
 21 files changed, 381 insertions(+), 153 deletions(-)
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java
 delete mode 100644 src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenExceptionHandler.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenRefreshException.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/models/RefreshToken.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/request/TokenRefreshRequest.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/response/JwtResponse.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/response/TokenRefreshResponse.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/utilities/ErrorMessage.java

diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
index 3b142f7..c5bb30e 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
@@ -1,26 +1,27 @@
 package com.kasv.gunda.bootcamp.controllers;
 
+import com.kasv.gunda.bootcamp.exceptions.TokenRefreshException;
 import com.kasv.gunda.bootcamp.models.ERole;
+import com.kasv.gunda.bootcamp.models.RefreshToken;
 import com.kasv.gunda.bootcamp.models.Role;
 import com.kasv.gunda.bootcamp.models.User;
 import com.kasv.gunda.bootcamp.payload.request.LoginRequest;
 import com.kasv.gunda.bootcamp.payload.request.SignupRequest;
+import com.kasv.gunda.bootcamp.payload.request.TokenRefreshRequest;
+import com.kasv.gunda.bootcamp.payload.response.JwtResponse;
 import com.kasv.gunda.bootcamp.payload.response.MessageResponse;
-import com.kasv.gunda.bootcamp.payload.response.UserResponse;
+import com.kasv.gunda.bootcamp.payload.response.TokenRefreshResponse;
 import com.kasv.gunda.bootcamp.repositories.RoleRepository;
 import com.kasv.gunda.bootcamp.repositories.UserRepository;
 import com.kasv.gunda.bootcamp.security.jwt.JwtUtils;
 import com.kasv.gunda.bootcamp.security.services.UserDetailsImpl;
-import jakarta.servlet.http.HttpServletRequest;
+import com.kasv.gunda.bootcamp.security.services.RefreshTokenService;
 import jakarta.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.ResponseCookie;
 import org.springframework.http.ResponseEntity;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
@@ -49,8 +50,10 @@ public class AuthController {
     @Autowired
     JwtUtils jwtUtils;
 
+    @Autowired
+    RefreshTokenService refreshTokenService;
+
     @PostMapping("/signin")
-    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
     public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
 
         Authentication authentication = authenticationManager
@@ -58,20 +61,13 @@ public class AuthController {
 
         SecurityContextHolder.getContext().setAuthentication(authentication);
 
-        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();;
+        UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
 
-        ResponseCookie cookie = jwtUtils.generateJwtCookie(userDetails);
+        String jwt = jwtUtils.generateJwtToken(userDetails);
 
-        List<String> roles = userDetails.getAuthorities().stream()
-                .map(GrantedAuthority::getAuthority)
-                .collect(Collectors.toList());
+        RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());
 
-        System.out.println("User logged");
-        return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString())
-                .body(new UserResponse(userDetails.getId(),
-                        userDetails.getUsername(),
-                        userDetails.getEmail(),
-                        roles));
+        return ResponseEntity.ok(new JwtResponse(jwt, refreshToken.getToken()));
     }
 
     @PostMapping("/signup")
@@ -84,8 +80,8 @@ public class AuthController {
             return ResponseEntity.badRequest().body(new MessageResponse("Error: Email is already in use!"));
         }
 
-        User user = new User(signUpRequest.getUsername(),
-                signUpRequest.getEmail(),
+        // Create new user's account
+        User user = new User(signUpRequest.getUsername(), signUpRequest.getEmail(),
                 encoder.encode(signUpRequest.getPassword()));
 
         Set<String> strRoles = signUpRequest.getRole();
@@ -124,28 +120,28 @@ public class AuthController {
         return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
     }
 
-    @PostMapping("/signout")
-    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
-    public ResponseEntity<?> logoutUser() {
-        ResponseCookie cookie = jwtUtils.getCleanJwtCookie();
-        return ResponseEntity.ok().header(HttpHeaders.SET_COOKIE, cookie.toString())
-                .body(new MessageResponse("You've been signed out!"));
+    @PostMapping("/refreshtoken")
+    public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) {
+        String requestRefreshToken = request.getRefreshToken();
+
+        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+
+        return refreshTokenService.findByToken(requestRefreshToken)
+                .map(refreshTokenService::verifyExpiration)
+                .map(RefreshToken::getUser)
+                .map(user -> {
+                    String token = jwtUtils.generateJwtToken(userDetails);
+                    return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
+                })
+                .orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
+                        "Refresh token is not in database!"));
     }
 
-    @PostMapping("/validate")
-    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
-    public ResponseEntity<?> validateUser(HttpServletRequest request) {
-        String token = jwtUtils.getJwtFromCookies(request);
-
-        if (token != null) {
-            if (jwtUtils.validateJwtToken(token)) {
-                System.out.println("Valid token!");
-                return ResponseEntity.ok(new MessageResponse("Valid token!"));
-            } else {
-                return ResponseEntity.badRequest().body(new MessageResponse("Error: Invalid token!"));
-            }
-        } else {
-            return ResponseEntity.badRequest().body(new MessageResponse("Error: Missing token in cookie!"));
-        }
+    @PostMapping("/signout")
+    public ResponseEntity<?> logoutUser() {
+        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+        Long userId = userDetails.getId();
+        refreshTokenService.deleteByUserId(userId);
+        return ResponseEntity.ok(new MessageResponse("Log out successful!"));
     }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java b/src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java
deleted file mode 100644
index 274bae5..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/exceptions/BlacklistedJwtException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.kasv.gunda.bootcamp.exceptions;
-
-public class BlacklistedJwtException extends RuntimeException {
-
-    public BlacklistedJwtException(String message) {
-        super(message);
-    }
-}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java b/src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java
deleted file mode 100644
index 57dc924..0000000
--- a/src/main/java/com/kasv/gunda/bootcamp/exceptions/JwtTokenException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.kasv.gunda.bootcamp.exceptions;
-
-public class JwtTokenException extends RuntimeException {
-
-    public JwtTokenException(String message) {
-        super(message);
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenExceptionHandler.java b/src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenExceptionHandler.java
new file mode 100644
index 0000000..908aab7
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenExceptionHandler.java
@@ -0,0 +1,25 @@
+package com.kasv.gunda.bootcamp.exceptions;
+
+import java.util.Date;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+import org.springframework.web.context.request.WebRequest;
+
+import com.kasv.gunda.bootcamp.utilities.ErrorMessage;
+
+@RestControllerAdvice
+public class TokenExceptionHandler {
+
+    @ExceptionHandler(value = TokenRefreshException.class)
+    @ResponseStatus(HttpStatus.FORBIDDEN)
+    public ErrorMessage handleTokenRefreshException(TokenRefreshException ex, WebRequest request) {
+        return new ErrorMessage(
+                HttpStatus.FORBIDDEN.value(),
+                new Date(),
+                ex.getMessage(),
+                request.getDescription(false));
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenRefreshException.java b/src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenRefreshException.java
new file mode 100644
index 0000000..7ebab0d
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/exceptions/TokenRefreshException.java
@@ -0,0 +1,14 @@
+package com.kasv.gunda.bootcamp.exceptions;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.ResponseStatus;
+
+@ResponseStatus(HttpStatus.FORBIDDEN)
+public class TokenRefreshException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public TokenRefreshException(String token, String message) {
+        super(String.format("Failed for [%s]: %s", token, message));
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/RefreshToken.java b/src/main/java/com/kasv/gunda/bootcamp/models/RefreshToken.java
new file mode 100644
index 0000000..8d5d906
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/RefreshToken.java
@@ -0,0 +1,27 @@
+package com.kasv.gunda.bootcamp.models;
+
+import java.time.Instant;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+@Entity(name = "refresh_token")
+@Getter
+@Setter
+public class RefreshToken {
+    @Id
+    @GeneratedValue(strategy = GenerationType.AUTO)
+    private int id;
+
+    @OneToOne
+    @JoinColumn(name = "user_id", referencedColumnName = "id")
+    private User user;
+
+    @Column(nullable = false, unique = true)
+    private String token;
+
+    @Column(nullable = false)
+    private Instant expiryDate;
+
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/request/TokenRefreshRequest.java b/src/main/java/com/kasv/gunda/bootcamp/payload/request/TokenRefreshRequest.java
new file mode 100644
index 0000000..105500f
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/request/TokenRefreshRequest.java
@@ -0,0 +1,12 @@
+package com.kasv.gunda.bootcamp.payload.request;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class TokenRefreshRequest {
+    @NotBlank
+    private String refreshToken;
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/response/JwtResponse.java b/src/main/java/com/kasv/gunda/bootcamp/payload/response/JwtResponse.java
new file mode 100644
index 0000000..c24c6a0
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/response/JwtResponse.java
@@ -0,0 +1,45 @@
+package com.kasv.gunda.bootcamp.payload.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.List;
+
+public class JwtResponse {
+    private String token;
+    private String type = "Bearer";
+    private String refreshToken;
+    private Long id;
+    private String username;
+    private String email;
+    private List<String> roles;
+
+    public JwtResponse(String accessToken, String refreshToken) {
+        this.token = accessToken;
+        this.refreshToken = refreshToken;
+    }
+
+    public String getAccessToken() {
+        return token;
+    }
+
+    public void setAccessToken(String accessToken) {
+        this.token = accessToken;
+    }
+
+    public String getTokenType() {
+        return type;
+    }
+
+    public void setTokenType(String tokenType) {
+        this.type = tokenType;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public void setRefreshToken(String refreshToken) {
+        this.refreshToken = refreshToken;
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java b/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java
index f6ff002..c05cde8 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/response/MessageResponse.java
@@ -1,5 +1,10 @@
 package com.kasv.gunda.bootcamp.payload.response;
 
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
 public class MessageResponse {
     private String message;
 
@@ -7,11 +12,4 @@ public class MessageResponse {
         this.message = message;
     }
 
-    public String getMessage() {
-        return message;
-    }
-
-    public void setMessage(String message) {
-        this.message = message;
-    }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/response/TokenRefreshResponse.java b/src/main/java/com/kasv/gunda/bootcamp/payload/response/TokenRefreshResponse.java
new file mode 100644
index 0000000..92e1f57
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/response/TokenRefreshResponse.java
@@ -0,0 +1,18 @@
+package com.kasv.gunda.bootcamp.payload.response;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Setter
+@Getter
+public class TokenRefreshResponse {
+    private String accessToken;
+    private String refreshToken;
+    private String tokenType = "Bearer";
+
+    public TokenRefreshResponse(String accessToken, String refreshToken) {
+        this.accessToken = accessToken;
+        this.refreshToken = refreshToken;
+    }
+
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java b/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java
index 4132972..c5c4864 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/response/UserResponse.java
@@ -1,7 +1,12 @@
 package com.kasv.gunda.bootcamp.payload.response;
 
+import lombok.Getter;
+import lombok.Setter;
+
 import java.util.List;
 
+@Getter
+@Setter
 public class UserResponse {
     private Long id;
     private String username;
@@ -15,31 +20,4 @@ public class UserResponse {
         this.roles = roles;
     }
 
-    public Long getId() {
-        return id;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public String getEmail() {
-        return email;
-    }
-
-    public void setEmail(String email) {
-        this.email = email;
-    }
-
-    public String getUsername() {
-        return username;
-    }
-
-    public void setUsername(String username) {
-        this.username = username;
-    }
-
-    public List<String> getRoles() {
-        return roles;
-    }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java
new file mode 100644
index 0000000..4153068
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java
@@ -0,0 +1,18 @@
+package com.kasv.gunda.bootcamp.repositories;
+
+import java.util.Optional;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Modifying;
+import org.springframework.stereotype.Repository;
+
+import com.kasv.gunda.bootcamp.models.RefreshToken;
+import  com.kasv.gunda.bootcamp.models.User;
+
+@Repository
+public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
+    Optional<RefreshToken> findByToken(String token);
+
+    @Modifying
+    int deleteByUser(User user);
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
index 3033044..eff9ccd 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
@@ -14,6 +14,4 @@ public interface UserRepository extends JpaRepository<User, Long> {
     Boolean existsByUsername(String username);
 
     Boolean existsByEmail(String email);
-
-    ERole findRoleByUsername(String username);
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java b/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
index 5c83c89..6157388 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
@@ -9,6 +9,7 @@ import org.springframework.context.annotation.Configuration;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
 import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
+import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 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.configurers.AbstractHttpConfigurer;
@@ -24,8 +25,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import java.util.Arrays;
 
 @Configuration
-@EnableMethodSecurity(prePostEnabled = true)
-public class WebSecurityConfig {
+@EnableMethodSecurity
+public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
     @Autowired
     UserDetailsServiceImpl userDetailsService;
 
@@ -37,6 +38,11 @@ public class WebSecurityConfig {
         return new AuthTokenFilter();
     }
 
+//	@Override
+//	public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
+//		authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
+//	}
+
     @Bean
     public DaoAuthenticationProvider authenticationProvider() {
         DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
@@ -47,6 +53,12 @@ public class WebSecurityConfig {
         return authProvider;
     }
 
+//	@Bean
+//	@Override
+//	public AuthenticationManager authenticationManagerBean() throws Exception {
+//		return super.authenticationManagerBean();
+//	}
+
     @Bean
     public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
         return authConfig.getAuthenticationManager();
@@ -57,14 +69,26 @@ public class WebSecurityConfig {
         return new BCryptPasswordEncoder();
     }
 
+//	@Override
+//	protected void configure(HttpSecurity http) throws Exception {
+//		http.cors().and().csrf().disable()
+//			.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
+//			.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
+//			.authorizeRequests().antMatchers("/api/auth/**").permitAll()
+//			.antMatchers("/api/test/**").permitAll()
+//			.anyRequest().authenticated();
+//
+//		http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
+//	}
+
     @Bean
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
-        http.csrf(AbstractHttpConfigurer::disable)
+        http.csrf(csrf -> csrf.disable())
                 .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                 .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                 .authorizeHttpRequests(auth ->
                         auth.requestMatchers("/api/auth/**").permitAll()
-                                .requestMatchers("/api/test/all").permitAll()
+                                .requestMatchers("/api/test/**").permitAll()
                                 .requestMatchers("/api/students/count").permitAll()
                                 .anyRequest().authenticated()
                 );
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java
index 85cbcc5..0eb65c4 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthEntryPointJwt.java
@@ -1,15 +1,20 @@
 package com.kasv.gunda.bootcamp.security.jwt;
 
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
 import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.http.MediaType;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.stereotype.Component;
 
-import java.io.IOException;
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 @Component
 public class AuthEntryPointJwt implements AuthenticationEntryPoint {
@@ -20,6 +25,19 @@ public class AuthEntryPointJwt implements AuthenticationEntryPoint {
     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
             throws IOException, ServletException {
         logger.error("Unauthorized error: {}", authException.getMessage());
-        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
+
+        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+
+        final Map<String, Object> body = new HashMap<>();
+        body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
+        body.put("error", "Unauthorized");
+        body.put("message", authException.getMessage());
+        body.put("path", request.getServletPath());
+
+        final ObjectMapper mapper = new ObjectMapper();
+        mapper.writeValue(response.getOutputStream(), body);
+
+//    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
     }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java
index d165a18..917d551 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/AuthTokenFilter.java
@@ -12,6 +12,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.util.StringUtils;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 import java.io.IOException;
@@ -34,25 +35,26 @@ public class AuthTokenFilter extends OncePerRequestFilter {
                 String username = jwtUtils.getUserNameFromJwtToken(jwt);
 
                 UserDetails userDetails = userDetailsService.loadUserByUsername(username);
-
-                UsernamePasswordAuthenticationToken authentication =
-                        new UsernamePasswordAuthenticationToken(userDetails,
-                                null,
-                                userDetails.getAuthorities());
-
+                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
+                        userDetails.getAuthorities());
                 authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 
                 SecurityContextHolder.getContext().setAuthentication(authentication);
             }
-        }  catch (Exception e) {
-            logger.error("Cannot set user authentication: {}", e);
+        } catch (Exception e) {
+            logger.error("Cannot set user authentication: {}", e.getMessage());
         }
 
         filterChain.doFilter(request, response);
     }
 
     private String parseJwt(HttpServletRequest request) {
-        String jwt = jwtUtils.getJwtFromCookies(request);
-        return jwt;
+        String headerAuth = request.getHeader("Authorization");
+
+        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
+            return headerAuth.substring(7, headerAuth.length());
+        }
+
+        return null;
     }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
index 3f63397..e380bae 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
@@ -1,19 +1,18 @@
 package com.kasv.gunda.bootcamp.security.jwt;
 
-import com.kasv.gunda.bootcamp.security.services.UserDetailsImpl;
-import io.jsonwebtoken.*;
-import io.jsonwebtoken.io.Decoders;
+
 import io.jsonwebtoken.security.Keys;
-import jakarta.servlet.http.Cookie;
-import jakarta.servlet.http.HttpServletRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.http.ResponseCookie;
 import org.springframework.stereotype.Component;
-import org.springframework.web.util.WebUtils;
+
+import io.jsonwebtoken.*;
+
+import com.kasv.gunda.bootcamp.security.services.UserDetailsImpl;
 
 import java.security.Key;
+import java.util.Base64;
 import java.util.Date;
 
 @Component
@@ -26,28 +25,16 @@ public class JwtUtils {
     @Value("${gunda.bootcamp.app.jwtExpirationMs}")
     private int jwtExpirationMs;
 
-    @Value("${gunda.bootcamp.app.jwtCookieName}")
-    private String jwtCookie;
-
-    public String getJwtFromCookies(HttpServletRequest request) {
-        Cookie cookie = WebUtils.getCookie(request, jwtCookie);
-        if (cookie != null) {
-            return cookie.getValue();
-        } else {
-            return null;
-        }
-    }
-
-    public ResponseCookie generateJwtCookie(UserDetailsImpl userPrincipal) {
-        String jwt = generateTokenFromUsername(userPrincipal.getUsername());
-        ResponseCookie cookie = ResponseCookie.from(jwtCookie, jwt).path("/api").maxAge(24 * 60 * 60).httpOnly(true).sameSite("None").secure(true).build();
-        return cookie;
+        public String generateJwtToken(UserDetailsImpl userPrincipal) {
+            return Jwts.builder().setSubject(userPrincipal.getUsername())
+                    .claim("id", userPrincipal.getId())
+                    .claim("email", userPrincipal.getEmail())
+                    .claim("roles", userPrincipal.getAuthorities())
+                    .setIssuedAt(new Date())
+                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs)).signWith(key(), SignatureAlgorithm.HS256)
+                .compact();
     }
 
-    public ResponseCookie getCleanJwtCookie() {
-        ResponseCookie cookie = ResponseCookie.from(jwtCookie, null).path("/api").build();
-        return cookie;
-    }
 
     public String getUserNameFromJwtToken(String token) {
         return Jwts.parserBuilder().setSigningKey(key()).build()
@@ -55,13 +42,16 @@ public class JwtUtils {
     }
 
     private Key key() {
-        return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
+        byte[] decodedSecret = Base64.getUrlDecoder().decode(jwtSecret);
+        return Keys.hmacShaKeyFor(decodedSecret);
     }
 
     public boolean validateJwtToken(String authToken) {
         try {
             Jwts.parserBuilder().setSigningKey(key()).build().parse(authToken);
             return true;
+        } catch (SignatureException e) {
+            logger.error("Invalid JWT signature: {}", e.getMessage());
         } catch (MalformedJwtException e) {
             logger.error("Invalid JWT token: {}", e.getMessage());
         } catch (ExpiredJwtException e) {
@@ -74,13 +64,4 @@ public class JwtUtils {
 
         return false;
     }
-
-    public String generateTokenFromUsername(String username) {
-        return Jwts.builder()
-                .setSubject(username)
-                .setIssuedAt(new Date())
-                .setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
-                .signWith(key(), SignatureAlgorithm.HS256)
-                .compact();
-    }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java b/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java
new file mode 100644
index 0000000..1ce54e8
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java
@@ -0,0 +1,55 @@
+package com.kasv.gunda.bootcamp.security.services;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.UUID;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import com.kasv.gunda.bootcamp.exceptions.TokenRefreshException;
+import com.kasv.gunda.bootcamp.models.RefreshToken;
+import com.kasv.gunda.bootcamp.repositories.RefreshTokenRepository;
+import com.kasv.gunda.bootcamp.repositories.UserRepository;
+
+@Service
+public class RefreshTokenService {
+    @Value("${gunda.bootcamp.app.jwtRefreshExpirationMs}")
+    private Long refreshTokenDurationMs;
+
+    @Autowired
+    private RefreshTokenRepository refreshTokenRepository;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    public Optional<RefreshToken> findByToken(String token) {
+        return refreshTokenRepository.findByToken(token);
+    }
+
+    public RefreshToken createRefreshToken(Long userId) {
+        RefreshToken refreshToken = new RefreshToken();
+
+        refreshToken.setUser(userRepository.findById(userId).get());
+        refreshToken.setExpiryDate(Instant.now().plusMillis(refreshTokenDurationMs));
+        refreshToken.setToken(UUID.randomUUID().toString());
+
+        refreshToken = refreshTokenRepository.save(refreshToken);
+        return refreshToken;
+    }
+
+    public RefreshToken verifyExpiration(RefreshToken token) {
+        if (token.getExpiryDate().compareTo(Instant.now()) < 0) {
+            refreshTokenRepository.delete(token);
+            throw new TokenRefreshException(token.getToken(), "Refresh token was expired. Please make a new signin request");
+        }
+
+        return token;
+    }
+
+    @Transactional
+    public int deleteByUserId(Long userId) {
+        return refreshTokenRepository.deleteByUser(userRepository.findById(userId).get());
+    }
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java b/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java
index 5efcfd3..3b161cd 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/services/UserDetailsImpl.java
@@ -1,16 +1,17 @@
 package com.kasv.gunda.bootcamp.security.services;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.kasv.gunda.bootcamp.models.User;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
 import java.util.Collection;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import com.kasv.gunda.bootcamp.models.User;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
 public class UserDetailsImpl implements UserDetails {
     private static final long serialVersionUID = 1L;
 
diff --git a/src/main/java/com/kasv/gunda/bootcamp/utilities/ErrorMessage.java b/src/main/java/com/kasv/gunda/bootcamp/utilities/ErrorMessage.java
new file mode 100644
index 0000000..9ee24b5
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/utilities/ErrorMessage.java
@@ -0,0 +1,32 @@
+package com.kasv.gunda.bootcamp.utilities;
+import java.util.Date;
+
+public class ErrorMessage {
+    private int statusCode;
+    private Date timestamp;
+    private String message;
+    private String description;
+
+    public ErrorMessage(int statusCode, Date timestamp, String message, String description) {
+        this.statusCode = statusCode;
+        this.timestamp = timestamp;
+        this.message = message;
+        this.description = description;
+    }
+
+    public int getStatusCode() {
+        return statusCode;
+    }
+
+    public Date getTimestamp() {
+        return timestamp;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+}
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index c23118f..36f4510 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -7,9 +7,11 @@ spring.datasource.password=
 spring.jpa.hibernate.ddl-auto=update
 
 # App Properties
-gunda.bootcamp.app.jwtCookieName= Authorization
+gunda.bootcamp.app.jwtCookieName= Jwt
+gunda.bootcamp.app.jwtRefreshCookieName= Jwt-Refresh
 gunda.bootcamp.app.jwtSecret= 15c8374b1a7b790f6888693fa0d2710cfa46bfa2b67b5a80c7aedf4109853c7c
-gunda.bootcamp.app.jwtExpirationMs= 20000
+gunda.bootcamp.app.jwtExpirationMs= 1200000
+gunda.bootcamp.app.jwtRefreshExpirationMs= 86400000 
 
 spring.mail.host=smtp.gmail.com
 spring.mail.port=587
-- 
GitLab


From e36f74716c7ab0531d0b1fdbb07f144d2de6edce Mon Sep 17 00:00:00 2001
From: Samuel Gunda <samuel.gunda@kosickaakademia.sk>
Date: Fri, 10 May 2024 02:33:06 +0200
Subject: [PATCH 3/4] refresh token function fix

---
 .../gunda/bootcamp/controllers/AuthController.java     | 10 ++++------
 src/main/resources/application.properties              |  4 +---
 2 files changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
index c5bb30e..e77cc84 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
@@ -27,9 +27,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 @CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
 @RestController
@@ -121,16 +119,15 @@ public class AuthController {
     }
 
     @PostMapping("/refreshtoken")
-    public ResponseEntity<?> refreshtoken(@Valid @RequestBody TokenRefreshRequest request) {
+    @CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
+    public ResponseEntity<?> refreshToken(@Valid @RequestBody TokenRefreshRequest request) {
         String requestRefreshToken = request.getRefreshToken();
 
-        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-
         return refreshTokenService.findByToken(requestRefreshToken)
                 .map(refreshTokenService::verifyExpiration)
                 .map(RefreshToken::getUser)
                 .map(user -> {
-                    String token = jwtUtils.generateJwtToken(userDetails);
+                    String token = jwtUtils.generateJwtToken(UserDetailsImpl.build(user));
                     return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
                 })
                 .orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
@@ -138,6 +135,7 @@ public class AuthController {
     }
 
     @PostMapping("/signout")
+    @CrossOrigin(origins = "http://localhost:3000" , maxAge = 3600)
     public ResponseEntity<?> logoutUser() {
         UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
         Long userId = userDetails.getId();
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 36f4510..c95eb14 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -7,10 +7,8 @@ spring.datasource.password=
 spring.jpa.hibernate.ddl-auto=update
 
 # App Properties
-gunda.bootcamp.app.jwtCookieName= Jwt
-gunda.bootcamp.app.jwtRefreshCookieName= Jwt-Refresh
 gunda.bootcamp.app.jwtSecret= 15c8374b1a7b790f6888693fa0d2710cfa46bfa2b67b5a80c7aedf4109853c7c
-gunda.bootcamp.app.jwtExpirationMs= 1200000
+gunda.bootcamp.app.jwtExpirationMs= 30000
 gunda.bootcamp.app.jwtRefreshExpirationMs= 86400000 
 
 spring.mail.host=smtp.gmail.com
-- 
GitLab


From 8b8ba65180687b8e460acf5fddd6037e0c665c2c Mon Sep 17 00:00:00 2001
From: Samuel Gunda <samuel.gunda@kosickaakademia.sk>
Date: Thu, 16 May 2024 02:17:02 +0200
Subject: [PATCH 4/4] finished auth, recovery of student service according to
 new authorization process, addition of student registration using approval
 system, api preparation for react front-end needs

---
 .../bootcamp/controllers/AuthController.java  |  13 +-
 .../controllers/StudentController.java        |  61 ++++--
 .../kasv/gunda/bootcamp/models/Status.java    |   5 +
 .../kasv/gunda/bootcamp/models/Student.java   |   9 +-
 .../bootcamp/models/StudentRegistration.java  |  38 ++++
 .../payload/request/StudentUpdateRequest.java |  14 ++
 .../repositories/RefreshTokenRepository.java  |   4 +
 .../StudentRegistrationRepository.java        |   9 +
 .../bootcamp/repositories/UserRepository.java |   2 +
 .../bootcamp/security/WebSecurityConfig.java  |  25 +--
 .../gunda/bootcamp/security/jwt/JwtUtils.java |   5 +
 .../services/RefreshTokenService.java         |   4 +-
 .../bootcamp/services/StudentService.java     | 200 +++++++++---------
 src/main/resources/application.properties     |   4 +-
 14 files changed, 234 insertions(+), 159 deletions(-)
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/models/Status.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/models/StudentRegistration.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/payload/request/StudentUpdateRequest.java
 create mode 100644 src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRegistrationRepository.java

diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
index e77cc84..522578a 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/AuthController.java
@@ -27,6 +27,7 @@ import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 @CrossOrigin(origins = "http://localhost:3000", maxAge = 3600)
@@ -137,9 +138,13 @@ public class AuthController {
     @PostMapping("/signout")
     @CrossOrigin(origins = "http://localhost:3000" , maxAge = 3600)
     public ResponseEntity<?> logoutUser() {
-        UserDetailsImpl userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
-        Long userId = userDetails.getId();
-        refreshTokenService.deleteByUserId(userId);
-        return ResponseEntity.ok(new MessageResponse("Log out successful!"));
+        Object principle = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
+
+        if (!Objects.equals(principle.toString(), "anonymousUser")) {
+            Long userId = ((UserDetailsImpl) principle).getId();
+            refreshTokenService.deleteByUserId(userId);
+        }
+
+        return ResponseEntity.ok().body(new MessageResponse("You've been signed out!"));
     }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java b/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java
index b37e6d1..b425ac5 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/controllers/StudentController.java
@@ -2,21 +2,28 @@ package com.kasv.gunda.bootcamp.controllers;
 
 import com.google.gson.Gson;
 import com.kasv.gunda.bootcamp.models.Student;
+import com.kasv.gunda.bootcamp.models.StudentRegistration;
+import com.kasv.gunda.bootcamp.payload.request.StudentUpdateRequest;
 import com.kasv.gunda.bootcamp.payload.response.MessageResponse;
 import com.kasv.gunda.bootcamp.services.StudentService;
 import jakarta.servlet.http.HttpServletRequest;
+import org.apache.coyote.Response;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.web.bind.annotation.*;
 import com.kasv.gunda.bootcamp.security.jwt.JwtUtils;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
 @RestController
 @RequestMapping("/api")
-@CrossOrigin
+@CrossOrigin(origins = "http://localhost:3000")
 public class StudentController {
     private final StudentService studentService;
 
@@ -32,40 +39,48 @@ public class StudentController {
      students can only view their first name
      and first letter of their last name */
     @GetMapping("/students")
-    @CrossOrigin(origins = "http://localhost:3000", allowCredentials = "true")
-    public String getAllStudents(HttpServletRequest request) {
-//        studentService.getAllStudents(request);
-        System.out.println(request.getHeader("Authorization"));
-        System.out.println(Arrays.toString(request.getCookies()));
-        return "All students";
+    @CrossOrigin(origins = "http://localhost:3000")
+    public ResponseEntity<String> getAllStudents() {
+
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
+
+        return ResponseEntity.ok(studentService.getAllStudents(authorities));
     }
 
-    @PostMapping("/student/{id}")
+    @GetMapping("/student/{id}")
+    @CrossOrigin(origins = "http://localhost:3000")
     public ResponseEntity<String> getStudentById(@PathVariable Long id) {
-        return null;
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
+
+        if (authorities.stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
+            return studentService.getStudentById(id);
+        } else {
+            return ResponseEntity.status(403).body("You are not authorized to view this resource.");
+        }
     }
 
     /* Registers a new student */
     @PostMapping("/student/register")
-    public ResponseEntity<String> registerStudent(@RequestBody Student student) {
+    @CrossOrigin(origins = "http://localhost:3000")
+    public ResponseEntity<String> registerStudent(@RequestBody StudentRegistration studentForm, HttpServletRequest request) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
+        String authorizationHeader = request.getHeader("Authorization");
 
-        Gson gson = new Gson();
-        Map<String, String> jsonResponse = new HashMap<>();
-
-        if (student.getFirstName() == null || student.getFirstName().isEmpty() ||
-                student.getLastName() == null || student.getLastName().isEmpty() ||
-                student.getDob() == null) {
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-        }
-
-        return studentService.registerStudent(student);
+        return studentService.registerStudent(studentForm, authorities, authorizationHeader);
     }
 
     /* Updates student data */
     @PutMapping("/student/update/{id}")
-    public ResponseEntity<String> updateStudent( @PathVariable Long id, @RequestBody Student student) {
-        return null;
+    @CrossOrigin(origins = "http://localhost:3000")
+    public ResponseEntity<String> updateStudent(@PathVariable Long id, @RequestBody StudentUpdateRequest updateRequest, HttpServletRequest request) {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
+        String authorizationHeader = request.getHeader("Authorization");
+
+        return studentService.updateStudent(id, updateRequest, authorities, authorizationHeader);
     }
 
     /* Returns the count of all students */
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/Status.java b/src/main/java/com/kasv/gunda/bootcamp/models/Status.java
new file mode 100644
index 0000000..41e95b7
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/Status.java
@@ -0,0 +1,5 @@
+package com.kasv.gunda.bootcamp.models;
+
+public enum Status {
+    PENDING, DECLINED, ACCEPTED
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/Student.java b/src/main/java/com/kasv/gunda/bootcamp/models/Student.java
index dd6c6cb..8d71f06 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/models/Student.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/Student.java
@@ -1,10 +1,6 @@
 package com.kasv.gunda.bootcamp.models;
 
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
 import lombok.*;
 
 import java.util.Date;
@@ -25,5 +21,8 @@ public class Student {
     private String lastName;
     private Date dob;
 
+    @OneToOne
+    @JoinColumn(name = "user_id")
+    private User user;
 
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/models/StudentRegistration.java b/src/main/java/com/kasv/gunda/bootcamp/models/StudentRegistration.java
new file mode 100644
index 0000000..00ba2af
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/models/StudentRegistration.java
@@ -0,0 +1,38 @@
+package com.kasv.gunda.bootcamp.models;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Entity
+@Getter
+@Setter
+@Table(name = "student_registrations")
+public class StudentRegistration {
+
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    private Long userId;
+
+    private String firstName;
+
+    private String lastName;
+
+    private Date dob;
+
+    @Enumerated(EnumType.STRING)
+    private Status status = Status.PENDING;
+
+    public StudentRegistration(User user, String firstName, String lastName, Date dob) {
+        this.userId = user.getId();
+        this.firstName = firstName;
+        this.lastName = lastName;
+        this.dob = dob;
+    }
+
+    public StudentRegistration() {}
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/payload/request/StudentUpdateRequest.java b/src/main/java/com/kasv/gunda/bootcamp/payload/request/StudentUpdateRequest.java
new file mode 100644
index 0000000..89e3678
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/payload/request/StudentUpdateRequest.java
@@ -0,0 +1,14 @@
+package com.kasv.gunda.bootcamp.payload.request;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Date;
+
+@Getter
+@Setter
+public class StudentUpdateRequest {
+    private String firstName;
+    private String lastName;
+    private Date dob;
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java
index 4153068..cfbd530 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/RefreshTokenRepository.java
@@ -13,6 +13,10 @@ import  com.kasv.gunda.bootcamp.models.User;
 public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
     Optional<RefreshToken> findByToken(String token);
 
+    Optional<RefreshToken> findByUser(User user);
+
     @Modifying
     int deleteByUser(User user);
+
+    int deleteByUserId(Long userId);
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRegistrationRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRegistrationRepository.java
new file mode 100644
index 0000000..457c352
--- /dev/null
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/StudentRegistrationRepository.java
@@ -0,0 +1,9 @@
+package com.kasv.gunda.bootcamp.repositories;
+
+import com.kasv.gunda.bootcamp.models.Status;
+import com.kasv.gunda.bootcamp.models.StudentRegistration;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface StudentRegistrationRepository extends JpaRepository<StudentRegistration, Integer> {
+
+}
diff --git a/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java b/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
index eff9ccd..4b8d4ed 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/repositories/UserRepository.java
@@ -11,6 +11,8 @@ import java.util.Optional;
 public interface UserRepository extends JpaRepository<User, Long> {
     Optional<User> findByUsername(String username);
 
+    User getUserById(Long id);
+
     Boolean existsByUsername(String username);
 
     Boolean existsByEmail(String email);
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java b/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
index 6157388..2b5bf4f 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/WebSecurityConfig.java
@@ -26,7 +26,7 @@ import java.util.Arrays;
 
 @Configuration
 @EnableMethodSecurity
-public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
+public class WebSecurityConfig {
     @Autowired
     UserDetailsServiceImpl userDetailsService;
 
@@ -38,11 +38,6 @@ public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
         return new AuthTokenFilter();
     }
 
-//	@Override
-//	public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
-//		authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
-//	}
-
     @Bean
     public DaoAuthenticationProvider authenticationProvider() {
         DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
@@ -53,12 +48,6 @@ public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
         return authProvider;
     }
 
-//	@Bean
-//	@Override
-//	public AuthenticationManager authenticationManagerBean() throws Exception {
-//		return super.authenticationManagerBean();
-//	}
-
     @Bean
     public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
         return authConfig.getAuthenticationManager();
@@ -69,18 +58,6 @@ public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
         return new BCryptPasswordEncoder();
     }
 
-//	@Override
-//	protected void configure(HttpSecurity http) throws Exception {
-//		http.cors().and().csrf().disable()
-//			.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
-//			.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
-//			.authorizeRequests().antMatchers("/api/auth/**").permitAll()
-//			.antMatchers("/api/test/**").permitAll()
-//			.anyRequest().authenticated();
-//
-//		http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
-//	}
-
     @Bean
     public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
         http.csrf(csrf -> csrf.disable())
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
index e380bae..cf3bf68 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/jwt/JwtUtils.java
@@ -41,6 +41,11 @@ public class JwtUtils {
                 .parseClaimsJws(token).getBody().getSubject();
     }
 
+    public Long getUserIdFromJwtToken(String token) {
+        return Jwts.parserBuilder().setSigningKey(key()).build()
+                .parseClaimsJws(token).getBody().get("id", Long.class);
+    }
+
     private Key key() {
         byte[] decodedSecret = Base64.getUrlDecoder().decode(jwtSecret);
         return Keys.hmacShaKeyFor(decodedSecret);
diff --git a/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java b/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java
index 1ce54e8..196446b 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/security/services/RefreshTokenService.java
@@ -49,7 +49,7 @@ public class RefreshTokenService {
     }
 
     @Transactional
-    public int deleteByUserId(Long userId) {
-        return refreshTokenRepository.deleteByUser(userRepository.findById(userId).get());
+    public void deleteByUserId(Long userId) {
+        refreshTokenRepository.findByUser(userRepository.findById(userId).get()).ifPresent(refreshTokenRepository::delete);
     }
 }
diff --git a/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java b/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java
index 79769c3..3aaa91a 100644
--- a/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java
+++ b/src/main/java/com/kasv/gunda/bootcamp/services/StudentService.java
@@ -2,137 +2,139 @@ package com.kasv.gunda.bootcamp.services;
 
 import com.google.gson.Gson;
 import com.kasv.gunda.bootcamp.models.Student;
+import com.kasv.gunda.bootcamp.models.StudentRegistration;
+import com.kasv.gunda.bootcamp.payload.request.StudentUpdateRequest;
+import com.kasv.gunda.bootcamp.repositories.StudentRegistrationRepository;
 import com.kasv.gunda.bootcamp.repositories.StudentRepository;
+import com.kasv.gunda.bootcamp.repositories.UserRepository;
 import com.kasv.gunda.bootcamp.security.jwt.JwtUtils;
-import jakarta.servlet.http.HttpServletRequest;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.GrantedAuthority;
 import org.springframework.stereotype.Service;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 @Service
 public class StudentService {
 
     private final JwtUtils jwtUtils;
     private final StudentRepository studentRepository;
+    private final StudentRegistrationRepository studentRegistrationRepository;
+    private final UserRepository userRepository;
 
-
-
-    public StudentService(JwtUtils jwtUtils, StudentRepository studentRepository) {
+    public StudentService(JwtUtils jwtUtils, StudentRepository studentRepository, StudentRegistrationRepository studentRegistrationRepository, UserRepository userRepository) {
         this.jwtUtils = jwtUtils;
         this.studentRepository = studentRepository;
+        this.studentRegistrationRepository = studentRegistrationRepository;
+        this.userRepository = userRepository;
     }
 
-//    public String getAllStudents(HttpServletRequest request) {
-//
-//        String token = jwtUtils.getJwtFromCookies(request);
-//        String username = jwtUtils.getUserNameFromJwtToken(token);
-//
-//
-//
-//
-//        List<Student> students = studentRepository.findAll();
-//
-//
-//        if () {
-//
-//            if (!tokenFunctions.isTokenValid(authCheck.getToken())) {
-//
-//                for (Student student : students) {
-//                    student.setDob(null);
-//                    student.setLastName(student.getLastName().substring(0, 1));
-//                }
-//
-//            }
-//        } else {
-//            for (Student student : students) {
-//                student.setDob(null);
-//                student.setLastName(student.getLastName().substring(0, 1));
-//            }
-//        }
-//        return gson.toJson(students);
-//    }
-
-//    public ResponseEntity<String> getStudentById(Long id, AuthCheck authCheck) {
-//
-//        Gson gson = new Gson();
-//        Map<String, String> jsonResponse = new HashMap<>();
-//
-//        long userId = (long) userRepository.findIdByUsername(authCheck.getUsername());
-//
-//        if (tokenFunctions.isTokenExists(userId)) {
-//            if (!tokenFunctions.isTokenValid(authCheck.getToken())) {
-//                jsonResponse.put("error", "Invalid token. Please provide valid token.");
-//                return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-//            }
-//        } else {
-//            jsonResponse.put("error", "Invalid token. Please provide valid token.");
-//            return ResponseEntity.status(401).body(gson.toJson(jsonResponse));
-//        }
-//
-//        if (!studentRepository.existsById(id)) {
-//            jsonResponse.put("error", "Student with id " + id + " not found.");
-//            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-//        }
-//
-//        return ResponseEntity.status(200).body(gson.toJson(studentRepository.findById(id)));
-//    }
+    public String getAllStudents(Collection<? extends GrantedAuthority> authorities) {
+
+        Gson gson = new Gson();
+        List<Student> students = studentRepository.findAll();
+
+        if (authorities.stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
+
+            return gson.toJson(students);
+
+        }
+
+        for (Student student : students) {
+            student.setDob(null);
+            student.setLastName(student.getLastName().substring(0, 1));
+        }
+
+        return gson.toJson(students);
+    }
 
     public int getStudentsCount() {
         return (int) studentRepository.count();
     }
 
-    public ResponseEntity<String> registerStudent(Student student) {
-
+    public ResponseEntity<String> registerStudent(StudentRegistration sur, Collection<? extends GrantedAuthority> authorities , String authorizationHeader) {
         Gson gson = new Gson();
         Map<String, String> jsonResponse = new HashMap<>();
 
-        if (student == null) {
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
+        if (authorities.stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
+            try {
+                Student student = new Student();
+                student.setFirstName(sur.getFirstName());
+                student.setLastName(sur.getLastName());
+                student.setDob(sur.getDob());
+                student.setUser(userRepository.getUserById(sur.getUserId()));
+                studentRepository.save(student);
+                return ResponseEntity.status(201).body("");
+            } catch (Exception e) {
+                jsonResponse.put("error", "Somethings wrong I can feel it");
+                return ResponseEntity.status(403).body(gson.toJson(jsonResponse));
+            }
         }
 
-        if (student.getFirstName() == null || student.getFirstName().isEmpty() ||
-                student.getLastName() == null || student.getLastName().isEmpty() ||
-                student.getDob() == null) {
-            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
+        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
+            String token = authorizationHeader.substring(7);
+            long usersId = jwtUtils.getUserIdFromJwtToken(token);
+
+            if (usersId != sur.getUserId()) {
+                jsonResponse.put("error", "You are not authorized to perform this action.");
+                return ResponseEntity.status(403).body(gson.toJson(jsonResponse));
+            }
+
+            try {
+                studentRegistrationRepository.save(sur);
+                return ResponseEntity.status(201).body("Your registration is pending approval.");
+            } catch (Exception e) {
+                jsonResponse.put("error", e.getMessage());
+                return ResponseEntity.status(403).body(gson.toJson(jsonResponse));
+            }
         }
 
-        if(studentRepository.existsById(student.getId())) {
-            jsonResponse.put("error", "Student with id " + student.getId() + " already exists.");
+        jsonResponse.put("error", "You are not authorized to perform this action.");
+        return ResponseEntity.status(403).body(gson.toJson(jsonResponse));
+    }
+
+    public ResponseEntity<String> getStudentById(Long id) {
+
+            Gson gson = new Gson();
+            Map<String, String> jsonResponse = new HashMap<>();
+
+            if (!studentRepository.existsById(id)) {
+                jsonResponse.put("error", "Student with id " + id + " not found.");
+                return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
+            }
+
+            return ResponseEntity.status(200).body(gson.toJson(studentRepository.findById(id)));
+    }
+
+    public ResponseEntity<String> updateStudent(long id, StudentUpdateRequest sur, Collection<? extends GrantedAuthority> authorities, String  authorizationHeader) {
+
+        Gson gson = new Gson();
+        Map<String, String> jsonResponse = new HashMap<>();
+
+        if (sur == null || !studentRepository.existsById(id)) {
+            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
             return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
         }
 
-        studentRepository.save(student);
+        Student studentFromDatabase = studentRepository.findById(id);
 
-        return ResponseEntity.status(201).body("{}");
-    }
+        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
+            String token = authorizationHeader.substring(7);
+            long usersId = jwtUtils.getUserIdFromJwtToken(token);
+
+            if (usersId != studentFromDatabase.getUser().getId()) {
+                if (authorities.stream().noneMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))){
+                    jsonResponse.put("error", "You are not authorized to perform this action.");
+                    return ResponseEntity.status(403).body(gson.toJson(jsonResponse));
+                }
+            }
+        }
 
+        studentFromDatabase.setFirstName(sur.getFirstName() != null ? sur.getFirstName() : studentFromDatabase.getFirstName());
+        studentFromDatabase.setLastName(sur.getLastName() != null ? sur.getLastName() : studentFromDatabase.getLastName());
+        studentFromDatabase.setDob(sur.getDob() != null ? sur.getDob() : studentFromDatabase.getDob());
 
-//    public ResponseEntity<String> updateLastName(Long id, LogoutRequest details) {
-//
-//        Gson gson = new Gson();
-//        Map<String, String> jsonResponse = new HashMap<>();
-//
-//        if (!tokenFunctions.isTokenValid(details.getToken())) {
-//            jsonResponse.put("error", "Invalid request. Please provide valid input data.");
-//            return ResponseEntity.status(400).body(gson.toJson(jsonResponse));
-//        }
-//
-//        if (!studentRepository.existsById(id)) {
-//            jsonResponse.put("error", "Student with id " + id + " not found.");
-//            return ResponseEntity.status(404).body(gson.toJson(jsonResponse));
-//        }
-//
-//        Student student = studentRepository.findById(id);
-//        student.setLastName(details.getLastName());
-//
-//        studentRepository.save(student);
-//
-//        return ResponseEntity.status(200).body("{}");
-//    }
+        studentRepository.save(studentFromDatabase);
+        return ResponseEntity.status(200).body("");
+    }
 }
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index c95eb14..36b17e6 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -8,8 +8,8 @@ spring.jpa.hibernate.ddl-auto=update
 
 # App Properties
 gunda.bootcamp.app.jwtSecret= 15c8374b1a7b790f6888693fa0d2710cfa46bfa2b67b5a80c7aedf4109853c7c
-gunda.bootcamp.app.jwtExpirationMs= 30000
-gunda.bootcamp.app.jwtRefreshExpirationMs= 86400000 
+gunda.bootcamp.app.jwtExpirationMs= 300000
+gunda.bootcamp.app.jwtRefreshExpirationMs= 86400000
 
 spring.mail.host=smtp.gmail.com
 spring.mail.port=587
-- 
GitLab