From 69aa0f5ebe70a940f8be2c20c633b59b6f9b65fa Mon Sep 17 00:00:00 2001 From: abeb Date: Wed, 4 Mar 2026 22:30:31 +0300 Subject: [PATCH 01/13] feat: add auth controller, service and repository structure --- backend/smartjam-api/build.gradle | 3 + .../smartjamapi/SmartjamApiApplication.java | 4 +- .../com/smartjam/smartjamapi/UserEntity.java | 64 +++++++++++++++++++ .../smartjamapi/config/SecurityConfig.java | 31 +++++++++ .../controller/AuthController.java | 37 +++++++++++ .../smartjamapi/dto/AuthResponse.java | 5 ++ .../smartjamapi/dto/ErrorResponseDto.java | 5 ++ .../smartjamapi/dto/LoginRequest.java | 3 + .../smartjamapi/dto/RegisterRequest.java | 3 + .../smartjamapi/enums/AvailabilityStatus.java | 8 +++ .../exception/GlobalExceptionHandler.java | 52 +++++++++++++++ .../repository/AuthRepository.java | 11 ++++ .../smartjamapi/service/AuthService.java | 53 +++++++++++++++ 13 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java diff --git a/backend/smartjam-api/build.gradle b/backend/smartjam-api/build.gradle index 3fbe05b..a920021 100644 --- a/backend/smartjam-api/build.gradle +++ b/backend/smartjam-api/build.gradle @@ -1,7 +1,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation("org.springframework.boot:spring-boot-starter-validation") + implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'org.postgresql:postgresql' implementation 'org.springframework.boot:spring-boot-starter-kafka' diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java index bff17ed..d327fae 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java @@ -1,12 +1,14 @@ package com.smartjam.smartjamapi; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SmartjamApiApplication { - public static void main(String[] args) { + static void main(String[] args) { SpringApplication.run(SmartjamApiApplication.class, args); } + } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java new file mode 100644 index 0000000..9edd3e7 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java @@ -0,0 +1,64 @@ +package com.smartjam.smartjamapi; + +import jakarta.persistence.*; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@Table(name = "users") +@Entity +public class UserEntity { + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE) + private Long id; + + @Column(nullable = false, unique = true) + private String username; + + @Column(nullable = false, unique = true) + private String email; + + @Column(name = "password_hash", nullable = false) + private String passwordHash; + + @Column(name = "first_name") + private String firstName; + + @Column(name = "last_name") + private String lastName; + + @Column(name = "avatar_url") + private String avatarUrl; + + @Column(nullable = false) + private String role; + + @Column(name = "fcm_token") + private String fcmToken; + + public UserEntity() {} + + public UserEntity( + Long id, + String username, + String email, + String passwordHash, + String firstName, + String lastName, + String avatarUrl, + String role, + String fcmToken) { + this.id = id; + this.username = username; + this.email = email; + this.passwordHash = passwordHash; + this.firstName = firstName; + this.lastName = lastName; + this.avatarUrl = avatarUrl; + this.role = role; + this.fcmToken = fcmToken; + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java new file mode 100644 index 0000000..840bc0a --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java @@ -0,0 +1,31 @@ +package com.smartjam.smartjamapi.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http.csrf(AbstractHttpConfigurer::disable) // <--- так правильно в Spring Security 6 + .authorizeHttpRequests(auth -> auth.requestMatchers("/auth/**") + .permitAll() + .anyRequest() + .authenticated()) + .httpBasic(Customizer.withDefaults()); + + return http.build(); + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java new file mode 100644 index 0000000..37bf74b --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -0,0 +1,37 @@ +package com.smartjam.smartjamapi.controller; + +import jakarta.validation.Valid; + +import com.smartjam.smartjamapi.dto.AuthResponse; +import com.smartjam.smartjamapi.dto.LoginRequest; +import com.smartjam.smartjamapi.dto.RegisterRequest; +import com.smartjam.smartjamapi.service.AuthService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/auth") +public class AuthController { + + private static final Logger log = LoggerFactory.getLogger(AuthController.class); + + private final AuthService authService; + + public AuthController(AuthService authService) { + this.authService = authService; + } + + @PostMapping("/login") + public ResponseEntity login(@RequestBody @Valid LoginRequest request) { + log.info("Calling login"); + return ResponseEntity.ok(authService.login(request)); + } + + @PostMapping("/register") + public ResponseEntity register(@RequestBody @Valid RegisterRequest request) { + log.info("Calling register"); + return ResponseEntity.status(201).body(authService.register(request)); + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java new file mode 100644 index 0000000..067bbae --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java @@ -0,0 +1,5 @@ +package com.smartjam.smartjamapi.dto; + +import com.smartjam.smartjamapi.enums.AvailabilityStatus; + +public record AuthResponse(String message, AvailabilityStatus status) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java new file mode 100644 index 0000000..85fd8eb --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java @@ -0,0 +1,5 @@ +package com.smartjam.smartjamapi.dto; + +import java.time.LocalDateTime; + +public record ErrorResponseDto(String message, String detailedMessage, LocalDateTime errorTime) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java new file mode 100644 index 0000000..7b94d88 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/LoginRequest.java @@ -0,0 +1,3 @@ +package com.smartjam.smartjamapi.dto; + +public record LoginRequest(String email, String password) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java new file mode 100644 index 0000000..f095506 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java @@ -0,0 +1,3 @@ +package com.smartjam.smartjamapi.dto; + +public record RegisterRequest(String username, String email, String password, String confirmPassword) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java new file mode 100644 index 0000000..28f71b7 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/AvailabilityStatus.java @@ -0,0 +1,8 @@ +package com.smartjam.smartjamapi.enums; + +public enum AvailabilityStatus { + AVAILABLE, + UNAVAILABLE, + PENDING, + BANNED +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..b8018b3 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java @@ -0,0 +1,52 @@ +package com.smartjam.smartjamapi.exception; + +import java.time.LocalDateTime; + +import jakarta.persistence.EntityNotFoundException; + +import com.smartjam.smartjamapi.dto.ErrorResponseDto; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + @ExceptionHandler(Exception.class) + public ResponseEntity handlerGenericException(Exception e) { + log.error("Handler exception", e); + + var errorDto = new ErrorResponseDto("INTERNAL_SERVER_ERROR", e.getMessage(), LocalDateTime.now()); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorDto); + } + + @ExceptionHandler(EntityNotFoundException.class) + public ResponseEntity handlerEntityNotFound(EntityNotFoundException e) { + log.error("Handler handlerEntityNotFound", e); + + var errorDto = new ErrorResponseDto("Not Found page", e.getMessage(), LocalDateTime.now()); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); + } + + @ExceptionHandler( + exception = { + IllegalArgumentException.class, + IllegalStateException.class, + MethodArgumentNotValidException.class + }) + public ResponseEntity handlerBadRequest(Exception e) { + log.error("Handler handlerBadRequest", e); + + var errorDto = new ErrorResponseDto("Bad request", e.getMessage(), LocalDateTime.now()); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDto); + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java new file mode 100644 index 0000000..a56e666 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java @@ -0,0 +1,11 @@ +package com.smartjam.smartjamapi.repository; + +import java.util.Optional; + +import com.smartjam.smartjamapi.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuthRepository extends JpaRepository { + + Optional findByEmail(String login); +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java new file mode 100644 index 0000000..690fc2c --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -0,0 +1,53 @@ +package com.smartjam.smartjamapi.service; + +import java.util.NoSuchElementException; + +import com.smartjam.smartjamapi.*; +import com.smartjam.smartjamapi.dto.AuthResponse; +import com.smartjam.smartjamapi.dto.LoginRequest; +import com.smartjam.smartjamapi.dto.RegisterRequest; +import com.smartjam.smartjamapi.enums.AvailabilityStatus; +import com.smartjam.smartjamapi.repository.AuthRepository; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +public class AuthService { + + private final AuthRepository repository; + private final PasswordEncoder passwordEncoder; + + public AuthService(AuthRepository repository, PasswordEncoder passwordEncoder) { + this.repository = repository; + this.passwordEncoder = passwordEncoder; + } + + public AuthResponse login(LoginRequest request) { + UserEntity userEntity = repository + .findByEmail(request.email()) + .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { + throw new IllegalStateException("Invalid password"); + } + + return new AuthResponse("Entrance is allowed", AvailabilityStatus.AVAILABLE); + } + + public AuthResponse register(RegisterRequest request) { + boolean exists = repository.findByEmail(request.email()).isPresent(); + + if (exists) { + throw new IllegalStateException("The account exists, try login, please"); + } + + UserEntity userEntity = new UserEntity(); + userEntity.setUsername(request.username()); + userEntity.setEmail(request.email()); + userEntity.setPasswordHash(passwordEncoder.encode(request.password())); + userEntity.setRole("USER"); + + repository.save(userEntity); + + return new AuthResponse("Registration was successful", AvailabilityStatus.AVAILABLE); + } +} From 183d3bb5eeb7171dc8c012938ea925125ff8dcee Mon Sep 17 00:00:00 2001 From: abeb Date: Thu, 5 Mar 2026 00:21:43 +0300 Subject: [PATCH 02/13] chore: add JWT dependencies and enable Lombok --- backend/smartjam-api/build.gradle | 4 +++ .../smartjamapi/SmartjamApiApplication.java | 2 -- .../com/smartjam/smartjamapi/UserEntity.java | 27 +++---------------- .../smartjamapi/dto/RegisterRequest.java | 2 +- 4 files changed, 9 insertions(+), 26 deletions(-) diff --git a/backend/smartjam-api/build.gradle b/backend/smartjam-api/build.gradle index a920021..793d171 100644 --- a/backend/smartjam-api/build.gradle +++ b/backend/smartjam-api/build.gradle @@ -3,6 +3,10 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-validation") implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.postgresql:postgresql' diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java index d327fae..7e58e78 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java @@ -1,6 +1,5 @@ package com.smartjam.smartjamapi; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -10,5 +9,4 @@ public class SmartjamApiApplication { static void main(String[] args) { SpringApplication.run(SmartjamApiApplication.class, args); } - } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java index 9edd3e7..67afa0f 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java @@ -2,11 +2,15 @@ import jakarta.persistence.*; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; @Setter @Getter +@NoArgsConstructor +@AllArgsConstructor @Table(name = "users") @Entity public class UserEntity { @@ -38,27 +42,4 @@ public class UserEntity { @Column(name = "fcm_token") private String fcmToken; - - public UserEntity() {} - - public UserEntity( - Long id, - String username, - String email, - String passwordHash, - String firstName, - String lastName, - String avatarUrl, - String role, - String fcmToken) { - this.id = id; - this.username = username; - this.email = email; - this.passwordHash = passwordHash; - this.firstName = firstName; - this.lastName = lastName; - this.avatarUrl = avatarUrl; - this.role = role; - this.fcmToken = fcmToken; - } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java index f095506..4989fa6 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java @@ -1,3 +1,3 @@ package com.smartjam.smartjamapi.dto; -public record RegisterRequest(String username, String email, String password, String confirmPassword) {} +public record RegisterRequest(String username, String email, String password) {} From 6286698451b50bc5cf58cf9da5619e03a1a672aa Mon Sep 17 00:00:00 2001 From: abeb Date: Wed, 11 Mar 2026 00:47:12 +0300 Subject: [PATCH 03/13] feat: add JWT-based authentication --- backend/smartjam-api/build.gradle | 6 +- .../smartjamapi/config/ApplicationConfig.java | 45 +++++++ .../config/CustomUserDetailsService.java | 26 ++++ .../smartjamapi/config/SecurityConfig.java | 43 ++++--- .../controller/AuthController.java | 21 +++- .../controller/MainController.java | 33 +++++ .../smartjamapi/dto/AuthResponse.java | 8 +- .../smartjam/smartjamapi/dto/TokenDto.java | 6 + .../entity/RefreshTokenEntity.java | 27 +++++ .../smartjamapi/{ => entity}/UserEntity.java | 28 ++++- .../com/smartjam/smartjamapi/enums/Role.java | 6 + .../repository/RefreshTokenRepository.java | 11 ++ ...uthRepository.java => UserRepository.java} | 10 +- .../security/JwtAuthenticationFilter.java | 64 ++++++++++ .../smartjamapi/security/JwtService.java | 93 ++++++++++++++ .../smartjamapi/security/UserDetailsImpl.java | 68 +++++++++++ .../smartjamapi/service/AuthService.java | 114 ++++++++++++++++-- .../src/main/resources/application.yaml | Bin 2244 -> 2466 bytes 18 files changed, 565 insertions(+), 44 deletions(-) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java rename backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/{ => entity}/UserEntity.java (52%) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java rename backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/{AuthRepository.java => UserRepository.java} (52%) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java diff --git a/backend/smartjam-api/build.gradle b/backend/smartjam-api/build.gradle index 793d171..15e51c5 100644 --- a/backend/smartjam-api/build.gradle +++ b/backend/smartjam-api/build.gradle @@ -3,9 +3,9 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-validation") implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java new file mode 100644 index 0000000..72af85c --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java @@ -0,0 +1,45 @@ +package com.smartjam.smartjamapi.config; + +import com.smartjam.smartjamapi.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +@RequiredArgsConstructor +public class ApplicationConfig { + + private final UserRepository userRepository; + + @Bean + public UserDetailsService userDetailsService() { + return email -> userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("Email not found")); + } + + @Bean + public AuthenticationProvider authenticationProvider() { + DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService()); + authProvider.setPasswordEncoder(passwordEncoder()); + return authProvider; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) + throws Exception { + return config.getAuthenticationManager(); + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java new file mode 100644 index 0000000..45f9014 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -0,0 +1,26 @@ +package com.smartjam.smartjamapi.config; + +import com.smartjam.smartjamapi.entity.UserEntity; +import com.smartjam.smartjamapi.repository.UserRepository; +import com.smartjam.smartjamapi.security.UserDetailsImpl; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +public class CustomUserDetailsService implements UserDetailsService { + + private final UserRepository userRepository; + + public CustomUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + public UserDetailsImpl loadUserByUsername(String email) throws UsernameNotFoundException { + UserEntity user = userRepository.findByEmail(email) + .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); + + return UserDetailsImpl.build(user); + } +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java index 840bc0a..db634ad 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java @@ -1,31 +1,44 @@ package com.smartjam.smartjamapi.config; + +import com.smartjam.smartjamapi.security.JwtAuthenticationFilter; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; +import org.springframework.security.authentication.AuthenticationProvider; +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.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration +@EnableWebSecurity +@EnableMethodSecurity +@RequiredArgsConstructor public class SecurityConfig { - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } + private final JwtAuthenticationFilter jwtAuthFilter; + private final AuthenticationProvider authenticationProvider; @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.csrf(AbstractHttpConfigurer::disable) // <--- так правильно в Spring Security 6 - .authorizeHttpRequests(auth -> auth.requestMatchers("/auth/**") - .permitAll() - .anyRequest() - .authenticated()) - .httpBasic(Customizer.withDefaults()); + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/api/admin/**").hasRole("ADMIN") +// .requestMatchers("/secured/**").authenticated().rermitAll() + .anyRequest().authenticated() + ) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authenticationProvider(authenticationProvider) + .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } -} +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java index 37bf74b..67edd8d 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -1,18 +1,18 @@ package com.smartjam.smartjamapi.controller; -import jakarta.validation.Valid; - import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; import com.smartjam.smartjamapi.dto.RegisterRequest; +import com.smartjam.smartjamapi.dto.TokenDto; import com.smartjam.smartjamapi.service.AuthService; +import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController -@RequestMapping("/auth") +@RequestMapping("/api/auth") public class AuthController { private static final Logger log = LoggerFactory.getLogger(AuthController.class); @@ -24,14 +24,25 @@ public AuthController(AuthService authService) { } @PostMapping("/login") - public ResponseEntity login(@RequestBody @Valid LoginRequest request) { + public ResponseEntity login( + @RequestBody @Valid LoginRequest request + ) { log.info("Calling login"); return ResponseEntity.ok(authService.login(request)); } @PostMapping("/register") - public ResponseEntity register(@RequestBody @Valid RegisterRequest request) { + public ResponseEntity register( + @RequestBody @Valid RegisterRequest request + ) { log.info("Calling register"); return ResponseEntity.status(201).body(authService.register(request)); } + + @PostMapping("/refresh") + public ResponseEntity getNewToken( + @RequestBody @Valid TokenDto tokenDto) { + log.info("Calling getNewToken"); + return ResponseEntity.status(201).body(authService.getNewToken(tokenDto)); + } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java new file mode 100644 index 0000000..cd72afc --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java @@ -0,0 +1,33 @@ +package com.smartjam.smartjamapi.controller; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + +@RestController +@RequestMapping("/secured") +public class MainController { + + private static final Logger log = LoggerFactory.getLogger(MainController.class); + + @GetMapping("/user") + public String userAccess(Principal principal){ + System.out.println(principal); + if (principal == null) { + return "Anonymous"; + } + + log.error("Call userAccess"); + return principal.getName(); + } + + @GetMapping ("/hello") + public String hello(){ + return "You are auth"; + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java index 067bbae..815f925 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java @@ -2,4 +2,10 @@ import com.smartjam.smartjamapi.enums.AvailabilityStatus; -public record AuthResponse(String message, AvailabilityStatus status) {} +public record AuthResponse( + String message, + AvailabilityStatus status, + String refresh_token, + String access_token +) { +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java new file mode 100644 index 0000000..4e857df --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java @@ -0,0 +1,6 @@ +package com.smartjam.smartjamapi.dto; + +public record TokenDto( + String refresh_token +) { +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java new file mode 100644 index 0000000..21d5316 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java @@ -0,0 +1,27 @@ +package com.smartjam.smartjamapi.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +import java.time.Instant; + +@Entity +@Table(name = "refresh_tokens") +@Setter +@Getter +public class RefreshTokenEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, unique = true) + private String token; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private UserEntity user; + + @Column(nullable = false, name = "expires_at") + private Instant expiresAt; +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java similarity index 52% rename from backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java rename to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java index 67afa0f..3770532 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/UserEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java @@ -1,11 +1,20 @@ -package com.smartjam.smartjamapi; +package com.smartjam.smartjamapi.entity; -import jakarta.persistence.*; +import com.smartjam.smartjamapi.enums.Role; +import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.jspecify.annotations.Nullable; +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; + @Setter @Getter @@ -13,7 +22,7 @@ @AllArgsConstructor @Table(name = "users") @Entity -public class UserEntity { +public class UserEntity implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) @@ -37,9 +46,20 @@ public class UserEntity { @Column(name = "avatar_url") private String avatarUrl; + @Enumerated(EnumType.STRING) @Column(nullable = false) - private String role; + private Role role = Role.STUDENT; @Column(name = "fcm_token") private String fcmToken; + + @Override + public Collection getAuthorities() { + return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); + } + + @Override + public @Nullable String getPassword() { + return passwordHash; + } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java new file mode 100644 index 0000000..0e1a3f7 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/Role.java @@ -0,0 +1,6 @@ +package com.smartjam.smartjamapi.enums; + +public enum Role { + STUDENT, + TEACHER +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..84ac4ed --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java @@ -0,0 +1,11 @@ +package com.smartjam.smartjamapi.repository; + +import com.smartjam.smartjamapi.entity.RefreshTokenEntity; +import com.smartjam.smartjamapi.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface RefreshTokenRepository extends JpaRepository { + Optional findByToken(String token); +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java similarity index 52% rename from backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java rename to backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java index a56e666..b7fcfbe 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/AuthRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java @@ -1,11 +1,13 @@ package com.smartjam.smartjamapi.repository; -import java.util.Optional; - -import com.smartjam.smartjamapi.UserEntity; +import com.smartjam.smartjamapi.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; -public interface AuthRepository extends JpaRepository { +import java.util.Optional; + +public interface UserRepository extends JpaRepository { Optional findByEmail(String login); + Optional findUserEntitiesByUsername(String username); + } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java new file mode 100644 index 0000000..6cf6eda --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java @@ -0,0 +1,64 @@ +package com.smartjam.smartjamapi.security; + +import com.smartjam.smartjamapi.config.CustomUserDetailsService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +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.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final CustomUserDetailsService customUserDetailsService; + + @Override + protected void doFilterInternal( + @NonNull HttpServletRequest request, + @NonNull HttpServletResponse response, + @NonNull 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); + final String email = jwtService.extractUsername(jwt); + + System.out.println("Email: " + email); + + if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); + + if (jwtService.isTokenValid(jwt, userDetails)) { + UsernamePasswordAuthenticationToken authToken = + new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + + authToken.setDetails( + new WebAuthenticationDetailsSource().buildDetails(request) + ); + + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java new file mode 100644 index 0000000..a0838f9 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java @@ -0,0 +1,93 @@ +package com.smartjam.smartjamapi.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; + +import java.security.Key; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.stereotype.Service; + +import javax.crypto.SecretKey; + + +@Service +public class JwtService { + + @Value("${security.jwt.secret-key}") + private String secretKey; + + @Getter + @Value("${security.jwt.expiration-time}") + private long jwtExpiration; + + + private Key getSigningKey() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } + + public String generateRefreshToken() { + byte[] randomBytes = new byte[64]; + new SecureRandom().nextBytes(randomBytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); + } + + public String generateAccessToken(UserDetailsImpl userDetails) { + Map claims = new HashMap<>(); + claims.put("authorities", userDetails.getAuthorities() + .stream() + .map(GrantedAuthority::getAuthority) + .toList()); + + + return Jwts.builder() + .claims(claims) + .subject(userDetails.getEmail()) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + jwtExpiration)) + .signWith(getSigningKey()) + .compact(); + } + + public String extractUsername(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(String token) { + return Jwts.parser() + .verifyWith((SecretKey) getSigningKey()) + .build() + .parseSignedClaims(token) + .getPayload(); + } + + public boolean isTokenValid(String token, UserDetailsImpl userDetails) { + final String emailFromToken = extractUsername(token); + return emailFromToken.equals(userDetails.getEmail()) + && !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java new file mode 100644 index 0000000..cb9f4ff --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java @@ -0,0 +1,68 @@ +package com.smartjam.smartjamapi.security; + +import com.smartjam.smartjamapi.entity.UserEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; + +@AllArgsConstructor + +public class UserDetailsImpl implements UserDetails { + private Long id; + private String username; + private String email; + private String password; + + public static UserDetailsImpl build(UserEntity user){ + return new UserDetailsImpl( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getPassword()); + } + + @Override + public Collection getAuthorities() { + return List.of(); + } + + @Override + public @Nullable String getPassword() { + return password; + } + + @Override + public String getUsername() { + return username; + } + + public void setId(Long id) { + this.id = id; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setEmail(String email) { + this.email = email; + } + + public void setPassword(String password) { + this.password = password; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index 690fc2c..655adb2 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -1,36 +1,79 @@ package com.smartjam.smartjamapi.service; -import java.util.NoSuchElementException; - -import com.smartjam.smartjamapi.*; import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; import com.smartjam.smartjamapi.dto.RegisterRequest; +import com.smartjam.smartjamapi.dto.TokenDto; +import com.smartjam.smartjamapi.entity.RefreshTokenEntity; +import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.enums.AvailabilityStatus; -import com.smartjam.smartjamapi.repository.AuthRepository; +import com.smartjam.smartjamapi.enums.Role; +import com.smartjam.smartjamapi.repository.RefreshTokenRepository; +import com.smartjam.smartjamapi.repository.UserRepository; +import com.smartjam.smartjamapi.security.JwtService; +import com.smartjam.smartjamapi.security.UserDetailsImpl; +import jakarta.transaction.Transactional; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import java.time.Instant; +import java.util.NoSuchElementException; + @Service public class AuthService { - private final AuthRepository repository; + private final UserRepository repository; + private final RefreshTokenRepository refreshTokenRepository; + private final PasswordEncoder passwordEncoder; + private final AuthenticationManager authenticationManager; + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + + private static final Logger log = LoggerFactory.getLogger(AuthService.class); - public AuthService(AuthRepository repository, PasswordEncoder passwordEncoder) { + public AuthService(UserRepository repository, + RefreshTokenRepository refreshTokenRepository, + PasswordEncoder passwordEncoder, + AuthenticationManager authenticationManager, + JwtService jwtService, + UserDetailsService userDetailsService) { this.repository = repository; + this.refreshTokenRepository = refreshTokenRepository; this.passwordEncoder = passwordEncoder; + this.authenticationManager = authenticationManager; + this.jwtService = jwtService; + this.userDetailsService = userDetailsService; + } public AuthResponse login(LoginRequest request) { - UserEntity userEntity = repository - .findByEmail(request.email()) - .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); + UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow( + () -> new NoSuchElementException("Login not found, try register, please") + ); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { throw new IllegalStateException("Invalid password"); } - return new AuthResponse("Entrance is allowed", AvailabilityStatus.AVAILABLE); + UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); + + String accessToken = jwtService.generateAccessToken(userDetails); + String refreshToken = jwtService.generateRefreshToken(); + + RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); + refreshTokenEntity.setToken(refreshToken); + refreshTokenEntity.setUser(userEntity); + refreshTokenEntity.setExpiresAt( + Instant.now().plusMillis(jwtService.getJwtExpiration()) + ); + + refreshTokenRepository.save(refreshTokenEntity); + + return new AuthResponse("Logged in successfully", AvailabilityStatus.AVAILABLE, refreshToken, accessToken); } public AuthResponse register(RegisterRequest request) { @@ -44,10 +87,57 @@ public AuthResponse register(RegisterRequest request) { userEntity.setUsername(request.username()); userEntity.setEmail(request.email()); userEntity.setPasswordHash(passwordEncoder.encode(request.password())); - userEntity.setRole("USER"); + userEntity.setRole(Role.STUDENT); repository.save(userEntity); - return new AuthResponse("Registration was successful", AvailabilityStatus.AVAILABLE); + + UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); + + String accessToken = jwtService.generateAccessToken(userDetails); + String refreshToken = jwtService.generateRefreshToken(); + + RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); + refreshTokenEntity.setToken(refreshToken); + refreshTokenEntity.setUser(userEntity); + refreshTokenEntity.setExpiresAt( + Instant.now().plusMillis(jwtService.getJwtExpiration()) + ); + + refreshTokenRepository.save(refreshTokenEntity); + + return new AuthResponse("Register successfully", AvailabilityStatus.AVAILABLE, refreshToken, accessToken); + } + + @Transactional + public AuthResponse getNewToken(TokenDto tokenDto) { + RefreshTokenEntity refreshToken = refreshTokenRepository.findByToken(tokenDto.refresh_token()).orElseThrow( + () -> new NoSuchElementException("Token not found, try login, please") + ); + if (refreshToken.getExpiresAt().isBefore(Instant.now())) { + throw new IllegalStateException("Refresh token expired"); + } + + UserEntity userEntity = repository.findById(refreshToken.getUser().getId()) + .orElseThrow(() -> new NoSuchElementException("User not found")); + UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); + + String accessToken = jwtService.generateAccessToken(userDetails); + String newRefreshToken = jwtService.generateRefreshToken(); + + RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); + refreshTokenEntity.setToken(newRefreshToken); + refreshTokenEntity.setUser(userEntity); + refreshTokenEntity.setExpiresAt( + Instant.now().plusMillis(jwtService.getJwtExpiration()) + ); + + refreshTokenRepository.save(refreshTokenEntity); + + return new AuthResponse( + "Token generate successfully", + AvailabilityStatus.AVAILABLE, + newRefreshToken, + accessToken); } } diff --git a/backend/smartjam-api/src/main/resources/application.yaml b/backend/smartjam-api/src/main/resources/application.yaml index 16aca50cb1b7899da50c07215c128395e42ba7fb..163bdbf3e7cb1e5c3b7e240ec18e397be080aa02 100644 GIT binary patch delta 231 zcmX|+O%B0O6otQ`n28N^0}^QwHAF;WA|!%|s8sSaMXD4Nd$9tG=)#;=KRLPY-gAG? z%PY_7D=$4L7dR2J;Y@-dxFp-L`&(H@>GW~;LkEy}(!?BS3rN6AZAYuISbUIJ_st7av5 Kw5b14eC!R!BqW~z delta 7 OcmZ1^d_-`=5e@(hp92Q~ From 185be1b5743d120aa743d8d2603b5f64d4142657 Mon Sep 17 00:00:00 2001 From: abeb Date: Wed, 11 Mar 2026 01:00:38 +0300 Subject: [PATCH 04/13] chose: style --- .../smartjamapi/config/ApplicationConfig.java | 9 ++- .../config/CustomUserDetailsService.java | 5 +- .../smartjamapi/config/SecurityConfig.java | 23 ++++---- .../controller/AuthController.java | 14 ++--- .../controller/MainController.java | 9 ++- .../smartjamapi/dto/AuthResponse.java | 8 +-- .../smartjam/smartjamapi/dto/TokenDto.java | 5 +- .../entity/RefreshTokenEntity.java | 5 +- .../smartjamapi/entity/UserEntity.java | 11 ++-- .../repository/RefreshTokenRepository.java | 5 +- .../repository/UserRepository.java | 6 +- .../security/JwtAuthenticationFilter.java | 23 +++----- .../smartjamapi/security/JwtService.java | 29 ++++------ .../smartjamapi/security/UserDetailsImpl.java | 17 ++---- .../smartjamapi/service/AuthService.java | 56 ++++++++----------- 15 files changed, 91 insertions(+), 134 deletions(-) diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java index 72af85c..3884c49 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java @@ -21,8 +21,8 @@ public class ApplicationConfig { @Bean public UserDetailsService userDetailsService() { - return email -> userRepository.findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("Email not found")); + return email -> + userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Email not found")); } @Bean @@ -33,8 +33,7 @@ public AuthenticationProvider authenticationProvider() { } @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration config) - throws Exception { + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } @@ -42,4 +41,4 @@ public AuthenticationManager authenticationManager(AuthenticationConfiguration c public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } -} \ No newline at end of file +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java index 45f9014..f47169f 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -18,9 +18,10 @@ public CustomUserDetailsService(UserRepository userRepository) { @Override public UserDetailsImpl loadUserByUsername(String email) throws UsernameNotFoundException { - UserEntity user = userRepository.findByEmail(email) + UserEntity user = userRepository + .findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); return UserDetailsImpl.build(user); } -} \ No newline at end of file +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java index db634ad..c844da3 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java @@ -1,6 +1,5 @@ package com.smartjam.smartjamapi.config; - import com.smartjam.smartjamapi.security.JwtAuthenticationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -25,20 +24,18 @@ public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(auth -> auth - .requestMatchers("/api/auth/**").permitAll() - .requestMatchers("/api/admin/**").hasRole("ADMIN") -// .requestMatchers("/secured/**").authenticated().rermitAll() - .anyRequest().authenticated() - ) - .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) + http.csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(auth -> auth.requestMatchers("/api/auth/**") + .permitAll() + .requestMatchers("/api/admin/**") + .hasRole("ADMIN") + // .requestMatchers("/secured/**").authenticated().rermitAll() + .anyRequest() + .authenticated()) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } -} \ No newline at end of file +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java index 67edd8d..936ce33 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -1,11 +1,12 @@ package com.smartjam.smartjamapi.controller; +import jakarta.validation.Valid; + import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; import com.smartjam.smartjamapi.dto.RegisterRequest; import com.smartjam.smartjamapi.dto.TokenDto; import com.smartjam.smartjamapi.service.AuthService; -import jakarta.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.ResponseEntity; @@ -24,24 +25,19 @@ public AuthController(AuthService authService) { } @PostMapping("/login") - public ResponseEntity login( - @RequestBody @Valid LoginRequest request - ) { + public ResponseEntity login(@RequestBody @Valid LoginRequest request) { log.info("Calling login"); return ResponseEntity.ok(authService.login(request)); } @PostMapping("/register") - public ResponseEntity register( - @RequestBody @Valid RegisterRequest request - ) { + public ResponseEntity register(@RequestBody @Valid RegisterRequest request) { log.info("Calling register"); return ResponseEntity.status(201).body(authService.register(request)); } @PostMapping("/refresh") - public ResponseEntity getNewToken( - @RequestBody @Valid TokenDto tokenDto) { + public ResponseEntity getNewToken(@RequestBody @Valid TokenDto tokenDto) { log.info("Calling getNewToken"); return ResponseEntity.status(201).body(authService.getNewToken(tokenDto)); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java index cd72afc..826f60d 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java @@ -1,5 +1,6 @@ package com.smartjam.smartjamapi.controller; +import java.security.Principal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,8 +8,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.security.Principal; - @RestController @RequestMapping("/secured") public class MainController { @@ -16,7 +15,7 @@ public class MainController { private static final Logger log = LoggerFactory.getLogger(MainController.class); @GetMapping("/user") - public String userAccess(Principal principal){ + public String userAccess(Principal principal) { System.out.println(principal); if (principal == null) { return "Anonymous"; @@ -26,8 +25,8 @@ public String userAccess(Principal principal){ return principal.getName(); } - @GetMapping ("/hello") - public String hello(){ + @GetMapping("/hello") + public String hello() { return "You are auth"; } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java index 815f925..b962bfa 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java @@ -2,10 +2,4 @@ import com.smartjam.smartjamapi.enums.AvailabilityStatus; -public record AuthResponse( - String message, - AvailabilityStatus status, - String refresh_token, - String access_token -) { -} +public record AuthResponse(String message, AvailabilityStatus status, String refresh_token, String access_token) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java index 4e857df..4327227 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java @@ -1,6 +1,3 @@ package com.smartjam.smartjamapi.dto; -public record TokenDto( - String refresh_token -) { -} +public record TokenDto(String refresh_token) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java index 21d5316..065cd07 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java @@ -1,11 +1,12 @@ package com.smartjam.smartjamapi.entity; +import java.time.Instant; + import jakarta.persistence.*; + import lombok.Getter; import lombok.Setter; -import java.time.Instant; - @Entity @Table(name = "refresh_tokens") @Setter diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java index 3770532..7c4fe25 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java @@ -1,8 +1,11 @@ package com.smartjam.smartjamapi.entity; +import java.util.Collection; +import java.util.List; -import com.smartjam.smartjamapi.enums.Role; import jakarta.persistence.*; + +import com.smartjam.smartjamapi.enums.Role; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -12,10 +15,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collection; -import java.util.List; - - @Setter @Getter @NoArgsConstructor @@ -55,7 +54,7 @@ public class UserEntity implements UserDetails { @Override public Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); + return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); } @Override diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java index 84ac4ed..76400b5 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java @@ -1,11 +1,10 @@ package com.smartjam.smartjamapi.repository; +import java.util.Optional; + import com.smartjam.smartjamapi.entity.RefreshTokenEntity; -import com.smartjam.smartjamapi.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface RefreshTokenRepository extends JpaRepository { Optional findByToken(String token); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java index b7fcfbe..2266028 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java @@ -1,13 +1,13 @@ package com.smartjam.smartjamapi.repository; +import java.util.Optional; + import com.smartjam.smartjamapi.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.Optional; - public interface UserRepository extends JpaRepository { Optional findByEmail(String login); - Optional findUserEntitiesByUsername(String username); + Optional findUserEntitiesByUsername(String username); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java index 6cf6eda..2959e67 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java @@ -1,10 +1,13 @@ package com.smartjam.smartjamapi.security; -import com.smartjam.smartjamapi.config.CustomUserDetailsService; +import java.io.IOException; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; + +import com.smartjam.smartjamapi.config.CustomUserDetailsService; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -13,8 +16,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; -import java.io.IOException; - @Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { @@ -26,8 +27,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain - ) throws ServletException, IOException { + @NonNull FilterChain filterChain) + throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); @@ -46,19 +47,13 @@ protected void doFilterInternal( if (jwtService.isTokenValid(jwt, userDetails)) { UsernamePasswordAuthenticationToken authToken = - new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - authToken.setDetails( - new WebAuthenticationDetailsSource().buildDetails(request) - ); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } -} \ No newline at end of file +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java index a0838f9..dbdad8e 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java @@ -1,10 +1,5 @@ package com.smartjam.smartjamapi.security; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; - import java.security.Key; import java.security.SecureRandom; import java.util.Base64; @@ -12,15 +7,17 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; +import javax.crypto.SecretKey; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Service; -import javax.crypto.SecretKey; - - @Service public class JwtService { @@ -31,7 +28,6 @@ public class JwtService { @Value("${security.jwt.expiration-time}") private long jwtExpiration; - private Key getSigningKey() { byte[] keyBytes = Decoders.BASE64.decode(secretKey); return Keys.hmacShaKeyFor(keyBytes); @@ -45,11 +41,11 @@ public String generateRefreshToken() { public String generateAccessToken(UserDetailsImpl userDetails) { Map claims = new HashMap<>(); - claims.put("authorities", userDetails.getAuthorities() - .stream() - .map(GrantedAuthority::getAuthority) - .toList()); - + claims.put( + "authorities", + userDetails.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .toList()); return Jwts.builder() .claims(claims) @@ -79,8 +75,7 @@ private Claims extractAllClaims(String token) { public boolean isTokenValid(String token, UserDetailsImpl userDetails) { final String emailFromToken = extractUsername(token); - return emailFromToken.equals(userDetails.getEmail()) - && !isTokenExpired(token); + return emailFromToken.equals(userDetails.getEmail()) && !isTokenExpired(token); } private boolean isTokenExpired(String token) { @@ -90,4 +85,4 @@ private boolean isTokenExpired(String token) { private Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } -} \ No newline at end of file +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java index cb9f4ff..5d3d853 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java @@ -1,30 +1,23 @@ package com.smartjam.smartjamapi.security; +import java.util.Collection; +import java.util.List; + import com.smartjam.smartjamapi.entity.UserEntity; import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.RequiredArgsConstructor; import org.jspecify.annotations.Nullable; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import java.util.Collection; -import java.util.List; - @AllArgsConstructor - public class UserDetailsImpl implements UserDetails { private Long id; private String username; private String email; private String password; - public static UserDetailsImpl build(UserEntity user){ - return new UserDetailsImpl( - user.getId(), - user.getUsername(), - user.getEmail(), - user.getPassword()); + public static UserDetailsImpl build(UserEntity user) { + return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPassword()); } @Override diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index 655adb2..06c178e 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -1,5 +1,10 @@ package com.smartjam.smartjamapi.service; +import java.time.Instant; +import java.util.NoSuchElementException; + +import jakarta.transaction.Transactional; + import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; import com.smartjam.smartjamapi.dto.RegisterRequest; @@ -12,7 +17,6 @@ import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.JwtService; import com.smartjam.smartjamapi.security.UserDetailsImpl; -import jakarta.transaction.Transactional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.AuthenticationManager; @@ -20,9 +24,6 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import java.time.Instant; -import java.util.NoSuchElementException; - @Service public class AuthService { @@ -36,25 +37,25 @@ public class AuthService { private static final Logger log = LoggerFactory.getLogger(AuthService.class); - public AuthService(UserRepository repository, - RefreshTokenRepository refreshTokenRepository, - PasswordEncoder passwordEncoder, - AuthenticationManager authenticationManager, - JwtService jwtService, - UserDetailsService userDetailsService) { + public AuthService( + UserRepository repository, + RefreshTokenRepository refreshTokenRepository, + PasswordEncoder passwordEncoder, + AuthenticationManager authenticationManager, + JwtService jwtService, + UserDetailsService userDetailsService) { this.repository = repository; this.refreshTokenRepository = refreshTokenRepository; this.passwordEncoder = passwordEncoder; this.authenticationManager = authenticationManager; this.jwtService = jwtService; this.userDetailsService = userDetailsService; - } public AuthResponse login(LoginRequest request) { - UserEntity userEntity = repository.findByEmail(request.email()).orElseThrow( - () -> new NoSuchElementException("Login not found, try register, please") - ); + UserEntity userEntity = repository + .findByEmail(request.email()) + .orElseThrow(() -> new NoSuchElementException("Login not found, try register, please")); if (!passwordEncoder.matches(request.password(), userEntity.getPasswordHash())) { throw new IllegalStateException("Invalid password"); } @@ -67,9 +68,7 @@ public AuthResponse login(LoginRequest request) { RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); refreshTokenEntity.setToken(refreshToken); refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt( - Instant.now().plusMillis(jwtService.getJwtExpiration()) - ); + refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); refreshTokenRepository.save(refreshTokenEntity); @@ -91,7 +90,6 @@ public AuthResponse register(RegisterRequest request) { repository.save(userEntity); - UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); String accessToken = jwtService.generateAccessToken(userDetails); @@ -100,9 +98,7 @@ public AuthResponse register(RegisterRequest request) { RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); refreshTokenEntity.setToken(refreshToken); refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt( - Instant.now().plusMillis(jwtService.getJwtExpiration()) - ); + refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); refreshTokenRepository.save(refreshTokenEntity); @@ -111,14 +107,15 @@ public AuthResponse register(RegisterRequest request) { @Transactional public AuthResponse getNewToken(TokenDto tokenDto) { - RefreshTokenEntity refreshToken = refreshTokenRepository.findByToken(tokenDto.refresh_token()).orElseThrow( - () -> new NoSuchElementException("Token not found, try login, please") - ); + RefreshTokenEntity refreshToken = refreshTokenRepository + .findByToken(tokenDto.refresh_token()) + .orElseThrow(() -> new NoSuchElementException("Token not found, try login, please")); if (refreshToken.getExpiresAt().isBefore(Instant.now())) { throw new IllegalStateException("Refresh token expired"); } - UserEntity userEntity = repository.findById(refreshToken.getUser().getId()) + UserEntity userEntity = repository + .findById(refreshToken.getUser().getId()) .orElseThrow(() -> new NoSuchElementException("User not found")); UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); @@ -128,16 +125,11 @@ public AuthResponse getNewToken(TokenDto tokenDto) { RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); refreshTokenEntity.setToken(newRefreshToken); refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt( - Instant.now().plusMillis(jwtService.getJwtExpiration()) - ); + refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); refreshTokenRepository.save(refreshTokenEntity); return new AuthResponse( - "Token generate successfully", - AvailabilityStatus.AVAILABLE, - newRefreshToken, - accessToken); + "Token generate successfully", AvailabilityStatus.AVAILABLE, newRefreshToken, accessToken); } } From 98013674f6ec15f6fb659416512ea68f9977583d Mon Sep 17 00:00:00 2001 From: abeb Date: Thu, 12 Mar 2026 19:23:46 +0300 Subject: [PATCH 05/13] chose: style + a few exceptions --- .../config/CustomUserDetailsService.java | 4 +- .../smartjamapi/config/SecurityConfig.java | 1 - .../controller/AuthController.java | 12 ++--- .../smartjamapi/dto/ErrorResponseDto.java | 9 +++- .../smartjam/smartjamapi/enums/ErrorCode.java | 7 +++ .../exception/GlobalExceptionHandler.java | 44 +++++++++++++++---- .../security/JwtAuthenticationFilter.java | 6 +-- 7 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java index f47169f..cb7f59f 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -3,6 +3,8 @@ import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.UserDetailsImpl; +import jakarta.annotation.Nullable; +import jakarta.validation.constraints.NotNull; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @@ -17,7 +19,7 @@ public CustomUserDetailsService(UserRepository userRepository) { } @Override - public UserDetailsImpl loadUserByUsername(String email) throws UsernameNotFoundException { + public UserDetailsImpl loadUserByUsername(@Nullable String email) throws UsernameNotFoundException { UserEntity user = userRepository .findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java index c844da3..1c87154 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java @@ -29,7 +29,6 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .permitAll() .requestMatchers("/api/admin/**") .hasRole("ADMIN") - // .requestMatchers("/secured/**").authenticated().rermitAll() .anyRequest() .authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java index 936ce33..43b6fa5 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -7,23 +7,19 @@ import com.smartjam.smartjamapi.dto.RegisterRequest; import com.smartjam.smartjamapi.dto.TokenDto; import com.smartjam.smartjamapi.service.AuthService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @RestController +@Slf4j @RequestMapping("/api/auth") +@RequiredArgsConstructor public class AuthController { - private static final Logger log = LoggerFactory.getLogger(AuthController.class); - private final AuthService authService; - public AuthController(AuthService authService) { - this.authService = authService; - } - @PostMapping("/login") public ResponseEntity login(@RequestBody @Valid LoginRequest request) { log.info("Calling login"); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java index 85fd8eb..a839896 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java @@ -1,5 +1,12 @@ package com.smartjam.smartjamapi.dto; +import com.smartjam.smartjamapi.enums.ErrorCode; + import java.time.LocalDateTime; -public record ErrorResponseDto(String message, String detailedMessage, LocalDateTime errorTime) {} +public record ErrorResponseDto( + ErrorCode code, + String message, + LocalDateTime errorTime +) { +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java new file mode 100644 index 0000000..6e819f6 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java @@ -0,0 +1,7 @@ +package com.smartjam.smartjamapi.enums; + +public enum ErrorCode { + INTERNAL_SERVER_ERROR, + NON_FOUND_PAGE, + BAD_REQUEST +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java index b8018b3..eedb15d 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java @@ -2,9 +2,12 @@ import java.time.LocalDateTime; +import com.smartjam.smartjamapi.enums.ErrorCode; import jakarta.persistence.EntityNotFoundException; import com.smartjam.smartjamapi.dto.ErrorResponseDto; +import lombok.extern.slf4j.Slf4j; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; @@ -12,26 +15,46 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.NoHandlerFoundException; -@ControllerAdvice +@Slf4j +@RestControllerAdvice public class GlobalExceptionHandler { - private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); - @ExceptionHandler(Exception.class) public ResponseEntity handlerGenericException(Exception e) { - log.error("Handler exception", e); + log.error("Unexpected error: ", e); - var errorDto = new ErrorResponseDto("INTERNAL_SERVER_ERROR", e.getMessage(), LocalDateTime.now()); + var errorDto = new ErrorResponseDto( + ErrorCode.INTERNAL_SERVER_ERROR, + "Internal server error", + LocalDateTime.now()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorDto); } @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity handlerEntityNotFound(EntityNotFoundException e) { - log.error("Handler handlerEntityNotFound", e); + log.warn("Entity not found: ", e); + + var errorDto = new ErrorResponseDto( + ErrorCode.NON_FOUND_PAGE, + "Requested resource not found", + LocalDateTime.now()); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); + } + + @ExceptionHandler(NoHandlerFoundException.class) + public ResponseEntity handleNoHandlerFound(NoHandlerFoundException e) { + log.warn("No handler found for request: {}", e.getRequestURL()); - var errorDto = new ErrorResponseDto("Not Found page", e.getMessage(), LocalDateTime.now()); + var errorDto = new ErrorResponseDto( + ErrorCode.NON_FOUND_PAGE, + "Requested resource not found", + LocalDateTime.now() + ); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); } @@ -43,9 +66,12 @@ public ResponseEntity handlerEntityNotFound(EntityNotFoundExce MethodArgumentNotValidException.class }) public ResponseEntity handlerBadRequest(Exception e) { - log.error("Handler handlerBadRequest", e); + log.warn("Bad request: ", e); - var errorDto = new ErrorResponseDto("Bad request", e.getMessage(), LocalDateTime.now()); + var errorDto = new ErrorResponseDto( + ErrorCode.BAD_REQUEST, + "Invalid request data", + LocalDateTime.now()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDto); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java index 2959e67..f4b2ae8 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java @@ -27,8 +27,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) - throws ServletException, IOException { + @NonNull FilterChain filterChain + ) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); @@ -40,8 +40,6 @@ protected void doFilterInternal( final String jwt = authHeader.substring(7); final String email = jwtService.extractUsername(jwt); - System.out.println("Email: " + email); - if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); From a6de26cb9d4c05c2064964656fdc7441f7dc7199 Mon Sep 17 00:00:00 2001 From: abeb Date: Fri, 13 Mar 2026 00:15:00 +0300 Subject: [PATCH 06/13] chose: spotlessApply --- .../config/CustomUserDetailsService.java | 4 +- .../smartjamapi/dto/ErrorResponseDto.java | 11 ++--- .../smartjam/smartjamapi/enums/ErrorCode.java | 5 ++- .../exception/GlobalExceptionHandler.java | 40 +++++++++---------- .../security/JwtAuthenticationFilter.java | 4 +- 5 files changed, 28 insertions(+), 36 deletions(-) diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java index cb7f59f..e1348cc 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -1,10 +1,10 @@ package com.smartjam.smartjamapi.config; +import jakarta.annotation.Nullable; + import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.UserDetailsImpl; -import jakarta.annotation.Nullable; -import jakarta.validation.constraints.NotNull; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java index a839896..f992e13 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java @@ -1,12 +1,7 @@ package com.smartjam.smartjamapi.dto; -import com.smartjam.smartjamapi.enums.ErrorCode; - import java.time.LocalDateTime; -public record ErrorResponseDto( - ErrorCode code, - String message, - LocalDateTime errorTime -) { -} +import com.smartjam.smartjamapi.enums.ErrorCode; + +public record ErrorResponseDto(ErrorCode code, String message, LocalDateTime errorTime) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java index 6e819f6..c3db6de 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java @@ -3,5 +3,6 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR, NON_FOUND_PAGE, - BAD_REQUEST -} \ No newline at end of file + BAD_REQUEST, + UNAUTHORIZED +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java index eedb15d..0ffe0ea 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java @@ -2,20 +2,17 @@ import java.time.LocalDateTime; -import com.smartjam.smartjamapi.enums.ErrorCode; import jakarta.persistence.EntityNotFoundException; import com.smartjam.smartjamapi.dto.ErrorResponseDto; +import com.smartjam.smartjamapi.enums.ErrorCode; import lombok.extern.slf4j.Slf4j; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; -import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.NoHandlerFoundException; @Slf4j @@ -26,10 +23,8 @@ public class GlobalExceptionHandler { public ResponseEntity handlerGenericException(Exception e) { log.error("Unexpected error: ", e); - var errorDto = new ErrorResponseDto( - ErrorCode.INTERNAL_SERVER_ERROR, - "Internal server error", - LocalDateTime.now()); + var errorDto = + new ErrorResponseDto(ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error", LocalDateTime.now()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorDto); } @@ -38,10 +33,8 @@ public ResponseEntity handlerGenericException(Exception e) { public ResponseEntity handlerEntityNotFound(EntityNotFoundException e) { log.warn("Entity not found: ", e); - var errorDto = new ErrorResponseDto( - ErrorCode.NON_FOUND_PAGE, - "Requested resource not found", - LocalDateTime.now()); + var errorDto = + new ErrorResponseDto(ErrorCode.NON_FOUND_PAGE, "Requested resource not found", LocalDateTime.now()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); } @@ -50,15 +43,21 @@ public ResponseEntity handlerEntityNotFound(EntityNotFoundExce public ResponseEntity handleNoHandlerFound(NoHandlerFoundException e) { log.warn("No handler found for request: {}", e.getRequestURL()); - var errorDto = new ErrorResponseDto( - ErrorCode.NON_FOUND_PAGE, - "Requested resource not found", - LocalDateTime.now() - ); + var errorDto = + new ErrorResponseDto(ErrorCode.NON_FOUND_PAGE, "Requested resource not found", LocalDateTime.now()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); } + @ExceptionHandler(ResponseStatusException.class) + public ResponseEntity handleUnauthenticated(ResponseStatusException e) { + log.warn("Unauthenticated: {}", e.getMessage()); + + var errorDto = new ErrorResponseDto(ErrorCode.UNAUTHORIZED, e.getMessage(), LocalDateTime.now()); + + return ResponseEntity.status(e.getStatusCode()).body(errorDto); + } + @ExceptionHandler( exception = { IllegalArgumentException.class, @@ -68,10 +67,7 @@ public ResponseEntity handleNoHandlerFound(NoHandlerFoundExcep public ResponseEntity handlerBadRequest(Exception e) { log.warn("Bad request: ", e); - var errorDto = new ErrorResponseDto( - ErrorCode.BAD_REQUEST, - "Invalid request data", - LocalDateTime.now()); + var errorDto = new ErrorResponseDto(ErrorCode.BAD_REQUEST, "Invalid request data", LocalDateTime.now()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDto); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java index f4b2ae8..51e72b0 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java @@ -27,8 +27,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal( @NonNull HttpServletRequest request, @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain - ) throws ServletException, IOException { + @NonNull FilterChain filterChain) + throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); From 64664f7e7bd14105ad79b28a4c932df4e5d49c3f Mon Sep 17 00:00:00 2001 From: abeb Date: Fri, 13 Mar 2026 00:25:49 +0300 Subject: [PATCH 07/13] Save work before merge --- backend/smartjam-api/build.gradle | 21 +++--- .../controller/UploadController.java | 24 +++++++ .../smartjamapi/dto/UploadRequest.java | 5 ++ .../smartjamapi/dto/UploadUrlResponse.java | 5 ++ .../smartjamapi/entity/UserEntity.java | 8 +-- .../smartjamapi/security/UserDetailsImpl.java | 32 +++------ .../smartjamapi/service/AuthService.java | 27 ++----- .../smartjamapi/service/UploadService.java | 68 ++++++++++++++++++ .../src/main/resources/application.yaml | Bin 2466 -> 2460 bytes 9 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java diff --git a/backend/smartjam-api/build.gradle b/backend/smartjam-api/build.gradle index 15e51c5..e7c1398 100644 --- a/backend/smartjam-api/build.gradle +++ b/backend/smartjam-api/build.gradle @@ -1,19 +1,20 @@ dependencies { - implementation 'org.springframework.boot:spring-boot-starter-web' + implementation('org.springframework.boot:spring-boot-starter-web') implementation("org.springframework.boot:spring-boot-starter-validation") - implementation 'org.springframework.boot:spring-boot-starter-security' + implementation('org.springframework.boot:spring-boot-starter-security') - implementation 'io.jsonwebtoken:jjwt-api:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' + implementation('io.jsonwebtoken:jjwt-api:0.12.3') + runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') + runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation('org.springframework.boot:spring-boot-starter-data-jpa') - runtimeOnly 'org.postgresql:postgresql' + runtimeOnly('org.postgresql:postgresql') - implementation 'org.springframework.boot:spring-boot-starter-kafka' - testImplementation 'org.springframework.kafka:spring-kafka-test' + implementation('org.springframework.boot:spring-boot-starter-kafka') + testImplementation('org.springframework.kafka:spring-kafka-test') + implementation('org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.2') - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.2' + implementation("software.amazon.awssdk:s3:2.29.22") } \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java new file mode 100644 index 0000000..433f54b --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java @@ -0,0 +1,24 @@ +package com.smartjam.smartjamapi.controller; + +import jakarta.validation.Valid; + +import com.smartjam.smartjamapi.dto.UploadRequest; +import com.smartjam.smartjamapi.dto.UploadUrlResponse; +import com.smartjam.smartjamapi.service.UploadService; +import lombok.AllArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AllArgsConstructor +public class UploadController { + + UploadService uploadService; + + @PostMapping("/upload-url") + public ResponseEntity getUploadUrl(@RequestBody @Valid UploadRequest request) { + return ResponseEntity.ok(uploadService.generateUploadUrl(request.fileName())); + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java new file mode 100644 index 0000000..897b9bf --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java @@ -0,0 +1,5 @@ +package com.smartjam.smartjamapi.dto; + +import jakarta.validation.constraints.NotNull; + +public record UploadRequest(@NotNull String fileName) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java new file mode 100644 index 0000000..6e07238 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java @@ -0,0 +1,5 @@ +package com.smartjam.smartjamapi.dto; + +import jakarta.validation.constraints.NotNull; + +public record UploadUrlResponse(@NotNull String uploadUrl) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java index 7c4fe25..d107ea8 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java @@ -10,7 +10,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.jspecify.annotations.Nullable; +import org.jspecify.annotations.NonNull; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -27,7 +27,7 @@ public class UserEntity implements UserDetails { @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; - @Column(nullable = false, unique = true) + @Column(nullable = false) private String username; @Column(nullable = false, unique = true) @@ -53,12 +53,12 @@ public class UserEntity implements UserDetails { private String fcmToken; @Override - public Collection getAuthorities() { + public @NonNull Collection getAuthorities() { return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); } @Override - public @Nullable String getPassword() { + public @NonNull String getPassword() { return passwordHash; } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java index 5d3d853..166f7e8 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java @@ -5,15 +5,23 @@ import com.smartjam.smartjamapi.entity.UserEntity; import lombok.AllArgsConstructor; +import lombok.Getter; +import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @AllArgsConstructor +@NullMarked public class UserDetailsImpl implements UserDetails { + @Getter private Long id; + private String username; + + @Getter private String email; + private String password; public static UserDetailsImpl build(UserEntity user) { @@ -34,28 +42,4 @@ public Collection getAuthorities() { public String getUsername() { return username; } - - public void setId(Long id) { - this.id = id; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setEmail(String email) { - this.email = email; - } - - public void setPassword(String password) { - this.password = password; - } - - public Long getId() { - return id; - } - - public String getEmail() { - return email; - } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index 06c178e..f08756e 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -17,40 +17,21 @@ import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.JwtService; import com.smartjam.smartjamapi.security.UserDetailsImpl; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.core.userdetails.UserDetailsService; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @Service +@AllArgsConstructor +@Slf4j public class AuthService { private final UserRepository repository; private final RefreshTokenRepository refreshTokenRepository; private final PasswordEncoder passwordEncoder; - private final AuthenticationManager authenticationManager; private final JwtService jwtService; - private final UserDetailsService userDetailsService; - - private static final Logger log = LoggerFactory.getLogger(AuthService.class); - - public AuthService( - UserRepository repository, - RefreshTokenRepository refreshTokenRepository, - PasswordEncoder passwordEncoder, - AuthenticationManager authenticationManager, - JwtService jwtService, - UserDetailsService userDetailsService) { - this.repository = repository; - this.refreshTokenRepository = refreshTokenRepository; - this.passwordEncoder = passwordEncoder; - this.authenticationManager = authenticationManager; - this.jwtService = jwtService; - this.userDetailsService = userDetailsService; - } public AuthResponse login(LoginRequest request) { UserEntity userEntity = repository diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java new file mode 100644 index 0000000..6c73694 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java @@ -0,0 +1,68 @@ +package com.smartjam.smartjamapi.service; + +import java.net.URI; +import java.net.URL; +import java.time.Duration; + +import jakarta.annotation.PostConstruct; + +import com.smartjam.smartjamapi.dto.UploadUrlResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.S3Configuration; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.presigner.S3Presigner; +import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest; + +@Service +@Slf4j +public class UploadService { + + private static final String BUCKET_NAME = "music"; + private static final String S3_ENDPOINT = "http://localhost:9000"; // пока захардкодил ссылку + + @PostConstruct + public void init() { + try (S3Client s3Client = S3Client.builder() + .endpointOverride(URI.create(S3_ENDPOINT)) + .region(Region.AP_EAST_1) + .serviceConfiguration(b -> b.pathStyleAccessEnabled(true)) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin", "minioadmin"))) + .build()) { + log.info("Trying to connect to MinIO at {}", s3Client); + if (s3Client.listBuckets().buckets().stream() + .noneMatch(b -> b.name().equals(BUCKET_NAME))) { + s3Client.createBucket(b -> b.bucket(BUCKET_NAME)); + log.info("Bucket '{}' created successfully", BUCKET_NAME); + } else { + log.info("Bucket '{}' already exists", BUCKET_NAME); + } + } + } + + public UploadUrlResponse generateUploadUrl(String fileName) { + try (S3Presigner presigner = S3Presigner.builder() + .endpointOverride(URI.create(S3_ENDPOINT)) + .region(Region.US_EAST_1) + .serviceConfiguration( + S3Configuration.builder().pathStyleAccessEnabled(true).build()) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin", "minioadmin"))) + .build()) { + + PutObjectRequest putObjectRequest = + PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName).build(); + + PresignedPutObjectRequest presignedGetObjectRequest = presigner.presignPutObject( + r -> r.putObjectRequest(putObjectRequest).signatureDuration(Duration.ofMinutes(10))); + + URL presignedUtl = presignedGetObjectRequest.url(); + return new UploadUrlResponse(presignedUtl.toString()); + } + } +} diff --git a/backend/smartjam-api/src/main/resources/application.yaml b/backend/smartjam-api/src/main/resources/application.yaml index 163bdbf3e7cb1e5c3b7e240ec18e397be080aa02..d5b1f49847e0cb684f20db54658a5ca03a335595 100644 GIT binary patch delta 11 TcmZ1^JV$uLNsh^ Date: Fri, 13 Mar 2026 01:22:12 +0300 Subject: [PATCH 08/13] Update SmartjamApiApplication.java No comments --- .../java/com/smartjam/smartjamapi/SmartjamApiApplication.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java index 7e58e78..bff17ed 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/SmartjamApiApplication.java @@ -6,7 +6,7 @@ @SpringBootApplication public class SmartjamApiApplication { - static void main(String[] args) { + public static void main(String[] args) { SpringApplication.run(SmartjamApiApplication.class, args); } } From e1ca78a78141e0d125ecf33540c444a00e495d2d Mon Sep 17 00:00:00 2001 From: abeb Date: Fri, 13 Mar 2026 15:13:53 +0300 Subject: [PATCH 09/13] fix: uuid generation (this was painful) --- .../smartjamapi/config/ApplicationConfig.java | 23 +------- .../config/CustomUserDetailsService.java | 8 +-- .../smartjamapi/config/SecurityConfig.java | 5 -- .../controller/AuthController.java | 6 +- .../controller/MainController.java | 13 ++--- .../controller/UploadController.java | 2 +- .../smartjamapi/dto/AuthResponse.java | 7 ++- .../smartjamapi/dto/ErrorResponseDto.java | 2 + .../smartjamapi/dto/RefreshTokenRequest.java | 6 ++ .../smartjam/smartjamapi/dto/TokenDto.java | 3 - .../smartjamapi/dto/UploadRequest.java | 4 +- .../smartjamapi/dto/UploadUrlResponse.java | 4 +- .../entity/RefreshTokenEntity.java | 5 +- .../smartjamapi/entity/UserEntity.java | 31 +++++----- .../smartjam/smartjamapi/enums/ErrorCode.java | 2 +- .../exception/GlobalExceptionHandler.java | 57 ++++++++----------- .../repository/UserRepository.java | 7 +++ .../security/JwtAuthenticationFilter.java | 3 + .../smartjamapi/security/UserDetailsImpl.java | 5 +- .../smartjamapi/service/AuthService.java | 10 ++-- .../smartjamapi/service/UploadService.java | 6 +- 21 files changed, 91 insertions(+), 118 deletions(-) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java delete mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java index 3884c49..d674051 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/ApplicationConfig.java @@ -1,15 +1,9 @@ package com.smartjam.smartjamapi.config; -import com.smartjam.smartjamapi.repository.UserRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @@ -17,24 +11,11 @@ @RequiredArgsConstructor public class ApplicationConfig { - private final UserRepository userRepository; + private final CustomUserDetailsService customUserDetailsService; @Bean public UserDetailsService userDetailsService() { - return email -> - userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("Email not found")); - } - - @Bean - public AuthenticationProvider authenticationProvider() { - DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(userDetailsService()); - authProvider.setPasswordEncoder(passwordEncoder()); - return authProvider; - } - - @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { - return config.getAuthenticationManager(); + return customUserDetailsService::loadUserByUsername; } @Bean diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java index e1348cc..1bd700e 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -5,24 +5,22 @@ import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.UserDetailsImpl; +import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; - public CustomUserDetailsService(UserRepository userRepository) { - this.userRepository = userRepository; - } - @Override public UserDetailsImpl loadUserByUsername(@Nullable String email) throws UsernameNotFoundException { UserEntity user = userRepository .findByEmail(email) - .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + email)); + .orElseThrow(() -> new UsernameNotFoundException("Invalid credentials")); return UserDetailsImpl.build(user); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java index 1c87154..07351fa 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/SecurityConfig.java @@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationProvider; 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.configuration.EnableWebSecurity; @@ -20,19 +19,15 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; - private final AuthenticationProvider authenticationProvider; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth.requestMatchers("/api/auth/**") .permitAll() - .requestMatchers("/api/admin/**") - .hasRole("ADMIN") .anyRequest() .authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authenticationProvider(authenticationProvider) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java index 43b6fa5..1ae2b8f 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -4,8 +4,8 @@ import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; +import com.smartjam.smartjamapi.dto.RefreshTokenRequest; import com.smartjam.smartjamapi.dto.RegisterRequest; -import com.smartjam.smartjamapi.dto.TokenDto; import com.smartjam.smartjamapi.service.AuthService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -33,8 +33,8 @@ public ResponseEntity register(@RequestBody @Valid RegisterRequest } @PostMapping("/refresh") - public ResponseEntity getNewToken(@RequestBody @Valid TokenDto tokenDto) { + public ResponseEntity getNewToken(@RequestBody @Valid RefreshTokenRequest refreshTokenRequest) { log.info("Calling getNewToken"); - return ResponseEntity.status(201).body(authService.getNewToken(tokenDto)); + return ResponseEntity.status(201).body(authService.getNewToken(refreshTokenRequest)); } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java index 826f60d..5e7b812 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java @@ -2,26 +2,21 @@ import java.security.Principal; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/secured") +@Slf4j public class MainController { - private static final Logger log = LoggerFactory.getLogger(MainController.class); - @GetMapping("/user") public String userAccess(Principal principal) { - System.out.println(principal); - if (principal == null) { - return "Anonymous"; - } + log.info(principal.getName()); - log.error("Call userAccess"); + log.info("Call userAccess"); return principal.getName(); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java index 433f54b..3026563 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/UploadController.java @@ -15,7 +15,7 @@ @AllArgsConstructor public class UploadController { - UploadService uploadService; + private final UploadService uploadService; @PostMapping("/upload-url") public ResponseEntity getUploadUrl(@RequestBody @Valid UploadRequest request) { diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java index b962bfa..84694ac 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java @@ -1,5 +1,10 @@ package com.smartjam.smartjamapi.dto; +import com.fasterxml.jackson.annotation.JsonProperty; import com.smartjam.smartjamapi.enums.AvailabilityStatus; -public record AuthResponse(String message, AvailabilityStatus status, String refresh_token, String access_token) {} +public record AuthResponse( + String message, + AvailabilityStatus status, + @JsonProperty("refresh_token") String refreshToken, + @JsonProperty("access_token") String accessToken) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java index f992e13..a90110b 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/ErrorResponseDto.java @@ -3,5 +3,7 @@ import java.time.LocalDateTime; import com.smartjam.smartjamapi.enums.ErrorCode; +import lombok.Builder; +@Builder public record ErrorResponseDto(ErrorCode code, String message, LocalDateTime errorTime) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java new file mode 100644 index 0000000..dd15e44 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java @@ -0,0 +1,6 @@ +package com.smartjam.smartjamapi.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record RefreshTokenRequest( + @JsonProperty("refresh_token") String refreshToken) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java deleted file mode 100644 index 4327227..0000000 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/TokenDto.java +++ /dev/null @@ -1,3 +0,0 @@ -package com.smartjam.smartjamapi.dto; - -public record TokenDto(String refresh_token) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java index 897b9bf..09b43a8 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadRequest.java @@ -1,5 +1,5 @@ package com.smartjam.smartjamapi.dto; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotBlank; -public record UploadRequest(@NotNull String fileName) {} +public record UploadRequest(@NotBlank String fileName) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java index 6e07238..e199325 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/UploadUrlResponse.java @@ -1,5 +1,3 @@ package com.smartjam.smartjamapi.dto; -import jakarta.validation.constraints.NotNull; - -public record UploadUrlResponse(@NotNull String uploadUrl) {} +public record UploadUrlResponse(String uploadUrl) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java index 065cd07..b6ea770 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java @@ -1,6 +1,7 @@ package com.smartjam.smartjamapi.entity; import java.time.Instant; +import java.util.UUID; import jakarta.persistence.*; @@ -13,8 +14,8 @@ @Getter public class RefreshTokenEntity { @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; @Column(nullable = false, unique = true) private String token; diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java index d107ea8..5dac90b 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java @@ -1,7 +1,6 @@ package com.smartjam.smartjamapi.entity; -import java.util.Collection; -import java.util.List; +import java.util.UUID; import jakarta.persistence.*; @@ -10,10 +9,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.jspecify.annotations.NonNull; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; @Setter @Getter @@ -21,11 +16,11 @@ @AllArgsConstructor @Table(name = "users") @Entity -public class UserEntity implements UserDetails { +public class UserEntity { @Id - @GeneratedValue(strategy = GenerationType.SEQUENCE) - private Long id; + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; @Column(nullable = false) private String username; @@ -52,13 +47,13 @@ public class UserEntity implements UserDetails { @Column(name = "fcm_token") private String fcmToken; - @Override - public @NonNull Collection getAuthorities() { - return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); - } - - @Override - public @NonNull String getPassword() { - return passwordHash; - } + // @Override + // public @NonNull Collection getAuthorities() { + // return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); + // } + // + // @Override + // public @NonNull String getPassword() { + // return passwordHash; + // } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java index c3db6de..e4bf924 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java @@ -2,7 +2,7 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR, - NON_FOUND_PAGE, + NOT_FOUND, BAD_REQUEST, UNAUTHORIZED } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java index 0ffe0ea..aedfe73 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java @@ -12,63 +12,52 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import org.springframework.web.server.ResponseStatusException; -import org.springframework.web.servlet.NoHandlerFoundException; + +// TODO: Это базовый шаблон для обработки ошибок +// TODO: Возможно, некоторые исключения ловятся неправильно +// TODO: Нужно будет потом уточнить маппинг (какое исключение -> какой статус) @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { + private ResponseEntity buildResponse(HttpStatus status, ErrorCode errorCode, String message) { + + var dto = ErrorResponseDto.builder() + .code(errorCode) + .message(message) + .errorTime(LocalDateTime.now()) + .build(); + + return ResponseEntity.status(status).body(dto); + } + @ExceptionHandler(Exception.class) public ResponseEntity handlerGenericException(Exception e) { log.error("Unexpected error: ", e); - var errorDto = - new ErrorResponseDto(ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error", LocalDateTime.now()); - - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorDto); + return buildResponse( + HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error"); } @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity handlerEntityNotFound(EntityNotFoundException e) { log.warn("Entity not found: ", e); - var errorDto = - new ErrorResponseDto(ErrorCode.NON_FOUND_PAGE, "Requested resource not found", LocalDateTime.now()); - - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); + return buildResponse(HttpStatus.NOT_FOUND, ErrorCode.NOT_FOUND, "Requested resource not found"); } - @ExceptionHandler(NoHandlerFoundException.class) - public ResponseEntity handleNoHandlerFound(NoHandlerFoundException e) { - log.warn("No handler found for request: {}", e.getRequestURL()); - - var errorDto = - new ErrorResponseDto(ErrorCode.NON_FOUND_PAGE, "Requested resource not found", LocalDateTime.now()); - - return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorDto); - } - - @ExceptionHandler(ResponseStatusException.class) - public ResponseEntity handleUnauthenticated(ResponseStatusException e) { + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleUnauthenticated(IllegalArgumentException e) { log.warn("Unauthenticated: {}", e.getMessage()); - var errorDto = new ErrorResponseDto(ErrorCode.UNAUTHORIZED, e.getMessage(), LocalDateTime.now()); - - return ResponseEntity.status(e.getStatusCode()).body(errorDto); + return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Unauthenticated"); } - @ExceptionHandler( - exception = { - IllegalArgumentException.class, - IllegalStateException.class, - MethodArgumentNotValidException.class - }) + @ExceptionHandler(exception = {IllegalStateException.class, MethodArgumentNotValidException.class}) public ResponseEntity handlerBadRequest(Exception e) { log.warn("Bad request: ", e); - var errorDto = new ErrorResponseDto(ErrorCode.BAD_REQUEST, "Invalid request data", LocalDateTime.now()); - - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorDto); + return buildResponse(HttpStatus.BAD_REQUEST, ErrorCode.BAD_REQUEST, "Invalid request data"); } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java index 2266028..e001e7a 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java @@ -1,6 +1,7 @@ package com.smartjam.smartjamapi.repository; import java.util.Optional; +import java.util.UUID; import com.smartjam.smartjamapi.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; @@ -9,5 +10,11 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String login); + Optional findById(UUID id); + Optional findUserEntitiesByUsername(String username); + + boolean existsByEmail(String email); + + boolean existsByUsername(String username); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java index 51e72b0..f9d7422 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java @@ -10,6 +10,7 @@ import com.smartjam.smartjamapi.config.CustomUserDetailsService; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; @@ -18,6 +19,7 @@ @Component @RequiredArgsConstructor +@Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtService jwtService; @@ -40,6 +42,7 @@ protected void doFilterInternal( final String jwt = authHeader.substring(7); final String email = jwtService.extractUsername(jwt); + log.info("Filter for {}", email); if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java index 166f7e8..a979ed3 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.List; +import java.util.UUID; import com.smartjam.smartjamapi.entity.UserEntity; import lombok.AllArgsConstructor; @@ -15,7 +16,7 @@ @NullMarked public class UserDetailsImpl implements UserDetails { @Getter - private Long id; + private UUID id; private String username; @@ -25,7 +26,7 @@ public class UserDetailsImpl implements UserDetails { private String password; public static UserDetailsImpl build(UserEntity user) { - return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPassword()); + return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPasswordHash()); } @Override diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index f08756e..4f18056 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -7,8 +7,8 @@ import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; +import com.smartjam.smartjamapi.dto.RefreshTokenRequest; import com.smartjam.smartjamapi.dto.RegisterRequest; -import com.smartjam.smartjamapi.dto.TokenDto; import com.smartjam.smartjamapi.entity.RefreshTokenEntity; import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.enums.AvailabilityStatus; @@ -33,6 +33,7 @@ public class AuthService { private final PasswordEncoder passwordEncoder; private final JwtService jwtService; + @Transactional public AuthResponse login(LoginRequest request) { UserEntity userEntity = repository .findByEmail(request.email()) @@ -57,9 +58,8 @@ public AuthResponse login(LoginRequest request) { } public AuthResponse register(RegisterRequest request) { - boolean exists = repository.findByEmail(request.email()).isPresent(); - if (exists) { + if (repository.existsByEmail(request.email())) { throw new IllegalStateException("The account exists, try login, please"); } @@ -87,9 +87,9 @@ public AuthResponse register(RegisterRequest request) { } @Transactional - public AuthResponse getNewToken(TokenDto tokenDto) { + public AuthResponse getNewToken(RefreshTokenRequest refreshTokenRequest) { RefreshTokenEntity refreshToken = refreshTokenRepository - .findByToken(tokenDto.refresh_token()) + .findByToken(refreshTokenRequest.refreshToken()) .orElseThrow(() -> new NoSuchElementException("Token not found, try login, please")); if (refreshToken.getExpiresAt().isBefore(Instant.now())) { throw new IllegalStateException("Refresh token expired"); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java index 6c73694..83a2cf0 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java @@ -29,10 +29,10 @@ public class UploadService { public void init() { try (S3Client s3Client = S3Client.builder() .endpointOverride(URI.create(S3_ENDPOINT)) - .region(Region.AP_EAST_1) + .region(Region.US_EAST_1) .serviceConfiguration(b -> b.pathStyleAccessEnabled(true)) - .credentialsProvider( - StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin", "minioadmin"))) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create("minioadmin", "minioadmin"))) // пока захардкодил .build()) { log.info("Trying to connect to MinIO at {}", s3Client); if (s3Client.listBuckets().buckets().stream() From eef6d70c84fd451c7fdf9e5e45b620c0a8684369 Mon Sep 17 00:00:00 2001 From: abeb Date: Sat, 21 Mar 2026 00:40:49 +0300 Subject: [PATCH 10/13] fix: responded to comments --- backend/smartjam-api/build.gradle | 23 +++-- .../config/CustomUserDetailsService.java | 5 +- .../controller/AuthController.java | 4 +- .../controller/MainController.java | 5 +- .../smartjamapi/dto/AuthResponse.java | 3 - .../smartjamapi/dto/RefreshTokenRequest.java | 3 + .../smartjamapi/dto/RegisterRequest.java | 2 +- .../entity/RefreshTokenEntity.java | 14 ++- .../smartjamapi/entity/UserEntity.java | 13 ++- .../smartjam/smartjamapi/enums/ErrorCode.java | 3 +- .../smartjamapi/enums/StatusRefreshToken.java | 7 ++ .../exception/GlobalExceptionHandler.java | 34 +++++- .../exception/SecurityException.java | 9 ++ .../exception/TokenExpiredException.java | 9 ++ .../exception/TokenNotFoundException.java | 9 ++ .../smartjamapi/mapper/UserMapper.java | 9 ++ .../repository/RefreshTokenRepository.java | 29 +++++- .../repository/UserRepository.java | 7 +- .../security/JwtAuthenticationFilter.java | 2 +- .../smartjamapi/security/JwtService.java | 7 +- .../security/RefreshTokenService.java | 55 ++++++++++ .../smartjamapi/security/UserDetailsImpl.java | 10 +- .../smartjamapi/service/AuthService.java | 97 ++++++++++++------ .../smartjamapi/service/UploadService.java | 70 ++++++++----- .../src/main/resources/application.yaml | Bin 2460 -> 2552 bytes 25 files changed, 322 insertions(+), 107 deletions(-) create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/StatusRefreshToken.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenExpiredException.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java create mode 100644 backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java diff --git a/backend/smartjam-api/build.gradle b/backend/smartjam-api/build.gradle index e7c1398..0b6206b 100644 --- a/backend/smartjam-api/build.gradle +++ b/backend/smartjam-api/build.gradle @@ -1,20 +1,23 @@ dependencies { - implementation('org.springframework.boot:spring-boot-starter-web') - implementation("org.springframework.boot:spring-boot-starter-validation") - implementation('org.springframework.boot:spring-boot-starter-security') + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation "org.springframework.boot:spring-boot-starter-validation" + implementation 'org.springframework.boot:spring-boot-starter-security' - implementation('io.jsonwebtoken:jjwt-api:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-impl:0.12.3') - runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.12.3') + implementation 'io.jsonwebtoken:jjwt-api:0.13.0' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0' - implementation('org.springframework.boot:spring-boot-starter-data-jpa') + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly('org.postgresql:postgresql') - implementation('org.springframework.boot:spring-boot-starter-kafka') - testImplementation('org.springframework.kafka:spring-kafka-test') + implementation 'org.springframework.boot:spring-boot-starter-kafka' + testImplementation 'org.springframework.kafka:spring-kafka-test' - implementation('org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.2') + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.2' implementation("software.amazon.awssdk:s3:2.29.22") } \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java index 1bd700e..2d1044a 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -1,10 +1,9 @@ package com.smartjam.smartjamapi.config; -import jakarta.annotation.Nullable; - import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.UserDetailsImpl; +import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -17,7 +16,7 @@ public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override - public UserDetailsImpl loadUserByUsername(@Nullable String email) throws UsernameNotFoundException { + public UserDetailsImpl loadUserByUsername(@NotBlank String email) throws UsernameNotFoundException { UserEntity user = userRepository .findByEmail(email) .orElseThrow(() -> new UsernameNotFoundException("Invalid credentials")); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java index 1ae2b8f..a4eef23 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -33,8 +33,8 @@ public ResponseEntity register(@RequestBody @Valid RegisterRequest } @PostMapping("/refresh") - public ResponseEntity getNewToken(@RequestBody @Valid RefreshTokenRequest refreshTokenRequest) { + public ResponseEntity getNewTokens(@RequestBody @Valid RefreshTokenRequest refreshTokenRequest) { log.info("Calling getNewToken"); - return ResponseEntity.status(201).body(authService.getNewToken(refreshTokenRequest)); + return ResponseEntity.ok(authService.getNewTokens(refreshTokenRequest)); } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java index 5e7b812..567d737 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java @@ -3,6 +3,8 @@ import java.security.Principal; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Role; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -16,11 +18,12 @@ public class MainController { public String userAccess(Principal principal) { log.info(principal.getName()); - log.info("Call userAccess"); + log.info("userAccess called for: {}", principal.getName()); return principal.getName(); } @GetMapping("/hello") +// @PreAuthorize() public String hello() { return "You are auth"; } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java index 84694ac..852d4b7 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/AuthResponse.java @@ -1,10 +1,7 @@ package com.smartjam.smartjamapi.dto; import com.fasterxml.jackson.annotation.JsonProperty; -import com.smartjam.smartjamapi.enums.AvailabilityStatus; public record AuthResponse( - String message, - AvailabilityStatus status, @JsonProperty("refresh_token") String refreshToken, @JsonProperty("access_token") String accessToken) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java index dd15e44..acb5585 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java @@ -1,6 +1,9 @@ package com.smartjam.smartjamapi.dto; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; + public record RefreshTokenRequest( + @NotBlank @JsonProperty("refresh_token") String refreshToken) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java index 4989fa6..c716a21 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RegisterRequest.java @@ -1,3 +1,3 @@ package com.smartjam.smartjamapi.dto; -public record RegisterRequest(String username, String email, String password) {} +public record RegisterRequest(String email, String nickname, String password) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java index b6ea770..3aa9f49 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java @@ -3,22 +3,23 @@ import java.time.Instant; import java.util.UUID; +import com.smartjam.smartjamapi.enums.StatusRefreshToken; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.Data; +import lombok.NoArgsConstructor; @Entity @Table(name = "refresh_tokens") -@Setter -@Getter +@Data +@NoArgsConstructor public class RefreshTokenEntity { @Id @GeneratedValue(strategy = GenerationType.UUID) private UUID id; @Column(nullable = false, unique = true) - private String token; + private String tokenHash; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) @@ -26,4 +27,7 @@ public class RefreshTokenEntity { @Column(nullable = false, name = "expires_at") private Instant expiresAt; + + @Column(nullable = false) + private StatusRefreshToken status; } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java index 5dac90b..56ff969 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/UserEntity.java @@ -1,5 +1,6 @@ package com.smartjam.smartjamapi.entity; +import java.time.Instant; import java.util.UUID; import jakarta.persistence.*; @@ -9,6 +10,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; @Setter @Getter @@ -23,7 +26,7 @@ public class UserEntity { private UUID id; @Column(nullable = false) - private String username; + private String nickname; @Column(nullable = false, unique = true) private String email; @@ -47,6 +50,14 @@ public class UserEntity { @Column(name = "fcm_token") private String fcmToken; + @CreationTimestamp + @Column(name = "created_at", nullable = false, updatable = false) + private Instant createdAt; + + @UpdateTimestamp + @Column(name = "updated_at", nullable = false) + private Instant updatedAt; + // @Override // public @NonNull Collection getAuthorities() { // return List.of(new SimpleGrantedAuthority("ROLE_" + role.name())); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java index e4bf924..13bb24d 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/ErrorCode.java @@ -4,5 +4,6 @@ public enum ErrorCode { INTERNAL_SERVER_ERROR, NOT_FOUND, BAD_REQUEST, - UNAUTHORIZED + UNAUTHORIZED, + RESOURCE_NOT_FOUND } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/StatusRefreshToken.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/StatusRefreshToken.java new file mode 100644 index 0000000..f7e9f5b --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/enums/StatusRefreshToken.java @@ -0,0 +1,7 @@ +package com.smartjam.smartjamapi.enums; + +public enum StatusRefreshToken { + ACTIVE, + USED, + REVOKED +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java index aedfe73..ad3c668 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package com.smartjam.smartjamapi.exception; import java.time.LocalDateTime; +import java.util.NoSuchElementException; import jakarta.persistence.EntityNotFoundException; @@ -9,9 +10,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; // TODO: Это базовый шаблон для обработки ошибок // TODO: Возможно, некоторые исключения ловятся неправильно @@ -40,6 +44,19 @@ public ResponseEntity handlerGenericException(Exception e) { HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.INTERNAL_SERVER_ERROR, "Internal server error"); } + @ExceptionHandler(UsernameNotFoundException.class) + public ResponseEntity handleUsernameNotFound(UsernameNotFoundException e) { + log.warn("Authentication failed"); + return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Invalid credentials"); + } + + @ExceptionHandler(NoSuchElementException.class) + public ResponseEntity handleNoSuchElement(NoSuchElementException e) { + log.warn("Resource not found: {}", e.getMessage()); + return buildResponse(HttpStatus.NOT_FOUND, ErrorCode.NOT_FOUND, "Requested resource not found"); + } + + @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity handlerEntityNotFound(EntityNotFoundException e) { log.warn("Entity not found: ", e); @@ -47,17 +64,28 @@ public ResponseEntity handlerEntityNotFound(EntityNotFoundExce return buildResponse(HttpStatus.NOT_FOUND, ErrorCode.NOT_FOUND, "Requested resource not found"); } - @ExceptionHandler(IllegalArgumentException.class) - public ResponseEntity handleUnauthenticated(IllegalArgumentException e) { + @ExceptionHandler({AuthenticationException.class, IllegalStateException.class}) + public ResponseEntity handleAuthException(AuthenticationException e) { log.warn("Unauthenticated: {}", e.getMessage()); return buildResponse(HttpStatus.UNAUTHORIZED, ErrorCode.UNAUTHORIZED, "Unauthenticated"); } - @ExceptionHandler(exception = {IllegalStateException.class, MethodArgumentNotValidException.class}) + @ExceptionHandler(exception = {IllegalArgumentException.class, MethodArgumentNotValidException.class}) public ResponseEntity handlerBadRequest(Exception e) { log.warn("Bad request: ", e); return buildResponse(HttpStatus.BAD_REQUEST, ErrorCode.BAD_REQUEST, "Invalid request data"); } + + @ExceptionHandler(NoResourceFoundException.class) + public ResponseEntity handleResourceNotFound(Exception e) { + log.warn("Resource not found: {}", e.getMessage()); + + return buildResponse(HttpStatus.NOT_FOUND, + ErrorCode.RESOURCE_NOT_FOUND, + "The requested resource was not found" + ); + } + } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java new file mode 100644 index 0000000..892140a --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/SecurityException.java @@ -0,0 +1,9 @@ +package com.smartjam.smartjamapi.exception; + +import org.springframework.security.core.AuthenticationException; + +public class SecurityException extends AuthenticationException { + public SecurityException(String message) { + super(message); + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenExpiredException.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenExpiredException.java new file mode 100644 index 0000000..3aabce1 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenExpiredException.java @@ -0,0 +1,9 @@ +package com.smartjam.smartjamapi.exception; + +import org.springframework.security.core.AuthenticationException; + +public class TokenExpiredException extends AuthenticationException { + public TokenExpiredException(String message) { + super(message); + } +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java new file mode 100644 index 0000000..024449a --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java @@ -0,0 +1,9 @@ +package com.smartjam.smartjamapi.exception; + +import org.springframework.security.core.AuthenticationException; + +public class TokenNotFoundException extends AuthenticationException { + public TokenNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java new file mode 100644 index 0000000..5d03883 --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java @@ -0,0 +1,9 @@ +package com.smartjam.smartjamapi.mapper; + +import com.smartjam.smartjamapi.dto.RegisterRequest; +import com.smartjam.smartjamapi.entity.UserEntity; +import org.mapstruct.Mapper; +@Mapper(componentModel = "spring") +public interface UserMapper { + UserEntity toEntity(RegisterRequest request); +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java index 76400b5..d17166e 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java @@ -1,10 +1,35 @@ package com.smartjam.smartjamapi.repository; import java.util.Optional; +import java.util.UUID; import com.smartjam.smartjamapi.entity.RefreshTokenEntity; +import com.smartjam.smartjamapi.entity.UserEntity; +import com.smartjam.smartjamapi.enums.StatusRefreshToken; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; + +public interface RefreshTokenRepository extends JpaRepository { + Optional findByTokenHash(String token); + + @Transactional + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("UPDATE RefreshTokenEntity r SET r.status = :status WHERE r.tokenHash = :tokenHash") + void setStatusByRefreshToken(@Param("tokenHash") String tokenHash,@Param("status") StatusRefreshToken status); + + @Modifying + @Transactional + @Query(""" + UPDATE RefreshTokenEntity r + SET r.status = :newStatus + WHERE r.user = :user + AND r.status = :currentStatus +""") + void setStatusUsedRefreshToken(@Param("user") UserEntity userEntity, + @Param("currentStatus") StatusRefreshToken currentStatus, + @Param("newStatus") StatusRefreshToken newStatus); -public interface RefreshTokenRepository extends JpaRepository { - Optional findByToken(String token); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java index e001e7a..09f74c1 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/UserRepository.java @@ -6,15 +6,10 @@ import com.smartjam.smartjamapi.entity.UserEntity; import org.springframework.data.jpa.repository.JpaRepository; -public interface UserRepository extends JpaRepository { +public interface UserRepository extends JpaRepository { Optional findByEmail(String login); - Optional findById(UUID id); - - Optional findUserEntitiesByUsername(String username); - boolean existsByEmail(String email); - boolean existsByUsername(String username); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java index f9d7422..f37cb92 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtAuthenticationFilter.java @@ -42,7 +42,7 @@ protected void doFilterInternal( final String jwt = authHeader.substring(7); final String email = jwtService.extractUsername(jwt); - log.info("Filter for {}", email); + log.debug("Filter for {}", email); if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetailsImpl userDetails = customUserDetailsService.loadUserByUsername(email); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java index dbdad8e..8915b9a 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java @@ -25,7 +25,7 @@ public class JwtService { private String secretKey; @Getter - @Value("${security.jwt.expiration-time}") + @Value("${security.jwt.expiration-time-access}") private long jwtExpiration; private Key getSigningKey() { @@ -33,11 +33,6 @@ private Key getSigningKey() { return Keys.hmacShaKeyFor(keyBytes); } - public String generateRefreshToken() { - byte[] randomBytes = new byte[64]; - new SecureRandom().nextBytes(randomBytes); - return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); - } public String generateAccessToken(UserDetailsImpl userDetails) { Map claims = new HashMap<>(); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java new file mode 100644 index 0000000..8efd34b --- /dev/null +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java @@ -0,0 +1,55 @@ +package com.smartjam.smartjamapi.security; + +import com.smartjam.smartjamapi.entity.RefreshTokenEntity; +import com.smartjam.smartjamapi.entity.UserEntity; +import com.smartjam.smartjamapi.enums.StatusRefreshToken; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Base64; + +@Service +@Slf4j +public class RefreshTokenService { + + @Getter + @Value("${security.jwt.expiration-time-refresh}") + private long refreshExpiration; + + + public String generateRefreshToken() { + byte[] randomBytes = new byte[64]; + new SecureRandom().nextBytes(randomBytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes); + } + + public static String hashRefreshToken(String token) { + try { + byte[] digest = MessageDigest.getInstance("SHA-256") + .digest(token.getBytes(StandardCharsets.UTF_8)); + return new String(Hex.encode(digest)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 algorithm not found", e); + } + } + + public RefreshTokenEntity create(UserEntity userEntity, String refreshToken) { + RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); + refreshTokenEntity.setTokenHash(hashRefreshToken(refreshToken)); + refreshTokenEntity.setUser(userEntity); + refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(getRefreshExpiration())); + refreshTokenEntity.setStatus(StatusRefreshToken.ACTIVE); + + return refreshTokenEntity; + } + + +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java index a979ed3..fe9fac5 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/UserDetailsImpl.java @@ -23,24 +23,24 @@ public class UserDetailsImpl implements UserDetails { @Getter private String email; - private String password; + private String PasswordHash; public static UserDetailsImpl build(UserEntity user) { - return new UserDetailsImpl(user.getId(), user.getUsername(), user.getEmail(), user.getPasswordHash()); + return new UserDetailsImpl(user.getId(), user.getNickname(), user.getEmail(), user.getPasswordHash()); } @Override public Collection getAuthorities() { - return List.of(); + return List.of(); // пока так, не знаю что сюда пихать } @Override public @Nullable String getPassword() { - return password; + return PasswordHash; } @Override public String getUsername() { - return username; + return email; } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index 4f18056..30a78c1 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -3,7 +3,11 @@ import java.time.Instant; import java.util.NoSuchElementException; -import jakarta.transaction.Transactional; +import com.smartjam.smartjamapi.enums.StatusRefreshToken; +import com.smartjam.smartjamapi.exception.TokenExpiredException; +import com.smartjam.smartjamapi.exception.TokenNotFoundException; +import com.smartjam.smartjamapi.mapper.UserMapper; +import com.smartjam.smartjamapi.security.RefreshTokenService; import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; @@ -11,7 +15,6 @@ import com.smartjam.smartjamapi.dto.RegisterRequest; import com.smartjam.smartjamapi.entity.RefreshTokenEntity; import com.smartjam.smartjamapi.entity.UserEntity; -import com.smartjam.smartjamapi.enums.AvailabilityStatus; import com.smartjam.smartjamapi.enums.Role; import com.smartjam.smartjamapi.repository.RefreshTokenRepository; import com.smartjam.smartjamapi.repository.UserRepository; @@ -21,6 +24,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; @Service @AllArgsConstructor @@ -32,6 +37,9 @@ public class AuthService { private final PasswordEncoder passwordEncoder; private final JwtService jwtService; + private final RefreshTokenService refreshTokenService; + + private final UserMapper userMapper; @Transactional public AuthResponse login(LoginRequest request) { @@ -45,27 +53,28 @@ public AuthResponse login(LoginRequest request) { UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); String accessToken = jwtService.generateAccessToken(userDetails); - String refreshToken = jwtService.generateRefreshToken(); + String refreshToken = refreshTokenService.generateRefreshToken(); + + RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, refreshToken); - RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); - refreshTokenEntity.setToken(refreshToken); - refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); + refreshTokenRepository.setStatusUsedRefreshToken(userEntity, StatusRefreshToken.ACTIVE, StatusRefreshToken.USED); refreshTokenRepository.save(refreshTokenEntity); - return new AuthResponse("Logged in successfully", AvailabilityStatus.AVAILABLE, refreshToken, accessToken); + log.info("Login successful"); + + return new AuthResponse(refreshToken, accessToken); } + @Transactional public AuthResponse register(RegisterRequest request) { if (repository.existsByEmail(request.email())) { throw new IllegalStateException("The account exists, try login, please"); } - UserEntity userEntity = new UserEntity(); - userEntity.setUsername(request.username()); - userEntity.setEmail(request.email()); + UserEntity userEntity = userMapper.toEntity(request); + userEntity.setPasswordHash(passwordEncoder.encode(request.password())); userEntity.setRole(Role.STUDENT); @@ -74,43 +83,67 @@ public AuthResponse register(RegisterRequest request) { UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); String accessToken = jwtService.generateAccessToken(userDetails); - String refreshToken = jwtService.generateRefreshToken(); + String refreshToken = refreshTokenService.generateRefreshToken(); + + RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, refreshToken); - RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); - refreshTokenEntity.setToken(refreshToken); - refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); + refreshTokenRepository.setStatusUsedRefreshToken(userEntity, StatusRefreshToken.ACTIVE, StatusRefreshToken.USED); refreshTokenRepository.save(refreshTokenEntity); - return new AuthResponse("Register successfully", AvailabilityStatus.AVAILABLE, refreshToken, accessToken); + log.info("Register successful"); + + return new AuthResponse(refreshToken, accessToken); + } + + + @Transactional + protected void revokeToken(String tokenHash) { + refreshTokenRepository.setStatusByRefreshToken(tokenHash, StatusRefreshToken.REVOKED); + + refreshTokenRepository.flush(); } @Transactional - public AuthResponse getNewToken(RefreshTokenRequest refreshTokenRequest) { + public AuthResponse getNewTokens(RefreshTokenRequest refreshTokenRequest) { + + String tokenHash = RefreshTokenService.hashRefreshToken(refreshTokenRequest.refreshToken()); + + log.info(tokenHash); + RefreshTokenEntity refreshToken = refreshTokenRepository - .findByToken(refreshTokenRequest.refreshToken()) - .orElseThrow(() -> new NoSuchElementException("Token not found, try login, please")); + .findByTokenHash(tokenHash) + .orElseThrow(() -> new TokenNotFoundException("Token not found, try login, please")); + + if (refreshToken.getStatus() == StatusRefreshToken.USED) { + revokeToken(tokenHash); + log.error(tokenHash); + + throw new SecurityException("Token reuse detected"); + } + if (refreshToken.getExpiresAt().isBefore(Instant.now())) { - throw new IllegalStateException("Refresh token expired"); + revokeToken(tokenHash); + log.error(tokenHash); + + throw new TokenExpiredException("Refresh token expired"); } - UserEntity userEntity = repository - .findById(refreshToken.getUser().getId()) - .orElseThrow(() -> new NoSuchElementException("User not found")); + refreshTokenRepository.setStatusByRefreshToken(tokenHash, StatusRefreshToken.USED); + refreshTokenRepository.flush(); + + UserEntity userEntity = refreshToken.getUser(); UserDetailsImpl userDetails = UserDetailsImpl.build(userEntity); String accessToken = jwtService.generateAccessToken(userDetails); - String newRefreshToken = jwtService.generateRefreshToken(); - - RefreshTokenEntity refreshTokenEntity = new RefreshTokenEntity(); - refreshTokenEntity.setToken(newRefreshToken); - refreshTokenEntity.setUser(userEntity); - refreshTokenEntity.setExpiresAt(Instant.now().plusMillis(jwtService.getJwtExpiration())); + String newRefreshToken = refreshTokenService.generateRefreshToken(); + RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, newRefreshToken); refreshTokenRepository.save(refreshTokenEntity); - return new AuthResponse( - "Token generate successfully", AvailabilityStatus.AVAILABLE, newRefreshToken, accessToken); + + log.info("New tokens successfully created"); + + return new AuthResponse(newRefreshToken, accessToken); } } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java index 83a2cf0..ac5c3f0 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java @@ -23,18 +23,41 @@ public class UploadService { private static final String BUCKET_NAME = "music"; - private static final String S3_ENDPOINT = "http://localhost:9000"; // пока захардкодил ссылку + private static final String S3_ENDPOINT = "http://localhost:9000"; + private static final String ACCESS_KEY = "minioadmin"; + private static final String SECRET_KEY = "minioadmin"; + + private static S3Presigner presigner; + private static S3Client s3Client; @PostConstruct public void init() { - try (S3Client s3Client = S3Client.builder() - .endpointOverride(URI.create(S3_ENDPOINT)) - .region(Region.US_EAST_1) - .serviceConfiguration(b -> b.pathStyleAccessEnabled(true)) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create("minioadmin", "minioadmin"))) // пока захардкодил - .build()) { - log.info("Trying to connect to MinIO at {}", s3Client); + if (s3Client == null) { + s3Client = S3Client.builder() + .endpointOverride(URI.create(S3_ENDPOINT)) + .region(Region.US_EAST_1) + .serviceConfiguration(b -> b.pathStyleAccessEnabled(true)) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY))) + .build(); + log.info("S3Client initialized"); + } + + if (presigner == null) { + presigner = S3Presigner.builder() + .endpointOverride(URI.create(S3_ENDPOINT)) + .region(Region.US_EAST_1) + .serviceConfiguration(S3Configuration.builder() + .pathStyleAccessEnabled(true) + .build()) + .credentialsProvider(StaticCredentialsProvider.create( + AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY))) + .build(); + log.info("S3Presigner initialized"); + } + + try { + log.info("Trying to connect to MinIO at {}", S3_ENDPOINT); if (s3Client.listBuckets().buckets().stream() .noneMatch(b -> b.name().equals(BUCKET_NAME))) { s3Client.createBucket(b -> b.bucket(BUCKET_NAME)); @@ -42,27 +65,24 @@ public void init() { } else { log.info("Bucket '{}' already exists", BUCKET_NAME); } + } catch (Exception e) { + log.error("Failed to initialize bucket: {}", e.getMessage(), e); } } public UploadUrlResponse generateUploadUrl(String fileName) { - try (S3Presigner presigner = S3Presigner.builder() - .endpointOverride(URI.create(S3_ENDPOINT)) - .region(Region.US_EAST_1) - .serviceConfiguration( - S3Configuration.builder().pathStyleAccessEnabled(true).build()) - .credentialsProvider( - StaticCredentialsProvider.create(AwsBasicCredentials.create("minioadmin", "minioadmin"))) - .build()) { + PutObjectRequest putObjectRequest = PutObjectRequest.builder() + .bucket(BUCKET_NAME) + .key(fileName) + .build(); - PutObjectRequest putObjectRequest = - PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName).build(); + PresignedPutObjectRequest presignedGetObjectRequest = presigner.presignPutObject( + r -> r.putObjectRequest(putObjectRequest) + .signatureDuration(Duration.ofMinutes(10))); - PresignedPutObjectRequest presignedGetObjectRequest = presigner.presignPutObject( - r -> r.putObjectRequest(putObjectRequest).signatureDuration(Duration.ofMinutes(10))); + URL presignedUrl = presignedGetObjectRequest.url(); + log.info("Generated upload URL for file: {}", fileName); - URL presignedUtl = presignedGetObjectRequest.url(); - return new UploadUrlResponse(presignedUtl.toString()); - } + return new UploadUrlResponse(presignedUrl.toString()); } -} +} \ No newline at end of file diff --git a/backend/smartjam-api/src/main/resources/application.yaml b/backend/smartjam-api/src/main/resources/application.yaml index d5b1f49847e0cb684f20db54658a5ca03a335595..9d1e6ed6537d6c1d92b3e7249c1de8faebc7fd56 100644 GIT binary patch delta 83 zcmbOu{6ly{C#SqFLn1>m5T-H|1ECdz0)sJw8G``~PEO#|RM%xF0?Ma>$zp~Kuqu!m M69x;IIv~vj0J~ca`~Uy| delta 27 bcmew%JV$s#C#SF#g93vwgBgPX9C85wUB?8? From a8ff2bd49eb66eda17cadb220ebbf815d916c6fc Mon Sep 17 00:00:00 2001 From: abeb Date: Sat, 21 Mar 2026 00:42:34 +0300 Subject: [PATCH 11/13] fix: responded to comments --- .../com/smartjam/smartjamapi/controller/MainController.java | 2 -- .../main/java/com/smartjam/smartjamapi/security/JwtService.java | 2 -- .../main/java/com/smartjam/smartjamapi/service/AuthService.java | 1 - 3 files changed, 5 deletions(-) diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java index 567d737..b7597e8 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java @@ -3,8 +3,6 @@ import java.security.Principal; import lombok.extern.slf4j.Slf4j; -import org.springframework.context.annotation.Role; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java index 8915b9a..295f791 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java @@ -1,8 +1,6 @@ package com.smartjam.smartjamapi.security; import java.security.Key; -import java.security.SecureRandom; -import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index 30a78c1..830d37a 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -24,7 +24,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; @Service From 535bcf80c9106593009b55b50fd720b25895aff3 Mon Sep 17 00:00:00 2001 From: abeb Date: Sat, 21 Mar 2026 00:45:33 +0300 Subject: [PATCH 12/13] fix: style --- .../config/CustomUserDetailsService.java | 3 ++- .../controller/AuthController.java | 2 +- .../controller/MainController.java | 2 +- .../smartjamapi/dto/RefreshTokenRequest.java | 5 ++--- .../entity/RefreshTokenEntity.java | 2 +- .../exception/GlobalExceptionHandler.java | 8 ++------ .../exception/TokenNotFoundException.java | 2 +- .../smartjamapi/mapper/UserMapper.java | 1 + .../repository/RefreshTokenRepository.java | 10 +++++----- .../repository/UserRepository.java | 1 - .../smartjamapi/security/JwtService.java | 1 - .../security/RefreshTokenService.java | 20 ++++++++----------- .../smartjamapi/service/AuthService.java | 19 +++++++++--------- .../smartjamapi/service/UploadService.java | 19 ++++++++---------- 14 files changed, 41 insertions(+), 54 deletions(-) diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java index 2d1044a..5400e9b 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/config/CustomUserDetailsService.java @@ -1,9 +1,10 @@ package com.smartjam.smartjamapi.config; +import jakarta.validation.constraints.NotBlank; + import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.UserDetailsImpl; -import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java index a4eef23..103ce1e 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/AuthController.java @@ -33,7 +33,7 @@ public ResponseEntity register(@RequestBody @Valid RegisterRequest } @PostMapping("/refresh") - public ResponseEntity getNewTokens(@RequestBody @Valid RefreshTokenRequest refreshTokenRequest) { + public ResponseEntity getNewTokens(@RequestBody @Valid RefreshTokenRequest refreshTokenRequest) { log.info("Calling getNewToken"); return ResponseEntity.ok(authService.getNewTokens(refreshTokenRequest)); } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java index b7597e8..282efb9 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/controller/MainController.java @@ -21,7 +21,7 @@ public String userAccess(Principal principal) { } @GetMapping("/hello") -// @PreAuthorize() + // @PreAuthorize() public String hello() { return "You are auth"; } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java index acb5585..ec47839 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/dto/RefreshTokenRequest.java @@ -1,9 +1,8 @@ package com.smartjam.smartjamapi.dto; -import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.validation.constraints.NotBlank; +import com.fasterxml.jackson.annotation.JsonProperty; public record RefreshTokenRequest( - @NotBlank - @JsonProperty("refresh_token") String refreshToken) {} + @NotBlank @JsonProperty("refresh_token") String refreshToken) {} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java index 3aa9f49..e058918 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/entity/RefreshTokenEntity.java @@ -3,9 +3,9 @@ import java.time.Instant; import java.util.UUID; -import com.smartjam.smartjamapi.enums.StatusRefreshToken; import jakarta.persistence.*; +import com.smartjam.smartjamapi.enums.StatusRefreshToken; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java index ad3c668..5178dcf 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/GlobalExceptionHandler.java @@ -56,7 +56,6 @@ public ResponseEntity handleNoSuchElement(NoSuchElementExcepti return buildResponse(HttpStatus.NOT_FOUND, ErrorCode.NOT_FOUND, "Requested resource not found"); } - @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity handlerEntityNotFound(EntityNotFoundException e) { log.warn("Entity not found: ", e); @@ -82,10 +81,7 @@ public ResponseEntity handlerBadRequest(Exception e) { public ResponseEntity handleResourceNotFound(Exception e) { log.warn("Resource not found: {}", e.getMessage()); - return buildResponse(HttpStatus.NOT_FOUND, - ErrorCode.RESOURCE_NOT_FOUND, - "The requested resource was not found" - ); + return buildResponse( + HttpStatus.NOT_FOUND, ErrorCode.RESOURCE_NOT_FOUND, "The requested resource was not found"); } - } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java index 024449a..b08ff73 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/exception/TokenNotFoundException.java @@ -6,4 +6,4 @@ public class TokenNotFoundException extends AuthenticationException { public TokenNotFoundException(String message) { super(message); } -} \ No newline at end of file +} diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java index 5d03883..7a3853a 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/mapper/UserMapper.java @@ -3,6 +3,7 @@ import com.smartjam.smartjamapi.dto.RegisterRequest; import com.smartjam.smartjamapi.entity.UserEntity; import org.mapstruct.Mapper; + @Mapper(componentModel = "spring") public interface UserMapper { UserEntity toEntity(RegisterRequest request); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java index d17166e..3718ae0 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/repository/RefreshTokenRepository.java @@ -18,7 +18,7 @@ public interface RefreshTokenRepository extends JpaRepository { Optional findByEmail(String login); boolean existsByEmail(String email); - } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java index 295f791..cd50c1f 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/JwtService.java @@ -31,7 +31,6 @@ private Key getSigningKey() { return Keys.hmacShaKeyFor(keyBytes); } - public String generateAccessToken(UserDetailsImpl userDetails) { Map claims = new HashMap<>(); claims.put( diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java index 8efd34b..c24d74d 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/security/RefreshTokenService.java @@ -1,5 +1,12 @@ package com.smartjam.smartjamapi.security; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.time.Instant; +import java.util.Base64; + import com.smartjam.smartjamapi.entity.RefreshTokenEntity; import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.enums.StatusRefreshToken; @@ -9,13 +16,6 @@ import org.springframework.security.crypto.codec.Hex; import org.springframework.stereotype.Service; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.time.Instant; -import java.util.Base64; - @Service @Slf4j public class RefreshTokenService { @@ -24,7 +24,6 @@ public class RefreshTokenService { @Value("${security.jwt.expiration-time-refresh}") private long refreshExpiration; - public String generateRefreshToken() { byte[] randomBytes = new byte[64]; new SecureRandom().nextBytes(randomBytes); @@ -33,8 +32,7 @@ public String generateRefreshToken() { public static String hashRefreshToken(String token) { try { - byte[] digest = MessageDigest.getInstance("SHA-256") - .digest(token.getBytes(StandardCharsets.UTF_8)); + byte[] digest = MessageDigest.getInstance("SHA-256").digest(token.getBytes(StandardCharsets.UTF_8)); return new String(Hex.encode(digest)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("SHA-256 algorithm not found", e); @@ -50,6 +48,4 @@ public RefreshTokenEntity create(UserEntity userEntity, String refreshToken) { return refreshTokenEntity; } - - } diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java index 830d37a..f2bc04a 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/AuthService.java @@ -3,12 +3,6 @@ import java.time.Instant; import java.util.NoSuchElementException; -import com.smartjam.smartjamapi.enums.StatusRefreshToken; -import com.smartjam.smartjamapi.exception.TokenExpiredException; -import com.smartjam.smartjamapi.exception.TokenNotFoundException; -import com.smartjam.smartjamapi.mapper.UserMapper; -import com.smartjam.smartjamapi.security.RefreshTokenService; - import com.smartjam.smartjamapi.dto.AuthResponse; import com.smartjam.smartjamapi.dto.LoginRequest; import com.smartjam.smartjamapi.dto.RefreshTokenRequest; @@ -16,9 +10,14 @@ import com.smartjam.smartjamapi.entity.RefreshTokenEntity; import com.smartjam.smartjamapi.entity.UserEntity; import com.smartjam.smartjamapi.enums.Role; +import com.smartjam.smartjamapi.enums.StatusRefreshToken; +import com.smartjam.smartjamapi.exception.TokenExpiredException; +import com.smartjam.smartjamapi.exception.TokenNotFoundException; +import com.smartjam.smartjamapi.mapper.UserMapper; import com.smartjam.smartjamapi.repository.RefreshTokenRepository; import com.smartjam.smartjamapi.repository.UserRepository; import com.smartjam.smartjamapi.security.JwtService; +import com.smartjam.smartjamapi.security.RefreshTokenService; import com.smartjam.smartjamapi.security.UserDetailsImpl; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -56,7 +55,8 @@ public AuthResponse login(LoginRequest request) { RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, refreshToken); - refreshTokenRepository.setStatusUsedRefreshToken(userEntity, StatusRefreshToken.ACTIVE, StatusRefreshToken.USED); + refreshTokenRepository.setStatusUsedRefreshToken( + userEntity, StatusRefreshToken.ACTIVE, StatusRefreshToken.USED); refreshTokenRepository.save(refreshTokenEntity); @@ -86,7 +86,8 @@ public AuthResponse register(RegisterRequest request) { RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, refreshToken); - refreshTokenRepository.setStatusUsedRefreshToken(userEntity, StatusRefreshToken.ACTIVE, StatusRefreshToken.USED); + refreshTokenRepository.setStatusUsedRefreshToken( + userEntity, StatusRefreshToken.ACTIVE, StatusRefreshToken.USED); refreshTokenRepository.save(refreshTokenEntity); @@ -95,7 +96,6 @@ public AuthResponse register(RegisterRequest request) { return new AuthResponse(refreshToken, accessToken); } - @Transactional protected void revokeToken(String tokenHash) { refreshTokenRepository.setStatusByRefreshToken(tokenHash, StatusRefreshToken.REVOKED); @@ -140,7 +140,6 @@ public AuthResponse getNewTokens(RefreshTokenRequest refreshTokenRequest) { RefreshTokenEntity refreshTokenEntity = refreshTokenService.create(userEntity, newRefreshToken); refreshTokenRepository.save(refreshTokenEntity); - log.info("New tokens successfully created"); return new AuthResponse(newRefreshToken, accessToken); diff --git a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java index ac5c3f0..44e1207 100644 --- a/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java +++ b/backend/smartjam-api/src/main/java/com/smartjam/smartjamapi/service/UploadService.java @@ -37,8 +37,8 @@ public void init() { .endpointOverride(URI.create(S3_ENDPOINT)) .region(Region.US_EAST_1) .serviceConfiguration(b -> b.pathStyleAccessEnabled(true)) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY))) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY))) .build(); log.info("S3Client initialized"); } @@ -50,8 +50,8 @@ public void init() { .serviceConfiguration(S3Configuration.builder() .pathStyleAccessEnabled(true) .build()) - .credentialsProvider(StaticCredentialsProvider.create( - AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY))) + .credentialsProvider( + StaticCredentialsProvider.create(AwsBasicCredentials.create(ACCESS_KEY, SECRET_KEY))) .build(); log.info("S3Presigner initialized"); } @@ -71,18 +71,15 @@ public void init() { } public UploadUrlResponse generateUploadUrl(String fileName) { - PutObjectRequest putObjectRequest = PutObjectRequest.builder() - .bucket(BUCKET_NAME) - .key(fileName) - .build(); + PutObjectRequest putObjectRequest = + PutObjectRequest.builder().bucket(BUCKET_NAME).key(fileName).build(); PresignedPutObjectRequest presignedGetObjectRequest = presigner.presignPutObject( - r -> r.putObjectRequest(putObjectRequest) - .signatureDuration(Duration.ofMinutes(10))); + r -> r.putObjectRequest(putObjectRequest).signatureDuration(Duration.ofMinutes(10))); URL presignedUrl = presignedGetObjectRequest.url(); log.info("Generated upload URL for file: {}", fileName); return new UploadUrlResponse(presignedUrl.toString()); } -} \ No newline at end of file +} From 85e9f77e46f1dcbc786929aaf6284bcda7e40599 Mon Sep 17 00:00:00 2001 From: abeb Date: Sat, 21 Mar 2026 17:45:58 +0300 Subject: [PATCH 13/13] fix: merge conflict --- backend/smartjam-api/build.gradle | 15 +++++++++++++-- .../src/main/resources/application.yaml | Bin 2244 -> 2550 bytes 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/backend/smartjam-api/build.gradle b/backend/smartjam-api/build.gradle index 3fbe05b..0b6206b 100644 --- a/backend/smartjam-api/build.gradle +++ b/backend/smartjam-api/build.gradle @@ -1,12 +1,23 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' + implementation "org.springframework.boot:spring-boot-starter-validation" + implementation 'org.springframework.boot:spring-boot-starter-security' + + implementation 'io.jsonwebtoken:jjwt-api:0.13.0' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.13.0' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.13.0' + + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - runtimeOnly 'org.postgresql:postgresql' + + runtimeOnly('org.postgresql:postgresql') implementation 'org.springframework.boot:spring-boot-starter-kafka' testImplementation 'org.springframework.kafka:spring-kafka-test' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.2' + + implementation("software.amazon.awssdk:s3:2.29.22") } \ No newline at end of file diff --git a/backend/smartjam-api/src/main/resources/application.yaml b/backend/smartjam-api/src/main/resources/application.yaml index 16aca50cb1b7899da50c07215c128395e42ba7fb..0dbdb99ac1d63d8e8e6655c243cb2b870e3a90c4 100644 GIT binary patch delta 316 zcmZvXOAf(M6o&tyn28P8fTT%FFeC<~!brl5s??(%Z3Uxl$s)QYW8YOeAt(2q=l#xq zv&ysj$V#coSS(+jE{e2KqPsfOYS>m+@^5A;`DDa=qPli?GhI_#3nGu>Dui;@QWL5@ zm^V0m`YSaxBbsZ-OIKa!=}2P@824#eni4l;a|ihSM}}GKgE;|?044aN4hS$&NUu)S j!+ZqBZ$UQ8titl|{NXr1AoI61YYZxQqYTJVtAJTARIoIA delta 7 Ocmew+d_-`=5e@(j>jPT=