diff --git a/app(backend)/.env-sample b/app(backend)/.env-sample
index f4da6c6..da8afd0 100644
--- a/app(backend)/.env-sample
+++ b/app(backend)/.env-sample
@@ -1,3 +1,10 @@
DB_URL= postgresSql_database_url
DB_USERNAME= postgresSql_database_username
DB_PASSWORD= postgresSql_database_password
+
+JWT_SECRET_KEY=your_secret_key
+JWT_EXP_TIME=expiration_time
+JWT_THRESHOLD_TIME=threshold_time
+
+REDIS_HOST=redis_host
+REDIS_PORT=redis_port
\ No newline at end of file
diff --git a/app(backend)/pom.xml b/app(backend)/pom.xml
index c63d311..10737a9 100644
--- a/app(backend)/pom.xml
+++ b/app(backend)/pom.xml
@@ -78,6 +78,27 @@
spring-security-test
test
+
+
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
+
+
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-redis
+
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/config/AppConfig.java b/app(backend)/src/main/java/com/knockoutzone/backend/config/AppConfig.java
new file mode 100644
index 0000000..9646dce
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/config/AppConfig.java
@@ -0,0 +1,14 @@
+package com.knockoutzone.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+
+@Configuration
+public class AppConfig {
+
+ @Bean
+ public BCryptPasswordEncoder passwordEncoder() {
+ return new BCryptPasswordEncoder();
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/config/RedisConfig.java b/app(backend)/src/main/java/com/knockoutzone/backend/config/RedisConfig.java
new file mode 100644
index 0000000..f9e6462
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/config/RedisConfig.java
@@ -0,0 +1,28 @@
+package com.knockoutzone.backend.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+ @Bean
+ public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
+ StringRedisTemplate template = new StringRedisTemplate();
+ template.setConnectionFactory(connectionFactory);
+ return template;
+ }
+
+ @Bean
+ public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
+ RedisTemplate template = new RedisTemplate<>();
+ template.setConnectionFactory(connectionFactory);
+ template.setKeySerializer(new StringRedisSerializer());
+ template.setValueSerializer(new StringRedisSerializer());
+ return template;
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/config/SecurityConfig.java b/app(backend)/src/main/java/com/knockoutzone/backend/config/SecurityConfig.java
new file mode 100644
index 0000000..d7d1a47
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/config/SecurityConfig.java
@@ -0,0 +1,44 @@
+package com.knockoutzone.backend.config;
+
+import com.knockoutzone.backend.filters.JwtAuthenticationFilter;
+import com.knockoutzone.backend.serviceimpl.TokenBlackListService;
+import com.knockoutzone.backend.util.JwtUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+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.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+@Configuration
+public class SecurityConfig {
+
+ private final JwtUtil jwtUtil;
+ private final TokenBlackListService tokenBlackListService;
+
+ @Autowired
+ public SecurityConfig(JwtUtil jwtUtil, TokenBlackListService tokenBlackListService) {
+ this.jwtUtil = jwtUtil;
+ this.tokenBlackListService = tokenBlackListService;
+ }
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+
+ JwtAuthenticationFilter jwtAuthFilter = new JwtAuthenticationFilter(jwtUtil, tokenBlackListService);
+
+ http
+ .csrf(csrf -> csrf.disable()) // Disable CSRF for REST APIs
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/auth/register", "/api/auth/login").permitAll() // Allow public access to register and login endpoint
+ .anyRequest().authenticated() // Secure all other endpoints
+ )
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/controller/AuthController.java b/app(backend)/src/main/java/com/knockoutzone/backend/controller/AuthController.java
new file mode 100644
index 0000000..7d57a75
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/controller/AuthController.java
@@ -0,0 +1,45 @@
+package com.knockoutzone.backend.controller;
+
+import com.knockoutzone.backend.dto.*;
+import com.knockoutzone.backend.service.AuthService;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/auth")
+public class AuthController {
+
+ @Autowired
+ private AuthService authService;
+
+ public AuthController(AuthService authService) {
+ this.authService = authService;
+ }
+
+ @PostMapping("/register")
+ public ResponseEntity register(@RequestBody UserRegistrationRequest request){
+ UserRegistrationResponse response = authService.registerUser(request);
+ return ResponseEntity.status(HttpStatus.CREATED).body(response);
+ }
+
+ @PostMapping("/login")
+ public ResponseEntity login(@RequestBody UserLoginRequest request) {
+ String jwtToken = authService.authenticateUser(request);
+ return ResponseEntity.ok().header(HttpHeaders.AUTHORIZATION, "Bearer " + jwtToken).body(Constants.USER_LOGIN_SUCCESSFUL);
+ }
+
+ @PostMapping("/logout")
+ public ResponseEntity> logout(HttpServletRequest request) {
+ String authHeader = request.getHeader("Authorization");
+
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ return ResponseEntity.badRequest().body("Missing or invalid Authorization header");
+ }
+
+ return ResponseEntity.ok(authService.logoutUser(authHeader));
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/dto/Constants.java b/app(backend)/src/main/java/com/knockoutzone/backend/dto/Constants.java
new file mode 100644
index 0000000..bd798e8
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/dto/Constants.java
@@ -0,0 +1,15 @@
+package com.knockoutzone.backend.dto;
+
+public class Constants {
+
+ public static final String INVALID_EMAIL = "Invalid email format.";
+ public static final String EMAIL_ALREADY_USED = "Email already in use.";
+ public static final String WEAK_PASSWORD = "Password is too weak. Must be 8+ chars, with uppercase, lowercase, digit and special character.";
+ public static final String FULL_NAME_REQUIRED = "Full name is required.";
+ public static final String REGISTRATION_SUCCESS = "User registered successfully!";
+ public static final String USER_NOT_FOUND = "User not found";
+ public static final String PASSWORD_MISMATCH = "Invalid password";
+ public static final String USER_LOGIN_SUCCESSFUL = "Login successful.";
+ public static final String USER_LOGOUT_SUCCESSFUL = "Logged out successfully.";
+ public static final String TOKEN_REVOKED = "Token has been revoked.";
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserLoginRequest.java b/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserLoginRequest.java
new file mode 100644
index 0000000..258b91f
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserLoginRequest.java
@@ -0,0 +1,10 @@
+package com.knockoutzone.backend.dto;
+
+import lombok.Data;
+
+@Data
+public class UserLoginRequest {
+
+ private String email;
+ private String password;
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserRegistrationRequest.java b/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserRegistrationRequest.java
new file mode 100644
index 0000000..d9a2487
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserRegistrationRequest.java
@@ -0,0 +1,13 @@
+package com.knockoutzone.backend.dto;
+
+import com.knockoutzone.backend.entity.enums.Role;
+import lombok.Data;
+
+@Data
+public class UserRegistrationRequest {
+
+ private String fullName;
+ private String email;
+ private String password;
+ private Role role;
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserRegistrationResponse.java b/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserRegistrationResponse.java
new file mode 100644
index 0000000..41468c2
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/dto/UserRegistrationResponse.java
@@ -0,0 +1,11 @@
+package com.knockoutzone.backend.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+@Data
+@AllArgsConstructor
+public class UserRegistrationResponse {
+ int statusCode;
+ String message;
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/entity/User.java b/app(backend)/src/main/java/com/knockoutzone/backend/entity/User.java
new file mode 100644
index 0000000..fee2981
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/entity/User.java
@@ -0,0 +1,40 @@
+package com.knockoutzone.backend.entity;
+
+import com.knockoutzone.backend.entity.enums.Role;
+import jakarta.persistence.*;
+import lombok.Data;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
+import java.time.LocalDateTime;
+
+@Entity
+@Table(name = "users")
+@Data
+public class User {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ private Long id;
+
+ @Column(name = "full_name", nullable = false)
+ private String fullName;
+
+ @Column(unique = true, length = 100, nullable = false)
+ private String email;
+
+ @Enumerated(EnumType.STRING)
+ @Column(nullable = false)
+ private Role role;
+
+ @Column(name = "password_hash", nullable = false)
+ private String passwordHash;
+
+ @CreationTimestamp
+ private LocalDateTime createdAt;
+
+ @UpdateTimestamp
+ private LocalDateTime updatedAt;
+
+ private boolean isDeleted = false;
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/entity/enums/Role.java b/app(backend)/src/main/java/com/knockoutzone/backend/entity/enums/Role.java
new file mode 100644
index 0000000..289ec44
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/entity/enums/Role.java
@@ -0,0 +1,7 @@
+package com.knockoutzone.backend.entity.enums;
+
+public enum Role {
+ PLAYER,
+ ADMIN,
+ ORGANISER
+}
\ No newline at end of file
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/filters/JwtAuthenticationFilter.java b/app(backend)/src/main/java/com/knockoutzone/backend/filters/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..f83197a
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/filters/JwtAuthenticationFilter.java
@@ -0,0 +1,84 @@
+package com.knockoutzone.backend.filters;
+
+import com.knockoutzone.backend.dto.Constants;
+import com.knockoutzone.backend.serviceimpl.TokenBlackListService;
+import com.knockoutzone.backend.util.JwtUtil;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+
+@Component
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ @Value("${security.jwt.threshold.time}")
+ private long thresholdTime;
+
+ private final JwtUtil jwtUtil;
+ private final TokenBlackListService tokenBlackListService;
+
+ @Autowired
+ public JwtAuthenticationFilter(JwtUtil jwtUtil, TokenBlackListService tokenBlackListService) {
+ this.jwtUtil = jwtUtil;
+ this.tokenBlackListService = tokenBlackListService;
+ }
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+
+ final String authHeader = request.getHeader("Authorization");
+
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ final String jwt = authHeader.substring(7);
+
+ if(jwtUtil.isTokenValid(jwt)) {
+
+ String jti = jwtUtil.extractJti(jwt);
+ if(tokenBlackListService.isTokenBlacklisted(jti)) {
+ response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ response.getWriter().write(Constants.TOKEN_REVOKED);
+ return;
+ }
+
+ String userName = jwtUtil.extractUsername(jwt);
+
+ if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
+ userName, null, Collections.emptyList());
+
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ }
+
+ checkIfTokenExpired(jwt, userName, response);
+ }
+
+ filterChain.doFilter(request, response);
+ }
+
+ public void checkIfTokenExpired(String token, String userName, HttpServletResponse response){
+
+ Date expirationTime = jwtUtil.extractExpiration(token);
+ long timeLeft = expirationTime.getTime() - System.currentTimeMillis();
+
+ if (timeLeft < thresholdTime) { // less than 5 minutes left
+ // Generate new token with refreshed expiry
+ String newToken = jwtUtil.generateToken(userName);
+
+ // Send new token back in response header (or response body)
+ response.setHeader("Authorization", "Bearer " + newToken);
+ }
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/repository/UserRepository.java b/app(backend)/src/main/java/com/knockoutzone/backend/repository/UserRepository.java
new file mode 100644
index 0000000..49291ee
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/repository/UserRepository.java
@@ -0,0 +1,12 @@
+package com.knockoutzone.backend.repository;
+
+import com.knockoutzone.backend.entity.User;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+import java.util.Optional;
+
+@Repository
+public interface UserRepository extends JpaRepository {
+ boolean existsByEmail(String email);
+ Optional findByEmail(String email);
+}
\ No newline at end of file
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/service/AuthService.java b/app(backend)/src/main/java/com/knockoutzone/backend/service/AuthService.java
new file mode 100644
index 0000000..f3062bf
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/service/AuthService.java
@@ -0,0 +1,14 @@
+package com.knockoutzone.backend.service;
+
+import com.knockoutzone.backend.dto.UserLoginRequest;
+import com.knockoutzone.backend.dto.UserRegistrationRequest;
+import com.knockoutzone.backend.dto.UserRegistrationResponse;
+
+public interface AuthService {
+
+ UserRegistrationResponse registerUser(UserRegistrationRequest userRegistrationRequest);
+
+ String authenticateUser(UserLoginRequest userLoginRequest);
+
+ String logoutUser(String authHeader);
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/service/impl/.gitkeep b/app(backend)/src/main/java/com/knockoutzone/backend/service/impl/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/serviceimpl/AuthServiceImpl.java b/app(backend)/src/main/java/com/knockoutzone/backend/serviceimpl/AuthServiceImpl.java
new file mode 100644
index 0000000..09c5e90
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/serviceimpl/AuthServiceImpl.java
@@ -0,0 +1,94 @@
+package com.knockoutzone.backend.serviceimpl;
+
+import com.knockoutzone.backend.dto.Constants;
+import com.knockoutzone.backend.dto.UserLoginRequest;
+import com.knockoutzone.backend.dto.UserRegistrationRequest;
+import com.knockoutzone.backend.dto.UserRegistrationResponse;
+import com.knockoutzone.backend.entity.User;
+import com.knockoutzone.backend.repository.UserRepository;
+import com.knockoutzone.backend.service.AuthService;
+import com.knockoutzone.backend.util.JwtUtil;
+import com.knockoutzone.backend.util.UserMapperUtil;
+import com.knockoutzone.backend.util.ValidationUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.stereotype.Service;
+import java.util.Date;
+
+@Service
+public class AuthServiceImpl implements AuthService {
+
+ private final UserRepository userRepository;
+ private final BCryptPasswordEncoder bCryptPasswordEncoder;
+ private final JwtUtil jwtUtil;
+ private final TokenBlackListService tokenBlackListService;
+ private final ValidationUtil validationUtil = new ValidationUtil();
+ private final UserMapperUtil userMapperUtil = new UserMapperUtil();
+
+ @Autowired
+ public AuthServiceImpl(UserRepository userRepository,
+ BCryptPasswordEncoder bCryptPasswordEncoder, JwtUtil jwtUtil, TokenBlackListService tokenBlackListService) {
+ this.userRepository = userRepository;
+ this.bCryptPasswordEncoder = bCryptPasswordEncoder;
+ this.jwtUtil = jwtUtil;
+ this.tokenBlackListService = tokenBlackListService;
+ }
+
+ @Override
+ public UserRegistrationResponse registerUser(UserRegistrationRequest userRegistrationRequest) {
+
+ if (!validationUtil.isValidEmail(userRegistrationRequest.getEmail())) {
+ throw new IllegalArgumentException(Constants.INVALID_EMAIL);
+ }
+
+ // Check if email is already taken
+ if (userRepository.existsByEmail(userRegistrationRequest.getEmail())) {
+ throw new IllegalArgumentException(Constants.EMAIL_ALREADY_USED);
+ }
+
+ // Validate password strength
+ if (!validationUtil.isStrongPassword(userRegistrationRequest.getPassword())) {
+ throw new IllegalArgumentException(Constants.WEAK_PASSWORD);
+ }
+
+ // Validate full name
+ if (!validationUtil.isNotEmpty(userRegistrationRequest.getFullName())) {
+ throw new IllegalArgumentException(Constants.FULL_NAME_REQUIRED);
+ }
+
+ // Hash password
+ String hashedPassword = bCryptPasswordEncoder.encode(userRegistrationRequest.getPassword());
+ User user = userMapperUtil.toEntity(userRegistrationRequest, hashedPassword);
+ userRepository.save(user);
+
+ return new UserRegistrationResponse(HttpStatus.CREATED.value(), Constants.REGISTRATION_SUCCESS);
+ }
+
+ @Override
+ public String authenticateUser(UserLoginRequest userLoginRequest) {
+
+ User user = userRepository.findByEmail(userLoginRequest.getEmail())
+ .orElseThrow(() -> new UsernameNotFoundException(Constants.USER_NOT_FOUND));
+
+ if (!bCryptPasswordEncoder.matches(userLoginRequest.getPassword(), user.getPasswordHash())) {
+ throw new BadCredentialsException(Constants.PASSWORD_MISMATCH);
+ }
+
+ //return jwt token
+ return jwtUtil.generateToken(user.getEmail());
+ }
+
+ @Override
+ public String logoutUser(String authHeader){
+ String jti = jwtUtil.extractJti(authHeader.substring(7));
+ Date expiration = jwtUtil.extractExpiration(authHeader.substring(7));
+ long ttl = expiration.getTime() - System.currentTimeMillis();
+
+ tokenBlackListService.saveBlacklistToken(jti, ttl);
+
+ return Constants.USER_LOGOUT_SUCCESSFUL;
+ }
+}
\ No newline at end of file
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/serviceimpl/TokenBlackListService.java b/app(backend)/src/main/java/com/knockoutzone/backend/serviceimpl/TokenBlackListService.java
new file mode 100644
index 0000000..7945df8
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/serviceimpl/TokenBlackListService.java
@@ -0,0 +1,25 @@
+package com.knockoutzone.backend.serviceimpl;
+
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Service;
+import java.util.concurrent.TimeUnit;
+
+@Service
+public class TokenBlackListService {
+
+ private final StringRedisTemplate redisTemplate;
+
+ public TokenBlackListService(StringRedisTemplate redisTemplate) {
+ this.redisTemplate = redisTemplate;
+ }
+
+ // Save token jti with TTL
+ public void saveBlacklistToken(String jti, long ttlMillis) {
+ redisTemplate.opsForValue().set(jti, "blacklisted", ttlMillis, TimeUnit.MILLISECONDS);
+ }
+
+ // Check if token jti is blacklisted
+ public boolean isTokenBlacklisted(String jti) {
+ return Boolean.TRUE.equals(redisTemplate.hasKey(jti));
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/util/JwtUtil.java b/app(backend)/src/main/java/com/knockoutzone/backend/util/JwtUtil.java
new file mode 100644
index 0000000..8063e0a
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/util/JwtUtil.java
@@ -0,0 +1,81 @@
+package com.knockoutzone.backend.util;
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.SignatureAlgorithm;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.security.Key;
+import java.util.Date;
+import java.util.UUID;
+import java.util.function.Function;
+
+@Component
+public class JwtUtil {
+
+ @Value("${security.jwt.secret-key}")
+ private String secretKey;
+
+ @Value("${security.jwt.expiration}")
+ private long expirationTime;
+
+ public String generateToken(String userEmail){
+ return Jwts.builder()
+ .setSubject(userEmail)
+ .setId(UUID.randomUUID().toString())
+ .setIssuedAt(new Date())
+ .setExpiration(new Date(System.currentTimeMillis() + expirationTime))
+ .signWith(getSignInKey(), SignatureAlgorithm.HS256)
+ .compact();
+ }
+
+ public boolean isTokenValid(String token) {
+ try {
+ Jwts.parserBuilder()
+ .setSigningKey(getSignInKey())
+ .build()
+ .parseClaimsJws(token);
+ return true; // signature and expiry check passed
+ } catch (JwtException | IllegalArgumentException e) {
+ // token invalid or expired
+ return false;
+ }
+ }
+
+ public String extractUsername(String token) {
+ return extractClaim(token, Claims::getSubject);
+ }
+
+ public String extractJti(String token) {
+ return extractClaim(token, Claims::getId);
+ }
+
+
+ public Date extractExpiration(String token) {
+ return extractClaim(token, Claims::getExpiration);
+ }
+
+ // Extract a single claim
+ public T extractClaim(String token, Function claimsResolver) {
+ final Claims claims = extractAllClaims(token);
+ return claimsResolver.apply(claims);
+ }
+
+ private Claims extractAllClaims(String token) {
+ return Jwts
+ .parserBuilder()
+ .setSigningKey(getSignInKey())
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+ }
+
+ private Key getSignInKey() {
+ byte[] keyBytes = Decoders.BASE64.decode(secretKey);
+ return Keys.hmacShaKeyFor(keyBytes);
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/util/UserMapperUtil.java b/app(backend)/src/main/java/com/knockoutzone/backend/util/UserMapperUtil.java
new file mode 100644
index 0000000..03a4c45
--- /dev/null
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/util/UserMapperUtil.java
@@ -0,0 +1,22 @@
+package com.knockoutzone.backend.util;
+
+import com.knockoutzone.backend.dto.UserRegistrationRequest;
+import com.knockoutzone.backend.entity.User;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+@Component
+public class UserMapperUtil {
+
+ public User toEntity(UserRegistrationRequest request, String hashedPassword) {
+ User user = new User();
+ user.setFullName(request.getFullName());
+ user.setEmail(request.getEmail());
+ user.setPasswordHash(hashedPassword);
+ user.setRole(request.getRole());
+ user.setCreatedAt(LocalDateTime.now());
+ user.setUpdatedAt(LocalDateTime.now());
+ return user;
+ }
+}
diff --git a/app(backend)/src/main/java/com/knockoutzone/backend/util/ValidationUtil.java b/app(backend)/src/main/java/com/knockoutzone/backend/util/ValidationUtil.java
index 9ab0179..8aaa2d5 100644
--- a/app(backend)/src/main/java/com/knockoutzone/backend/util/ValidationUtil.java
+++ b/app(backend)/src/main/java/com/knockoutzone/backend/util/ValidationUtil.java
@@ -1,10 +1,12 @@
package com.knockoutzone.backend.util;
+import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;
+@Component
public class ValidationUtil {
private static final Pattern EMAIL_PATTERN=Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[A-Za-z]{2,}$");
@@ -14,7 +16,7 @@ public class ValidationUtil {
private static final Pattern STRONG_PASSWORD_PATTERN = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$");
- private ValidationUtil(){
+ public ValidationUtil(){
}
diff --git a/app(backend)/src/main/resources/application-dev.properties b/app(backend)/src/main/resources/application-dev.properties
index 2affa4e..23f4b9c 100644
--- a/app(backend)/src/main/resources/application-dev.properties
+++ b/app(backend)/src/main/resources/application-dev.properties
@@ -3,4 +3,11 @@ spring.datasource.username=${DB_USERNAME}
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
-spring.jpa.show-sql=true
\ No newline at end of file
+spring.jpa.show-sql=false
+
+security.jwt.secret-key=${JWT_SECRET_KEY}
+security.jwt.expiration=${JWT_EXP_TIME}
+security.jwt.threshold.time=${JWT_THRESHOLD_TIME}
+
+spring.data.redis.host=${REDIS_HOST}
+spring.data.redis.port=${REDIS_PORT}
\ No newline at end of file
diff --git a/app(backend)/src/main/resources/application.properties b/app(backend)/src/main/resources/application.properties
index a6b2d45..bfdccbf 100644
--- a/app(backend)/src/main/resources/application.properties
+++ b/app(backend)/src/main/resources/application.properties
@@ -1,2 +1,2 @@
spring.application.name=KnockOutZone Backend
-spring.profiles.active=dev
+spring.profiles.active=dev
\ No newline at end of file
diff --git a/app(backend)/src/test/java/com/knockoutzone/backend/util/LoggerUtilTest.java b/app(backend)/src/test/java/com/knockoutzone/backend/util/LoggerUtilTest.java
index 0ff838a..f2630c5 100644
--- a/app(backend)/src/test/java/com/knockoutzone/backend/util/LoggerUtilTest.java
+++ b/app(backend)/src/test/java/com/knockoutzone/backend/util/LoggerUtilTest.java
@@ -8,9 +8,9 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
-
import static org.junit.jupiter.api.Assertions.*;
+
class LoggerUtilTest {
private ListAppender listAppender;