Index: finkwave/pom.xml
===================================================================
--- finkwave/pom.xml	(revision 4063ae02d3571017ce6ab0f099fb83bcf055ffcd)
+++ finkwave/pom.xml	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -61,4 +61,30 @@
 			<artifactId>spring-boot-starter-data-jpa</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>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-test</artifactId>
+			<scope>test</scope>
+		</dependency>
 	</dependencies>
 
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/JwtProperties.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/JwtProperties.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/JwtProperties.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,14 @@
+//package com.ukim.finki.develop.finkwave.config;
+//
+//import lombok.Getter;
+//import lombok.Setter;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//import org.springframework.context.annotation.Configuration;
+//
+//@Configuration
+//@Getter @Setter
+//@ConfigurationProperties(prefix = "jwt")
+//public class JwtProperties {
+//    private String secret;
+//    private long expirationMs;
+//}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/SecurityBeans.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/SecurityBeans.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/SecurityBeans.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,14 @@
+package com.ukim.finki.develop.finkwave.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+@Configuration
+public class SecurityBeans {
+    @Bean
+    public PasswordEncoder passwordEncoder(){
+        return new BCryptPasswordEncoder(10);
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/SecurityConfig.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/SecurityConfig.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/config/SecurityConfig.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,53 @@
+package com.ukim.finki.develop.finkwave.config;
+
+import java.util.List;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+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 com.ukim.finki.develop.finkwave.filters.JwtFilter;
+
+import lombok.RequiredArgsConstructor;
+
+@Configuration
+@RequiredArgsConstructor
+public class SecurityConfig {
+    private final JwtFilter jwtFilter;
+
+    @Bean
+    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+        http
+                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
+                .csrf(AbstractHttpConfigurer::disable)
+                .authorizeHttpRequests(auth -> auth
+                    .requestMatchers("/auth/user").authenticated()
+                    .anyRequest().permitAll())
+                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
+                .headers((headers) ->
+                        headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
+        return http.build();
+    }
+
+    @Bean
+    public CorsConfigurationSource corsConfigurationSource() {
+        CorsConfiguration configuration = new CorsConfiguration();
+
+        configuration.setAllowedOrigins(List.of("*"));
+        configuration.setAllowedMethods(List.of("*"));
+        configuration.setAllowedHeaders(List.of("*"));
+        configuration.setAllowCredentials(true);
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", configuration);
+        return source;
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AllController.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AllController.java	(revision 4063ae02d3571017ce6ab0f099fb83bcf055ffcd)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AllController.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -28,5 +28,5 @@
 
     @GetMapping("/admins")
-    public HttpEntity<List<Admin>> getAllAdmins(){
+        public HttpEntity<List<Admin>> getAllAdmins(){
         return ResponseEntity.ok(adminRepository.findAll());
     }
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AuthController.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AuthController.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/controller/AuthController.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,144 @@
+package com.ukim.finki.develop.finkwave.controller;
+
+import java.util.Map;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.server.ResponseStatusException;
+
+import com.ukim.finki.develop.finkwave.dto.AuthRequestDto;
+import com.ukim.finki.develop.finkwave.dto.AuthResponseDto;
+import com.ukim.finki.develop.finkwave.dto.LoginRequestDto;
+import com.ukim.finki.develop.finkwave.dto.UserResponseDto;
+import com.ukim.finki.develop.finkwave.service.AuthService;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+public class AuthController {
+    // todo: read from config
+    private final int accessTokenMaxAge = 900;
+    private final int refreshTokenMaxAge = 10 * 24 * 60 * 60;
+    private final AuthService authService;
+
+    @PostMapping("/register")
+    public ResponseEntity<Map<String, Object>> register(
+            @RequestBody AuthRequestDto authRequestDto,
+            HttpServletResponse httpServletResponse){
+        try {
+            AuthResponseDto authResponse = authService.registerAndLogIn(authRequestDto);
+            addTokensToResponse(httpServletResponse, authResponse);
+            return ResponseEntity.ok(Map.of(
+                    "user", authResponse.userResponseDto(),
+                    "tokenExpiresIn", accessTokenMaxAge
+            ));
+        } catch (Exception e){
+            return ResponseEntity
+                    .status(HttpStatus.BAD_REQUEST)
+                    .body(Map.of("error", e.getMessage()));
+        }
+    }
+
+    @PostMapping("/login")
+    public ResponseEntity<Map<String, Object>> login(
+            @RequestBody LoginRequestDto loginRequestDto,
+            HttpServletResponse httpServletResponse){
+        try {
+            AuthResponseDto authResponse = authService.login(loginRequestDto.username(), loginRequestDto.password());
+            addTokensToResponse(httpServletResponse, authResponse);
+            return ResponseEntity.ok(Map.of(
+                    "user", authResponse.userResponseDto(),
+                    "tokenExpiresIn", accessTokenMaxAge
+                    )
+            );
+        } catch (Exception e){
+            return ResponseEntity
+                    .status(HttpStatus.BAD_REQUEST)
+                    .body(Map.of("error", e.getMessage()));
+        }
+    }
+
+
+    @PostMapping("/refresh")
+    public ResponseEntity<Map<String, Integer>> refresh(
+            HttpServletResponse httpServletResponse,
+            @CookieValue(name = "refreshToken", required = false) String refreshToken){
+        if (refreshToken == null){
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "No credentials");
+        }
+        authService.refreshAccessTokenAndAddCookie(httpServletResponse, refreshToken);
+        return ResponseEntity.ok(Map.of(
+                "tokenExpiresIn", accessTokenMaxAge
+        ));
+    }
+
+    @GetMapping("/logout")
+    public ResponseEntity<Map<String, String>> logout(
+            HttpServletResponse httpServletResponse,
+            @CookieValue(name = "refreshToken", required = false) String refreshToken){
+        clearCookies(httpServletResponse, refreshToken);
+        SecurityContextHolder.clearContext();
+        return ResponseEntity.ok(Map.of("message", "Successful log out"));
+    }
+
+    @GetMapping("/user")
+    public ResponseEntity<Map<String, Object>> getUser(){
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        if (authentication == null || !authentication.isAuthenticated()){
+            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Not authenticated");
+        }
+        Object principal = authentication.getPrincipal();
+        String username = principal instanceof UserDetails userDetails
+                ? userDetails.getUsername()
+                : (principal != null ? principal.toString() : "");
+        UserResponseDto userDto = authService.getUserDtoByUsername(username);
+        return ResponseEntity.ok(Map.of(
+                "tokenExpiresIn", accessTokenMaxAge,
+                "user", userDto
+        ));
+    }
+
+    private void clearCookies(HttpServletResponse httpServletResponse, String refreshToken) {
+        if (refreshToken != null){
+            authService.invalidateRefreshToken(refreshToken);
+            Cookie clearCookie = new Cookie("refreshToken", null);
+            clearCookie.setHttpOnly(true);
+            clearCookie.setSecure(false);
+            clearCookie.setPath("/");
+            clearCookie.setMaxAge(0);
+            httpServletResponse.addCookie(clearCookie);
+        }
+
+        Cookie clearAccess = new Cookie("accessToken", null);
+        clearAccess.setHttpOnly(true);
+        clearAccess.setSecure(false);
+        clearAccess.setPath("/");
+        clearAccess.setMaxAge(0);
+        httpServletResponse.addCookie(clearAccess);
+    }
+
+
+    private void addTokensToResponse(HttpServletResponse response, AuthResponseDto authResponse){
+        Cookie refreshCookie = new Cookie("refreshToken", authResponse.refreshToken().getToken());
+        refreshCookie.setSecure(false);
+        refreshCookie.setHttpOnly(true);
+        refreshCookie.setPath("/");
+        refreshCookie.setMaxAge(refreshTokenMaxAge);
+        response.addCookie(refreshCookie);
+
+        authService.addAccessCookieToResponse(response, authResponse.accessToken());
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/AuthRequestDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/AuthRequestDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/AuthRequestDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,12 @@
+package com.ukim.finki.develop.finkwave.dto;
+
+import jakarta.annotation.Nullable;
+
+public record AuthRequestDto(
+        String username,
+        String fullname,
+        String email,
+        String password,
+        @Nullable String profilePhoto
+){
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/AuthResponseDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/AuthResponseDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/AuthResponseDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,7 @@
+package com.ukim.finki.develop.finkwave.dto;
+
+import com.ukim.finki.develop.finkwave.model.RefreshToken;
+
+public record AuthResponseDto (String accessToken, RefreshToken refreshToken, UserResponseDto userResponseDto) {
+}
+
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/LoginRequestDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/LoginRequestDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/LoginRequestDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,9 @@
+package com.ukim.finki.develop.finkwave.dto;
+
+import jakarta.annotation.Nullable;
+
+public record LoginRequestDto(
+        String username,
+        String password
+){
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/UserResponseDto.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/UserResponseDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/dto/UserResponseDto.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,8 @@
+package com.ukim.finki.develop.finkwave.dto;
+
+import com.ukim.finki.develop.finkwave.model.Role;
+
+public record UserResponseDto(
+        String username,
+        Role role){
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/filters/JwtFilter.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/filters/JwtFilter.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/filters/JwtFilter.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,68 @@
+package com.ukim.finki.develop.finkwave.filters;
+
+import com.ukim.finki.develop.finkwave.service.CustomUserDetailsService;
+import com.ukim.finki.develop.finkwave.service.JwtService;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.JwtException;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import org.springframework.http.HttpStatus;
+
+import java.io.IOException;
+
+@Component
+@RequiredArgsConstructor
+public class JwtFilter extends OncePerRequestFilter {
+    private final CustomUserDetailsService userDetailsService;
+    private final JwtService jwtService;
+
+    @Override
+    protected void doFilterInternal(
+            @NonNull HttpServletRequest request,
+            @NonNull HttpServletResponse response,
+            @NonNull FilterChain filterChain) throws ServletException, IOException {
+        String token = null;
+        if (request.getCookies() != null){
+            for (Cookie cookie: request.getCookies()){
+                if ("accessToken".equals(cookie.getName())){
+                    token = cookie.getValue();
+                    break;
+                }
+            }
+        }
+        if (token != null && !token.isEmpty()){
+            try {
+                Claims claims = jwtService.extractClaims(token);
+                String username = claims.getSubject();
+                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null){
+                    UserDetails userDetails = userDetailsService.loadUserByUsername(username);
+                    UsernamePasswordAuthenticationToken authToken =
+                            new UsernamePasswordAuthenticationToken(username, null, userDetails.getAuthorities());
+                    SecurityContextHolder.getContext().setAuthentication(authToken);
+                }
+            } catch (ExpiredJwtException e){
+                System.out.println("Expired jwt token.");
+                response.setStatus(HttpStatus.UNAUTHORIZED.value());
+                return;
+            } catch (JwtException e){
+                System.out.println("Invalid jwt token.");
+                return;
+            } catch (Exception e){
+                System.out.println(e.getMessage());
+                return;
+            }
+        }
+        filterChain.doFilter(request, response);
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/RefreshToken.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/RefreshToken.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/RefreshToken.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,27 @@
+package com.ukim.finki.develop.finkwave.model;
+
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.Instant;
+
+@Entity
+@Getter @Setter
+public class RefreshToken {
+    @Id
+    @GeneratedValue(strategy = GenerationType.IDENTITY)
+    private Long id;
+
+    @Column(nullable = false, unique = true)
+    private String token;
+
+    @ManyToOne
+    @JoinColumn(name = "user_id", nullable = false)
+    private User user;
+
+    @Column(name = "expires_at")
+    private Instant expiresAt;
+
+    private boolean revoked = false;
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Role.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Role.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/Role.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,6 @@
+package com.ukim.finki.develop.finkwave.model;
+
+public enum Role {
+    ADMIN,
+    NONADMIN
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/User.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/User.java	(revision 4063ae02d3571017ce6ab0f099fb83bcf055ffcd)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/model/User.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -4,9 +4,5 @@
 import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Id;
-import jakarta.persistence.OneToOne;
-import jakarta.persistence.Table;
+import jakarta.persistence.*;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
@@ -22,4 +18,5 @@
     @Id
     @Column(name = "user_id", nullable = false)
+    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_id_seq")
     private Long id;
 
@@ -44,4 +41,8 @@
     private Admin admin;
 
+    // todo: is this column needed? if so, add to entities and documentation
+    @Enumerated(EnumType.STRING)
+    private Role role;
+
     @OneToOne(mappedBy = "user")
     @JsonIgnore
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/RefreshTokenRepository.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/RefreshTokenRepository.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/RefreshTokenRepository.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,12 @@
+package com.ukim.finki.develop.finkwave.repository;
+
+import com.ukim.finki.develop.finkwave.model.RefreshToken;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.Optional;
+
+@Repository
+public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
+    Optional<RefreshToken> findByToken(String token);
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java	(revision 4063ae02d3571017ce6ab0f099fb83bcf055ffcd)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/repository/UserRepository.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -5,5 +5,8 @@
 import org.springframework.stereotype.Repository;
 
+import java.util.Optional;
+
 @Repository
 public interface UserRepository extends JpaRepository<User, Long> {
+    Optional<User> findByUsername(String username);
 }
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/AuthService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,96 @@
+package com.ukim.finki.develop.finkwave.service;
+
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+
+import com.ukim.finki.develop.finkwave.dto.AuthRequestDto;
+import com.ukim.finki.develop.finkwave.dto.AuthResponseDto;
+import com.ukim.finki.develop.finkwave.dto.UserResponseDto;
+import com.ukim.finki.develop.finkwave.model.NonAdminUser;
+import com.ukim.finki.develop.finkwave.model.RefreshToken;
+import com.ukim.finki.develop.finkwave.model.Role;
+import com.ukim.finki.develop.finkwave.model.User;
+import com.ukim.finki.develop.finkwave.repository.NonAdminUserRepository;
+import com.ukim.finki.develop.finkwave.repository.UserRepository;
+
+import jakarta.servlet.http.Cookie;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+
+@Service
+@RequiredArgsConstructor
+public class AuthService {
+    private final UserRepository userRepository;
+    private final NonAdminUserRepository nonAdminUserRepository;
+    private final PasswordEncoder passwordEncoder;
+    private final JwtService jwtService;
+    private final RefreshTokenService refreshTokenService;
+    private final int accessTokenMaxAge = 900;
+
+    // todo: should it be transactional?
+//    @Transactional
+    public AuthResponseDto registerAndLogIn(AuthRequestDto authRequestDto){
+        if (userRepository.findByUsername(authRequestDto.username()).isPresent()){
+            throw new RuntimeException("User already exists");
+        }
+        User user = new User();
+
+        // todo: builder
+        user.setUsername(authRequestDto.username());
+        user.setRole(Role.NONADMIN);
+        user.setFullName(authRequestDto.fullname());
+        user.setProfilePhoto(authRequestDto.profilePhoto());
+        user.setEmail(authRequestDto.email());
+        user.setPassword(passwordEncoder.encode(authRequestDto.password()));
+        NonAdminUser nonAdminUser = new NonAdminUser();
+        nonAdminUser.setUser(user);
+        user.setNonAdminUser(nonAdminUser);
+        nonAdminUserRepository.save(nonAdminUser);
+        userRepository.save(user);
+        return login(user.getUsername(), authRequestDto.password());
+    }
+
+
+    public AuthResponseDto login(String username, String password){
+        User user = userRepository.findByUsername(username)
+                .orElseThrow(() -> new RuntimeException("Invalid credentials"));
+        if (!passwordEncoder.matches(password, user.getPassword())){
+            throw new RuntimeException("Invalid credentials");
+        }
+
+        String accessToken = jwtService.generateToken(username, user.getRole().name());
+        RefreshToken refreshToken = refreshTokenService.createRefreshToken(user);
+        UserResponseDto userResponseDto = new UserResponseDto(user.getUsername(), user.getRole());
+        return new AuthResponseDto(accessToken, refreshToken, userResponseDto);
+    }
+
+    public String refreshAccessToken(String refreshTokenString) {
+        RefreshToken refreshToken = refreshTokenService.validateRefreshToken(refreshTokenString);
+        User user = refreshToken.getUser();
+        return jwtService.generateToken(user.getUsername(), user.getRole().name());
+    }
+
+    public void refreshAccessTokenAndAddCookie(HttpServletResponse response, String refreshToken){
+        String accessToken = refreshAccessToken(refreshToken);
+        addAccessCookieToResponse(response, accessToken);
+    }
+
+    public UserResponseDto getUserDtoByUsername(String username){
+        User user = userRepository.findByUsername(username)
+                .orElseThrow(() -> new RuntimeException("Invalid credentials"));
+        return new UserResponseDto(user.getUsername(), user.getRole());
+    }
+
+    public void invalidateRefreshToken(String refreshToken) {
+        refreshTokenService.revokeToken(refreshToken);
+    }
+
+    public void addAccessCookieToResponse(HttpServletResponse response, String accessToken){
+        Cookie accessCookie = new Cookie("accessToken", accessToken);
+        accessCookie.setHttpOnly(true);
+        accessCookie.setSecure(false);
+        accessCookie.setPath("/");
+        accessCookie.setMaxAge(accessTokenMaxAge);
+        response.addCookie(accessCookie);
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/CustomUserDetailsService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/CustomUserDetailsService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/CustomUserDetailsService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,32 @@
+package com.ukim.finki.develop.finkwave.service;
+
+import com.ukim.finki.develop.finkwave.model.User;
+import com.ukim.finki.develop.finkwave.repository.UserRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+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 java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class CustomUserDetailsService implements UserDetailsService {
+    private final UserRepository userRepository;
+
+    @Override
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        User user = userRepository.findByUsername(username)
+                .orElseThrow(() -> new RuntimeException("user not found"));
+//        List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()));
+        List<GrantedAuthority> authorities = List.of(new SimpleGrantedAuthority("ROLE_NONADMIN"));
+        return new org.springframework.security.core.userdetails.User(
+                user.getUsername(),
+                user.getPassword(),
+                authorities
+        );
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/JwtService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/JwtService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/JwtService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,37 @@
+package com.ukim.finki.develop.finkwave.service;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.stereotype.Service;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+@Service
+public class JwtService {
+    // todo: read from config
+    private final int accessTokenMaxAge = 900; // 900s - 15min
+    // todo: replace hardcoded secret
+    private final String secretKey = "very-really-extremely-secret-key-123";
+
+    public String generateToken(String username, String role){
+        SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
+        return Jwts.builder()
+                .setSubject(username)
+                .claim("role", "ROLE_" + role)
+                .setExpiration(new Date(System.currentTimeMillis() + (long) accessTokenMaxAge * 1000))
+                .signWith(key)
+                .compact();
+    }
+
+    public Claims extractClaims(String token){
+        SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
+        return Jwts.parserBuilder()
+                .setSigningKey(key)
+                .build()
+                .parseClaimsJws(token)
+                .getBody();
+    }
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/RefreshTokenService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/RefreshTokenService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/RefreshTokenService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -0,0 +1,46 @@
+package com.ukim.finki.develop.finkwave.service;
+
+import com.ukim.finki.develop.finkwave.model.RefreshToken;
+import com.ukim.finki.develop.finkwave.model.User;
+import com.ukim.finki.develop.finkwave.repository.RefreshTokenRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Optional;
+import java.util.UUID;
+
+@Service
+@RequiredArgsConstructor
+public class RefreshTokenService {
+    private final RefreshTokenRepository refreshTokenRepository;
+
+    public RefreshToken createRefreshToken(User user){
+        RefreshToken refreshToken = new RefreshToken();
+        refreshToken.setUser(user);
+        refreshToken.setToken(UUID.randomUUID().toString());
+        refreshToken.setExpiresAt(Instant.now().plus(14, ChronoUnit.DAYS));
+        return refreshTokenRepository.save(refreshToken);
+    }
+
+    public Optional<RefreshToken> findByToken(String token){
+        return refreshTokenRepository.findByToken(token);
+    }
+
+    public RefreshToken validateRefreshToken(String token){
+        RefreshToken refreshToken = findByToken(token).orElseThrow(() -> new RuntimeException("Invalid token"));
+        if (refreshToken.isRevoked() || refreshToken.getExpiresAt().isBefore(Instant.now())){
+            throw new RuntimeException("Invalid refresh token");
+        }
+        return refreshToken;
+    }
+
+    public void revokeToken(String token){
+        findByToken(token).ifPresent(t -> {
+            t.setRevoked(true);
+            refreshTokenRepository.save(t);
+        });
+    }
+
+}
Index: finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/UserService.java
===================================================================
--- finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/UserService.java	(revision 4063ae02d3571017ce6ab0f099fb83bcf055ffcd)
+++ finkwave/src/main/java/com/ukim/finki/develop/finkwave/service/UserService.java	(revision c6f7486d575c33b8d0eb258135fcb1cf36590e21)
@@ -4,5 +4,4 @@
 import com.ukim.finki.develop.finkwave.repository.UserRepository;
 import lombok.AllArgsConstructor;
-import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 
