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;