From 4814f5482c4b1c4e5ad261fb018081599ae1fd56 Mon Sep 17 00:00:00 2001 From: balex Date: Sun, 8 Mar 2026 14:56:27 +0100 Subject: [PATCH] auth gateway refact --- .../posthub/gateway/config/R2dbcConfig.java | 2 +- .../gateway/controller/AuthController.java | 56 ++++-- .../gateway/model/constants/ApiConstants.java | 21 +++ .../model/constants/ApiErrorMessage.java | 29 +++ .../gateway/model/entity/RefreshToken.java | 30 ++++ ...oginUserRequest.java => LoginRequest.java} | 9 +- .../gateway/model/response/JwtResponse.java | 14 -- .../gateway/model/response/RagResponse.java | 25 +++ .../model/response/UserProfileDTO.java | 22 +++ .../repository/RefreshTokenRepository.java | 12 ++ .../posthub/gateway/service/AuthService.java | 166 +++++++++++------- .../posthub/gateway/util/PasswordUtils.java | 34 ++++ rag-service/pom.xml | 17 -- .../com/balex/rag/config/SecurityConfig.java | 81 +-------- .../balex/rag/controller/AuthController.java | 86 --------- .../balex/rag/controller/ChatController.java | 22 +-- .../rag/controller/ChatEntryController.java | 8 +- .../DocumentUploadStreamController.java | 10 +- .../balex/rag/controller/UserController.java | 23 +-- .../balex/rag/filter/GatewayAuthFilter.java | 37 ++++ .../java/com/balex/rag/mapper/UserMapper.java | 19 +- .../rag/model/constants/ApiConstants.java | 2 - .../balex/rag/model/entity/RefreshToken.java | 31 ---- .../exception/InvalidTokenException.java | 9 - .../rag/model/request/user/LoginRequest.java | 25 --- .../request/user/RegistrationUserRequest.java | 31 ---- .../rag/repo/RefreshTokenRepository.java | 15 -- .../balex/rag/security/JwtTokenProvider.java | 104 ----------- .../rag/security/filter/JwtRequestFilter.java | 130 -------------- .../handler/AccessRestrictionHandler.java | 25 --- .../security/validation/AccessValidator.java | 34 +--- .../validation/PasswordMatchesValidator.java | 16 -- .../com/balex/rag/service/AuthService.java | 16 -- .../com/balex/rag/service/ChatService.java | 2 +- .../rag/service/RefreshTokenService.java | 12 -- .../com/balex/rag/service/UserService.java | 15 +- .../rag/service/impl/AuthServiceImpl.java | 97 ---------- .../rag/service/impl/ChatServiceImpl.java | 24 +-- .../service/impl/RefreshTokenServiceImpl.java | 57 ------ .../rag/service/impl/UserServiceImpl.java | 65 +------ .../java/com/balex/rag/utils/ApiUtils.java | 49 +----- .../com/balex/rag/utils/PasswordMatches.java | 23 --- .../com/balex/rag/utils/PasswordUtils.java | 4 - .../java/com/balex/rag/utils/UserContext.java | 25 +++ .../src/main/resources/application.properties | 2 - 45 files changed, 450 insertions(+), 1086 deletions(-) create mode 100644 gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java create mode 100644 gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java create mode 100644 gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java rename gateway-service/src/main/java/com/posthub/gateway/model/request/{LoginUserRequest.java => LoginRequest.java} (59%) delete mode 100644 gateway-service/src/main/java/com/posthub/gateway/model/response/JwtResponse.java create mode 100644 gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java create mode 100644 gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java create mode 100644 gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java create mode 100644 gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java delete mode 100644 rag-service/src/main/java/com/balex/rag/controller/AuthController.java create mode 100644 rag-service/src/main/java/com/balex/rag/filter/GatewayAuthFilter.java delete mode 100644 rag-service/src/main/java/com/balex/rag/model/entity/RefreshToken.java delete mode 100644 rag-service/src/main/java/com/balex/rag/model/exception/InvalidTokenException.java delete mode 100644 rag-service/src/main/java/com/balex/rag/model/request/user/LoginRequest.java delete mode 100644 rag-service/src/main/java/com/balex/rag/model/request/user/RegistrationUserRequest.java delete mode 100644 rag-service/src/main/java/com/balex/rag/repo/RefreshTokenRepository.java delete mode 100644 rag-service/src/main/java/com/balex/rag/security/JwtTokenProvider.java delete mode 100644 rag-service/src/main/java/com/balex/rag/security/filter/JwtRequestFilter.java delete mode 100644 rag-service/src/main/java/com/balex/rag/security/handler/AccessRestrictionHandler.java delete mode 100644 rag-service/src/main/java/com/balex/rag/security/validation/PasswordMatchesValidator.java delete mode 100644 rag-service/src/main/java/com/balex/rag/service/AuthService.java delete mode 100644 rag-service/src/main/java/com/balex/rag/service/RefreshTokenService.java delete mode 100644 rag-service/src/main/java/com/balex/rag/service/impl/AuthServiceImpl.java delete mode 100644 rag-service/src/main/java/com/balex/rag/service/impl/RefreshTokenServiceImpl.java delete mode 100644 rag-service/src/main/java/com/balex/rag/utils/PasswordMatches.java create mode 100644 rag-service/src/main/java/com/balex/rag/utils/UserContext.java diff --git a/gateway-service/src/main/java/com/posthub/gateway/config/R2dbcConfig.java b/gateway-service/src/main/java/com/posthub/gateway/config/R2dbcConfig.java index c2f069e..3f27d9b 100644 --- a/gateway-service/src/main/java/com/posthub/gateway/config/R2dbcConfig.java +++ b/gateway-service/src/main/java/com/posthub/gateway/config/R2dbcConfig.java @@ -19,7 +19,7 @@ public class R2dbcConfig { @Bean public R2dbcCustomConversions r2dbcCustomConversions() { return R2dbcCustomConversions.of( - PostgresDialect.INSTANCE.getStoreConversions(), + PostgresDialect.INSTANCE, List.of( new RegistrationStatusReadConverter(), new RegistrationStatusWriteConverter(), diff --git a/gateway-service/src/main/java/com/posthub/gateway/controller/AuthController.java b/gateway-service/src/main/java/com/posthub/gateway/controller/AuthController.java index 51382c8..3cea092 100644 --- a/gateway-service/src/main/java/com/posthub/gateway/controller/AuthController.java +++ b/gateway-service/src/main/java/com/posthub/gateway/controller/AuthController.java @@ -1,14 +1,16 @@ package com.posthub.gateway.controller; -import com.posthub.gateway.model.request.LoginUserRequest; +import com.posthub.gateway.model.request.LoginRequest; import com.posthub.gateway.model.request.RegistrationUserRequest; -import com.posthub.gateway.model.response.JwtResponse; +import com.posthub.gateway.model.response.RagResponse; +import com.posthub.gateway.model.response.UserProfileDTO; import com.posthub.gateway.service.AuthService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; +import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Mono; @@ -19,22 +21,46 @@ public class AuthController { private final AuthService authService; - @PostMapping("/register") - public Mono> register(@Valid @RequestBody RegistrationUserRequest request) { - return authService.register(request) - .map(response -> ResponseEntity.status(HttpStatus.CREATED).body(response)); + @PostMapping("/login") + public Mono>> login( + @RequestBody @Valid LoginRequest request, + ServerHttpResponse response) { + return authService.login(request) + .map(result -> { + addAuthCookie(response, result.getPayload().getToken()); + return ResponseEntity.ok(result); + }); } - @PostMapping("/login") - public Mono> login(@Valid @RequestBody LoginUserRequest request) { - return authService.login(request) - .map(ResponseEntity::ok); + @PostMapping("/register") + public Mono>> register( + @RequestBody @Valid RegistrationUserRequest request, + ServerHttpResponse response) { + return authService.register(request) + .map(result -> { + addAuthCookie(response, result.getPayload().getToken()); + return ResponseEntity.status(HttpStatus.CREATED).body(result); + }); } @GetMapping("/refresh/token") - public Mono> refreshToken( - @RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader) { - return authService.refreshToken(authHeader) - .map(ResponseEntity::ok); + public Mono>> refreshToken( + @RequestParam(name = "token") String refreshToken, + ServerHttpResponse response) { + return authService.refreshAccessToken(refreshToken) + .map(result -> { + addAuthCookie(response, result.getPayload().getToken()); + return ResponseEntity.ok(result); + }); + } + + private void addAuthCookie(ServerHttpResponse response, String token) { + ResponseCookie cookie = ResponseCookie.from("Authorization", token) + .httpOnly(true) + .secure(true) + .path("/") + .maxAge(300) + .build(); + response.addCookie(cookie); } } \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java new file mode 100644 index 0000000..9348a01 --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java @@ -0,0 +1,21 @@ +package com.posthub.gateway.model.constants; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ApiConstants { + + public static final String DASH = "-"; + + public static final String PASSWORD_ALL_CHARACTERS = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"; + public static final String PASSWORD_LETTERS_UPPER_CASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + public static final String PASSWORD_LETTERS_LOWER_CASE = "abcdefghijklmnopqrstuvwxyz"; + public static final String PASSWORD_DIGITS = "0123456789"; + public static final String PASSWORD_CHARACTERS = "~`!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?"; + public static final Integer REQUIRED_MIN_PASSWORD_LENGTH = 8; + public static final Integer REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD = 1; + public static final Integer REQUIRED_MIN_DIGITS_NUMBER_IN_PASSWORD = 1; + public static final Integer REQUIRED_MIN_CHARACTERS_NUMBER_IN_PASSWORD = 1; +} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java new file mode 100644 index 0000000..42304ac --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java @@ -0,0 +1,29 @@ +package com.posthub.gateway.model.constants; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public enum ApiErrorMessage { + + USERNAME_ALREADY_EXISTS("Username: %s already exists"), + EMAIL_ALREADY_EXISTS("Email: %s already exists"), + INVALID_USER_OR_PASSWORD("Invalid email or password. Try again"), + NOT_FOUND_REFRESH_TOKEN("Refresh token not found"), + MISMATCH_PASSWORDS("Password does not match"), + INVALID_PASSWORD("Invalid password. It must have: " + + "length at least " + ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH + ", including " + + ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD + " letter(s) in upper and lower cases, " + + ApiConstants.REQUIRED_MIN_CHARACTERS_NUMBER_IN_PASSWORD + " character(s), " + + ApiConstants.REQUIRED_MIN_DIGITS_NUMBER_IN_PASSWORD + " digit(s). "), + ACCOUNT_NOT_ACTIVE("Account is not active"), + ; + + private final String message; + + public String getMessage(Object... args) { + return String.format(message, args); + } +} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java b/gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java new file mode 100644 index 0000000..e98c9a2 --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java @@ -0,0 +1,30 @@ +package com.posthub.gateway.model.entity; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Column; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDateTime; + +@Table("refresh_token") +@Getter +@Setter +@NoArgsConstructor +public class RefreshToken { + + @Id + private Integer id; + + private String token; + + private LocalDateTime created; + + @Column("session_id") + private String sessionId; + + @Column("user_id") + private Integer userId; +} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginUserRequest.java b/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginRequest.java similarity index 59% rename from gateway-service/src/main/java/com/posthub/gateway/model/request/LoginUserRequest.java rename to gateway-service/src/main/java/com/posthub/gateway/model/request/LoginRequest.java index 10730eb..1fb3fc9 100644 --- a/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginUserRequest.java +++ b/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginRequest.java @@ -1,18 +1,19 @@ package com.posthub.gateway.model.request; import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; @Getter @Setter -public class LoginUserRequest { +public class LoginRequest { - @NotBlank @Email + @NotNull private String email; - @NotBlank + @NotEmpty private String password; } \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/response/JwtResponse.java b/gateway-service/src/main/java/com/posthub/gateway/model/response/JwtResponse.java deleted file mode 100644 index c71e031..0000000 --- a/gateway-service/src/main/java/com/posthub/gateway/model/response/JwtResponse.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.posthub.gateway.model.response; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Getter -@AllArgsConstructor -public class JwtResponse { - - private String token; - private String refreshToken; - private String email; - private String username; -} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java b/gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java new file mode 100644 index 0000000..2618109 --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java @@ -0,0 +1,25 @@ +package com.posthub.gateway.model.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class RagResponse

{ + + private String message; + private P payload; + private boolean success; + + public static

RagResponse

createSuccessful(P payload) { + return new RagResponse<>("", payload, true); + } + + public static

RagResponse

createSuccessfulWithNewToken(P payload) { + return new RagResponse<>("Token created or updated", payload, true); + } +} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java b/gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java new file mode 100644 index 0000000..cdd099e --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java @@ -0,0 +1,22 @@ +package com.posthub.gateway.model.response; + +import com.posthub.gateway.model.enums.RegistrationStatus; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; + +@Getter +@Setter +@AllArgsConstructor +public class UserProfileDTO { + + private Integer id; + private String username; + private String email; + private RegistrationStatus registrationStatus; + private LocalDateTime lastLogin; + private String token; + private String refreshToken; +} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java b/gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..4a9b32a --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java @@ -0,0 +1,12 @@ +package com.posthub.gateway.repository; + +import com.posthub.gateway.model.entity.RefreshToken; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import reactor.core.publisher.Mono; + +public interface RefreshTokenRepository extends ReactiveCrudRepository { + + Mono findByToken(String token); + + Mono findByUserId(Integer userId); +} \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java b/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java index bb9fc91..f8fa9c2 100644 --- a/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java +++ b/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java @@ -1,13 +1,18 @@ package com.posthub.gateway.service; +import com.posthub.gateway.model.constants.ApiErrorMessage; +import com.posthub.gateway.model.entity.RefreshToken; import com.posthub.gateway.model.entity.User; import com.posthub.gateway.model.enums.RegistrationStatus; import com.posthub.gateway.model.enums.UserRole; -import com.posthub.gateway.model.request.LoginUserRequest; +import com.posthub.gateway.model.request.LoginRequest; import com.posthub.gateway.model.request.RegistrationUserRequest; -import com.posthub.gateway.model.response.JwtResponse; +import com.posthub.gateway.model.response.RagResponse; +import com.posthub.gateway.model.response.UserProfileDTO; +import com.posthub.gateway.repository.RefreshTokenRepository; import com.posthub.gateway.repository.UserRepository; import com.posthub.gateway.security.JwtTokenProvider; +import com.posthub.gateway.util.PasswordUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -25,84 +30,117 @@ import java.util.UUID; public class AuthService { private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; private final JwtTokenProvider jwtTokenProvider; private final PasswordEncoder passwordEncoder; - public Mono register(RegistrationUserRequest request) { - if (!request.getPassword().equals(request.getConfirmPassword())) { - return Mono.error(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Passwords do not match")); - } - - return userRepository.existsByEmail(request.getEmail()) - .flatMap(exists -> { - if (exists) { - return Mono.error(new ResponseStatusException(HttpStatus.CONFLICT, "Email already registered")); - } - - User user = new User(); - user.setUsername(request.getUsername()); - user.setEmail(request.getEmail()); - user.setPassword(passwordEncoder.encode(request.getPassword())); - user.setRegistrationStatus(RegistrationStatus.ACTIVE); - user.setRole(UserRole.USER); - user.setCreated(LocalDateTime.now()); - user.setUpdated(LocalDateTime.now()); - user.setDeleted(false); - - return userRepository.save(user); - }) - .map(savedUser -> { - String sessionId = UUID.randomUUID().toString(); - String token = jwtTokenProvider.generateToken(savedUser, sessionId); - String refreshToken = jwtTokenProvider.refreshToken(token); - log.info("User registered: {}", savedUser.getEmail()); - return new JwtResponse(token, refreshToken, savedUser.getEmail(), savedUser.getUsername()); - }); - } - - public Mono login(LoginUserRequest request) { + public Mono> login(LoginRequest request) { return userRepository.findByEmail(request.getEmail()) - .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials"))) + .switchIfEmpty(Mono.error(new ResponseStatusException( + HttpStatus.UNAUTHORIZED, ApiErrorMessage.INVALID_USER_OR_PASSWORD.getMessage()))) .flatMap(user -> { if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials")); + return Mono.error(new ResponseStatusException( + HttpStatus.UNAUTHORIZED, ApiErrorMessage.INVALID_USER_OR_PASSWORD.getMessage())); } if (user.getRegistrationStatus() != RegistrationStatus.ACTIVE) { - return Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN, "Account is not active")); + return Mono.error(new ResponseStatusException( + HttpStatus.FORBIDDEN, ApiErrorMessage.ACCOUNT_NOT_ACTIVE.getMessage())); } - user.setLastLogin(LocalDateTime.now()); user.setUpdated(LocalDateTime.now()); - return userRepository.save(user); }) - .map(user -> { - String sessionId = UUID.randomUUID().toString(); - String token = jwtTokenProvider.generateToken(user, sessionId); - String refreshToken = jwtTokenProvider.refreshToken(token); - log.info("User logged in: {}", user.getEmail()); - return new JwtResponse(token, refreshToken, user.getEmail(), user.getUsername()); + .flatMap(this::generateTokensAndBuildResponse); + } + + public Mono> register(RegistrationUserRequest request) { + if (!request.getPassword().equals(request.getConfirmPassword())) { + return Mono.error(new ResponseStatusException( + HttpStatus.BAD_REQUEST, ApiErrorMessage.MISMATCH_PASSWORDS.getMessage())); + } + if (PasswordUtils.isNotValidPassword(request.getPassword())) { + return Mono.error(new ResponseStatusException( + HttpStatus.BAD_REQUEST, ApiErrorMessage.INVALID_PASSWORD.getMessage())); + } + + return userRepository.findByUsername(request.getUsername()) + .flatMap(existing -> Mono.error(new ResponseStatusException( + HttpStatus.CONFLICT, ApiErrorMessage.USERNAME_ALREADY_EXISTS.getMessage(request.getUsername())))) + .switchIfEmpty(userRepository.existsByEmail(request.getEmail()) + .flatMap(exists -> { + if (exists) { + return Mono.error(new ResponseStatusException( + HttpStatus.CONFLICT, ApiErrorMessage.EMAIL_ALREADY_EXISTS.getMessage(request.getEmail()))); + } + User user = new User(); + user.setUsername(request.getUsername()); + user.setEmail(request.getEmail()); + user.setPassword(passwordEncoder.encode(request.getPassword())); + user.setRegistrationStatus(RegistrationStatus.ACTIVE); + user.setRole(UserRole.USER); + user.setCreated(LocalDateTime.now()); + user.setUpdated(LocalDateTime.now()); + user.setDeleted(false); + return userRepository.save(user); + })) + .flatMap(this::generateTokensAndBuildResponse); + } + + public Mono> refreshAccessToken(String refreshTokenValue) { + return refreshTokenRepository.findByToken(refreshTokenValue) + .switchIfEmpty(Mono.error(new ResponseStatusException( + HttpStatus.UNAUTHORIZED, ApiErrorMessage.NOT_FOUND_REFRESH_TOKEN.getMessage()))) + .flatMap(refreshToken -> { + refreshToken.setCreated(LocalDateTime.now()); + refreshToken.setToken(generateUuid()); + return refreshTokenRepository.save(refreshToken) + .flatMap(saved -> userRepository.findById(saved.getUserId()) + .flatMap(user -> { + String accessToken = jwtTokenProvider.generateToken(user, saved.getSessionId()); + UserProfileDTO dto = toUserProfileDto(user, accessToken, saved.getToken()); + return Mono.just(RagResponse.createSuccessfulWithNewToken(dto)); + })); }); } - public Mono refreshToken(String authHeader) { - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing or invalid Authorization header")); - } - - String token = authHeader.substring(7); - if (!jwtTokenProvider.validateToken(token)) { - return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid or expired token")); - } - - String email = jwtTokenProvider.getUserEmail(token); - return userRepository.findByEmail(email) - .switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "User not found"))) - .map(user -> { - String sessionId = jwtTokenProvider.getSessionId(token); - String newToken = jwtTokenProvider.generateToken(user, sessionId); - String newRefreshToken = jwtTokenProvider.refreshToken(newToken); - return new JwtResponse(newToken, newRefreshToken, user.getEmail(), user.getUsername()); + private Mono> generateTokensAndBuildResponse(User user) { + return refreshTokenRepository.findByUserId(user.getId()) + .flatMap(existing -> { + existing.setCreated(LocalDateTime.now()); + existing.setToken(generateUuid()); + existing.setSessionId(generateUuid()); + return refreshTokenRepository.save(existing); + }) + .switchIfEmpty(Mono.defer(() -> { + RefreshToken newToken = new RefreshToken(); + newToken.setUserId(user.getId()); + newToken.setCreated(LocalDateTime.now()); + newToken.setToken(generateUuid()); + newToken.setSessionId(generateUuid()); + return refreshTokenRepository.save(newToken); + })) + .map(refreshToken -> { + String accessToken = jwtTokenProvider.generateToken(user, refreshToken.getSessionId()); + UserProfileDTO dto = toUserProfileDto(user, accessToken, refreshToken.getToken()); + log.info("Auth success for user: {}", user.getEmail()); + return RagResponse.createSuccessfulWithNewToken(dto); }); } + + private UserProfileDTO toUserProfileDto(User user, String token, String refreshToken) { + return new UserProfileDTO( + user.getId(), + user.getUsername(), + user.getEmail(), + user.getRegistrationStatus(), + user.getLastLogin(), + token, + refreshToken + ); + } + + private String generateUuid() { + return UUID.randomUUID().toString().replace("-", ""); + } } \ No newline at end of file diff --git a/gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java b/gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java new file mode 100644 index 0000000..a86d8a8 --- /dev/null +++ b/gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java @@ -0,0 +1,34 @@ +package com.posthub.gateway.util; + +import com.posthub.gateway.model.constants.ApiConstants; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class PasswordUtils { + + public static boolean isNotValidPassword(String password) { + if (password == null || password.isEmpty() || password.trim().isEmpty()) { + return true; + } + String trim = password.trim(); + if (trim.length() < ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH) { + return true; + } + int charactersNumber = ApiConstants.REQUIRED_MIN_CHARACTERS_NUMBER_IN_PASSWORD; + int lettersUCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD; + int lettersLCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD; + int digitsNumber = ApiConstants.REQUIRED_MIN_DIGITS_NUMBER_IN_PASSWORD; + for (int i = 0; i < trim.length(); i++) { + String currentLetter = String.valueOf(trim.charAt(i)); + if (!ApiConstants.PASSWORD_ALL_CHARACTERS.contains(currentLetter)) { + return true; + } + charactersNumber -= ApiConstants.PASSWORD_CHARACTERS.contains(currentLetter) ? 1 : 0; + lettersUCaseNumber -= ApiConstants.PASSWORD_LETTERS_UPPER_CASE.contains(currentLetter) ? 1 : 0; + lettersLCaseNumber -= ApiConstants.PASSWORD_LETTERS_LOWER_CASE.contains(currentLetter) ? 1 : 0; + digitsNumber -= ApiConstants.PASSWORD_DIGITS.contains(currentLetter) ? 1 : 0; + } + return ((charactersNumber > 0) || (lettersUCaseNumber > 0) || (lettersLCaseNumber > 0) || (digitsNumber > 0)); + } +} \ No newline at end of file diff --git a/rag-service/pom.xml b/rag-service/pom.xml index 96d3c12..ceef7bc 100644 --- a/rag-service/pom.xml +++ b/rag-service/pom.xml @@ -128,23 +128,6 @@ spring-security-test test - - io.jsonwebtoken - jjwt-api - 0.11.5 - - - io.jsonwebtoken - jjwt-impl - 0.11.5 - runtime - - - io.jsonwebtoken - jjwt-jackson - 0.11.5 - runtime - org.springdoc springdoc-openapi-starter-webmvc-ui diff --git a/rag-service/src/main/java/com/balex/rag/config/SecurityConfig.java b/rag-service/src/main/java/com/balex/rag/config/SecurityConfig.java index c196351..1186a96 100644 --- a/rag-service/src/main/java/com/balex/rag/config/SecurityConfig.java +++ b/rag-service/src/main/java/com/balex/rag/config/SecurityConfig.java @@ -1,99 +1,26 @@ package com.balex.rag.config; -import com.balex.rag.security.filter.JwtRequestFilter; -import com.balex.rag.security.handler.AccessRestrictionHandler; -import jakarta.servlet.DispatcherType; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.HttpStatusEntryPoint; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.CorsConfigurationSource; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import java.util.List; @Configuration -@EnableWebSecurity -@EnableMethodSecurity -@RequiredArgsConstructor public class SecurityConfig { - private final JwtRequestFilter jwtRequestFilter; - private final AccessRestrictionHandler accessRestrictionHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http - .cors(Customizer.withDefaults()) + return http .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests(auth -> auth - .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll() - .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() - .requestMatchers(HttpMethod.GET, "/auth/refresh/token").permitAll() - .requestMatchers(HttpMethod.POST, "/auth/login", "/auth/register").permitAll() - .requestMatchers( - "/v3/api-docs/**", - "/swagger-ui/**", - "/swagger-ui.html", - "/webjars/**", - "/actuator/**" - ).permitAll() - .anyRequest().authenticated() - ) - .exceptionHandling(exceptions -> exceptions - .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) - .accessDeniedHandler(accessRestrictionHandler) - ) - .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); + .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) + .build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - - @Bean - public DaoAuthenticationProvider daoAuthenticationProvider(UserDetailsService userDetailsService) { - DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService); - daoAuthenticationProvider.setPasswordEncoder(passwordEncoder()); - return daoAuthenticationProvider; - } - - @Bean - public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception { - return authenticationConfiguration.getAuthenticationManager(); - } - - @Bean - public CorsConfigurationSource corsConfigurationSource() { - CorsConfiguration config = new CorsConfiguration(); - //config.setAllowedOrigins(List.of("http://localhost:5173")); - config.addAllowedOriginPattern("*"); - config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); - config.setAllowedHeaders(List.of("*")); - config.setAllowCredentials(true); - - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; - } - -} - +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/controller/AuthController.java b/rag-service/src/main/java/com/balex/rag/controller/AuthController.java deleted file mode 100644 index 058a03f..0000000 --- a/rag-service/src/main/java/com/balex/rag/controller/AuthController.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.balex.rag.controller; - -import com.balex.rag.model.constants.ApiLogMessage; -import com.balex.rag.model.dto.UserProfileDTO; -import com.balex.rag.model.request.user.LoginRequest; -import com.balex.rag.model.request.user.RegistrationUserRequest; -import com.balex.rag.model.response.RagResponse; -import com.balex.rag.service.AuthService; -import com.balex.rag.service.EventPublisher; -import com.balex.rag.utils.ApiUtils; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.ExampleObject; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -@Slf4j -@RestController -@Validated -@RequiredArgsConstructor -@RequestMapping("${end.points.auth}") -public class AuthController { - private final AuthService authService; - private final EventPublisher eventPublisher; - - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Successful authorization", - content = @Content(mediaType = "application/json", - examples = @ExampleObject(value = "{ \"token\": \"eyJhbGcIoIJIuz...\" }"))) - }) - @PostMapping("${end.points.login}") - @Operation(summary = "User login", description = "Authenticates the user and returns an access/refresh token" - ) - public ResponseEntity login( - @RequestBody @Valid LoginRequest request, - HttpServletResponse response) { - log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - - RagResponse result = authService.login(request); - Cookie authorizationCookie = ApiUtils.createAuthCookie(result.getPayload().getToken()); - response.addCookie(authorizationCookie); - - return ResponseEntity.ok(result); - } - - @GetMapping("${end.points.refresh.token}") - @Operation(summary = "Refresh access token", description = "Generates new access token using provided refresh token" - ) - public ResponseEntity> refreshToken( - @RequestParam(name = "token") String refreshToken, - HttpServletResponse response) { - log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - - RagResponse result = authService.refreshAccessToken(refreshToken); - Cookie authorizationCookie = ApiUtils.createAuthCookie(result.getPayload().getToken()); - response.addCookie(authorizationCookie); - - return ResponseEntity.ok(result); - } - - @PostMapping("${end.points.register}") - @Operation(summary = "Register a new user", description = "Creates new user and returns authentication details" - ) - public ResponseEntity register( - @RequestBody @Valid RegistrationUserRequest request, - HttpServletResponse response) { - log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - - RagResponse result = authService.registerUser(request); - Cookie authorizationCookie = ApiUtils.createAuthCookie(result.getPayload().getToken()); - response.addCookie(authorizationCookie); - - eventPublisher.publishUserCreated(result.getPayload().getId().toString()); - - return ResponseEntity.ok(result); - } - -} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/controller/ChatController.java b/rag-service/src/main/java/com/balex/rag/controller/ChatController.java index abf792c..56ef1aa 100644 --- a/rag-service/src/main/java/com/balex/rag/controller/ChatController.java +++ b/rag-service/src/main/java/com/balex/rag/controller/ChatController.java @@ -1,10 +1,10 @@ package com.balex.rag.controller; -import com.balex.rag.model.constants.ApiLogMessage; import com.balex.rag.model.entity.Chat; import com.balex.rag.service.ChatService; import com.balex.rag.service.EventPublisher; -import com.balex.rag.utils.ApiUtils; +import com.balex.rag.utils.UserContext; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -22,25 +22,25 @@ public class ChatController { private final ChatService chatService; private final EventPublisher eventPublisher; - private final ApiUtils apiUtils; @GetMapping("") - public ResponseEntity> mainPage() { - Long ownerId = apiUtils.getUserIdFromAuthentication().longValue(); + public ResponseEntity> mainPage(HttpServletRequest request) { + Long ownerId = UserContext.getUserId(request).longValue(); List response = chatService.getAllChatsByOwner(ownerId); return ResponseEntity.ok(response); } @GetMapping("/{chatId}") - public ResponseEntity showChat(@PathVariable Long chatId) { - Long ownerId = apiUtils.getUserIdFromAuthentication().longValue(); + public ResponseEntity showChat(@PathVariable Long chatId, HttpServletRequest request) { + Long ownerId = UserContext.getUserId(request).longValue(); Chat response = chatService.getChat(chatId, ownerId); return ResponseEntity.ok(response); } @PostMapping("/new") - public ResponseEntity newChat(@RequestParam String title) { - Chat chat = chatService.createNewChat(title); + public ResponseEntity newChat(@RequestParam String title, HttpServletRequest request) { + Long ownerId = UserContext.getUserId(request).longValue(); + Chat chat = chatService.createNewChat(title, ownerId); eventPublisher.publishChatCreated( chat.getIdOwner().toString(), @@ -50,8 +50,8 @@ public class ChatController { } @DeleteMapping("/{chatId}") - public ResponseEntity deleteChat(@PathVariable Long chatId) { - Long ownerId = apiUtils.getUserIdFromAuthentication().longValue(); + public ResponseEntity deleteChat(@PathVariable Long chatId, HttpServletRequest request) { + Long ownerId = UserContext.getUserId(request).longValue(); Chat chat = chatService.getChat(chatId, ownerId); chatService.deleteChat(chatId, ownerId); diff --git a/rag-service/src/main/java/com/balex/rag/controller/ChatEntryController.java b/rag-service/src/main/java/com/balex/rag/controller/ChatEntryController.java index 82b9486..6ff1045 100644 --- a/rag-service/src/main/java/com/balex/rag/controller/ChatEntryController.java +++ b/rag-service/src/main/java/com/balex/rag/controller/ChatEntryController.java @@ -9,6 +9,8 @@ import com.balex.rag.service.ChatEntryService; import com.balex.rag.service.ChatService; import com.balex.rag.service.EventPublisher; import com.balex.rag.utils.ApiUtils; +import com.balex.rag.utils.UserContext; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; @@ -26,15 +28,15 @@ public class ChatEntryController { private final ChatService chatService; private final RagDefaultsProperties ragDefaults; private final EventPublisher eventPublisher; - private final ApiUtils apiUtils; @PostMapping("/{chatId}") public ResponseEntity addUserEntry( @PathVariable Long chatId, - @RequestBody UserEntryRequest request) { + @RequestBody UserEntryRequest request, + HttpServletRequest httpRequest) { log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - Long ownerId = apiUtils.getUserIdFromAuthentication().longValue(); + Long ownerId = UserContext.getUserId(httpRequest).longValue(); boolean onlyContext = request.onlyContext() != null ? request.onlyContext() : ragDefaults.onlyContext(); double topP = request.topP() != null ? request.topP() : ragDefaults.topP(); diff --git a/rag-service/src/main/java/com/balex/rag/controller/DocumentUploadStreamController.java b/rag-service/src/main/java/com/balex/rag/controller/DocumentUploadStreamController.java index 9f3f607..ae82b95 100644 --- a/rag-service/src/main/java/com/balex/rag/controller/DocumentUploadStreamController.java +++ b/rag-service/src/main/java/com/balex/rag/controller/DocumentUploadStreamController.java @@ -1,11 +1,12 @@ package com.balex.rag.controller; import com.balex.rag.service.UserDocumentService; -import com.balex.rag.utils.ApiUtils; +import com.balex.rag.utils.UserContext; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -28,7 +29,6 @@ import java.util.List; public class DocumentUploadStreamController { private final UserDocumentService userDocumentService; - private final ApiUtils apiUtils; @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Document processing progress stream", @@ -39,9 +39,9 @@ public class DocumentUploadStreamController { }) @PostMapping(value = "/upload-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter uploadDocumentsWithProgress( - @RequestPart("files") @Valid List files - ) { - Integer userId = apiUtils.getUserIdFromAuthentication(); + @RequestPart("files") @Valid List files, + HttpServletRequest request) { + Integer userId = UserContext.getUserId(request); return userDocumentService.processUploadedFilesWithSse(files, userId.longValue()); } } \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/controller/UserController.java b/rag-service/src/main/java/com/balex/rag/controller/UserController.java index 1dba786..9b40e5e 100644 --- a/rag-service/src/main/java/com/balex/rag/controller/UserController.java +++ b/rag-service/src/main/java/com/balex/rag/controller/UserController.java @@ -11,6 +11,8 @@ import com.balex.rag.model.response.RagResponse; import com.balex.rag.service.EventPublisher; import com.balex.rag.service.UserService; import com.balex.rag.utils.ApiUtils; +import com.balex.rag.utils.UserContext; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -51,26 +53,25 @@ public class UserController { } @GetMapping("${end.points.userinfo}") - public ResponseEntity> getUserInfo( - @RequestHeader("Authorization") String token) { + public ResponseEntity> getUserInfo(HttpServletRequest request) { log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - RagResponse userInfo = userService.getUserInfo(token); + Integer userId = UserContext.getUserId(request); + RagResponse userInfo = userService.getUserInfo(userId); return ResponseEntity.ok(userInfo); } @DeleteMapping("${end.points.userinfo}") - public ResponseEntity> deleteUserDocuments( - @RequestHeader("Authorization") String token) { + public ResponseEntity> deleteUserDocuments(HttpServletRequest request) { log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - RagResponse deletedCount = userService.deleteUserDocuments(token); + Integer userId = UserContext.getUserId(request); + RagResponse deletedCount = userService.deleteUserDocuments(userId); return ResponseEntity.ok(deletedCount); } - @PutMapping("${end.points.id}") public ResponseEntity> updateUserById( @PathVariable(name = "id") Integer userId, @@ -80,14 +81,14 @@ public class UserController { RagResponse updatedPost = userService.updateUser(userId, request); return ResponseEntity.ok(updatedPost); } - @DeleteMapping("${end.points.id}") public ResponseEntity softDeleteUser( - @PathVariable(name = "id") Integer userId - ) { + @PathVariable(name = "id") Integer userId, + HttpServletRequest request) { log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName()); - userService.softDeleteUser(userId); + Integer currentUserId = UserContext.getUserId(request); + userService.softDeleteUser(userId, currentUserId); return ResponseEntity.ok().build(); } diff --git a/rag-service/src/main/java/com/balex/rag/filter/GatewayAuthFilter.java b/rag-service/src/main/java/com/balex/rag/filter/GatewayAuthFilter.java new file mode 100644 index 0000000..94e4ace --- /dev/null +++ b/rag-service/src/main/java/com/balex/rag/filter/GatewayAuthFilter.java @@ -0,0 +1,37 @@ +package com.balex.rag.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Slf4j +@Component +public class GatewayAuthFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String userId = request.getHeader("X-User-Id"); + String email = request.getHeader("X-User-Email"); + String username = request.getHeader("X-User-Name"); + String role = request.getHeader("X-User-Role"); + + if (userId != null) { + request.setAttribute("userId", userId); + request.setAttribute("userEmail", email); + request.setAttribute("userName", username); + request.setAttribute("userRole", role); + log.debug("Gateway user: id={}, email={}, role={}", userId, email, role); + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/mapper/UserMapper.java b/rag-service/src/main/java/com/balex/rag/mapper/UserMapper.java index 83911ec..605dda5 100644 --- a/rag-service/src/main/java/com/balex/rag/mapper/UserMapper.java +++ b/rag-service/src/main/java/com/balex/rag/mapper/UserMapper.java @@ -1,14 +1,11 @@ package com.balex.rag.mapper; import com.balex.rag.model.dto.UserDTO; -import com.balex.rag.model.dto.UserProfileDTO; import com.balex.rag.model.dto.UserSearchDTO; import com.balex.rag.model.entity.User; import com.balex.rag.model.enums.RegistrationStatus; import com.balex.rag.model.request.user.NewUserRequest; -import com.balex.rag.model.request.user.RegistrationUserRequest; import com.balex.rag.model.request.user.UpdateUserRequest; -import org.hibernate.type.descriptor.DateTimeUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; @@ -19,7 +16,7 @@ import java.util.Objects; @Mapper( componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE, - imports = {RegistrationStatus.class, Objects.class, DateTimeUtils.class} + imports = {RegistrationStatus.class, Objects.class} ) public interface UserMapper { @@ -36,16 +33,4 @@ public interface UserMapper { @Mapping(source = "deleted", target = "isDeleted") UserSearchDTO toUserSearchDto(User user); - - @Mapping(target = "username", source = "user.username") - @Mapping(target = "email", source = "user.email") - @Mapping(target = "token", source = "token") - @Mapping(target = "refreshToken", source = "refreshToken") - UserProfileDTO toUserProfileDto(User user, String token, String refreshToken); - - @Mapping(target = "password", ignore = true) - @Mapping(target = "registrationStatus", expression = "java(RegistrationStatus.ACTIVE)") - User fromDto(RegistrationUserRequest request); - -} - +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/model/constants/ApiConstants.java b/rag-service/src/main/java/com/balex/rag/model/constants/ApiConstants.java index 6cb3949..ce506cc 100644 --- a/rag-service/src/main/java/com/balex/rag/model/constants/ApiConstants.java +++ b/rag-service/src/main/java/com/balex/rag/model/constants/ApiConstants.java @@ -8,8 +8,6 @@ public final class ApiConstants { public static final String UNDEFINED = "undefined"; public static final String EMPTY_FILENAME = "unknown"; - public static final String NO_NEW_DOCUMENTS_UPLOADED = "No new documents uploaded"; - public static final String DOCUMENTS_UPLOADED = "Documents uploaded: "; public static final String ANSI_RED = "\u001B[31m"; public static final String ANSI_WHITE = "\u001B[37m"; public static final String BREAK_LINE = "\n"; diff --git a/rag-service/src/main/java/com/balex/rag/model/entity/RefreshToken.java b/rag-service/src/main/java/com/balex/rag/model/entity/RefreshToken.java deleted file mode 100644 index c15f9af..0000000 --- a/rag-service/src/main/java/com/balex/rag/model/entity/RefreshToken.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.balex.rag.model.entity; - -import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; - -import java.time.LocalDateTime; - -@Entity -@Getter -@Setter -@Table(name = "refresh_token") -public class RefreshToken { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - @Column(nullable = false, unique = true) - private String token; - - @Column(nullable = false) - private LocalDateTime created; - - @Column(name = "session_id") - private String sessionId; - - @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST) - @JoinColumn(name = "user_id", nullable = false) - private User user; -} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/model/exception/InvalidTokenException.java b/rag-service/src/main/java/com/balex/rag/model/exception/InvalidTokenException.java deleted file mode 100644 index cf2b2d2..0000000 --- a/rag-service/src/main/java/com/balex/rag/model/exception/InvalidTokenException.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.balex.rag.model.exception; - -public class InvalidTokenException extends RuntimeException { - - public InvalidTokenException(String message) { - super(message); - } - -} diff --git a/rag-service/src/main/java/com/balex/rag/model/request/user/LoginRequest.java b/rag-service/src/main/java/com/balex/rag/model/request/user/LoginRequest.java deleted file mode 100644 index 8e0bbf4..0000000 --- a/rag-service/src/main/java/com/balex/rag/model/request/user/LoginRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.balex.rag.model.request.user; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class LoginRequest implements Serializable { - - @Email - @NotNull - private String email; - - @NotEmpty - private String password; - -} - diff --git a/rag-service/src/main/java/com/balex/rag/model/request/user/RegistrationUserRequest.java b/rag-service/src/main/java/com/balex/rag/model/request/user/RegistrationUserRequest.java deleted file mode 100644 index 4a5a031..0000000 --- a/rag-service/src/main/java/com/balex/rag/model/request/user/RegistrationUserRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.balex.rag.model.request.user; - -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class RegistrationUserRequest implements Serializable { - - @NotBlank - private String username; - - @Email - @NotNull - private String email; - - @NotEmpty - private String password; - - @NotEmpty - private String confirmPassword; - -} diff --git a/rag-service/src/main/java/com/balex/rag/repo/RefreshTokenRepository.java b/rag-service/src/main/java/com/balex/rag/repo/RefreshTokenRepository.java deleted file mode 100644 index f90d1f1..0000000 --- a/rag-service/src/main/java/com/balex/rag/repo/RefreshTokenRepository.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.balex.rag.repo; - -import com.balex.rag.model.entity.RefreshToken; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Optional; - -public interface RefreshTokenRepository extends JpaRepository { - - Optional findByToken(String token); - - Optional findByUserId(Integer userId); - - Optional findByUserEmail(String email); -} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/security/JwtTokenProvider.java b/rag-service/src/main/java/com/balex/rag/security/JwtTokenProvider.java deleted file mode 100644 index ffb0129..0000000 --- a/rag-service/src/main/java/com/balex/rag/security/JwtTokenProvider.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.balex.rag.security; - -import com.balex.rag.model.entity.User; -import com.balex.rag.service.model.AuthenticationConstants; -import io.jsonwebtoken.*; -import io.jsonwebtoken.io.Decoders; -import io.jsonwebtoken.security.Keys; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.lang.NonNull; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.time.LocalDateTime; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -@Slf4j -@Component -public class JwtTokenProvider { - private final SecretKey secretKey; - private final Long jwtValidityInMilliseconds; - - public JwtTokenProvider(@Value("${jwt.secret}") String secret, - @Value("${jwt.expiration:3600000}") long jwtValidityInMilliseconds) { - this.secretKey = getKey(secret); - this.jwtValidityInMilliseconds = jwtValidityInMilliseconds; - } - - public String generateToken(@NonNull User user, String sessionId) { - Map claims = new HashMap<>(); - claims.put(AuthenticationConstants.USER_ID, user.getId()); - claims.put(AuthenticationConstants.USERNAME, user.getUsername()); - claims.put(AuthenticationConstants.USER_EMAIL, user.getEmail()); - claims.put(AuthenticationConstants.USER_REGISTRATION_STATUS, user.getRegistrationStatus().name()); - claims.put(AuthenticationConstants.SESSION_ID, sessionId); - claims.put(AuthenticationConstants.LAST_UPDATE, LocalDateTime.now().toString()); - - return createToken(claims, user.getEmail()); - } - - public String refreshToken(String token) { - Claims claims = getAllClaimsFromToken(token); - return createToken(claims, claims.getSubject()); - } - - public boolean validateToken(String token) { - try { - Jws claims = Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token); - return !claims.getBody().getExpiration().before(new Date()); - } catch (JwtException | IllegalArgumentException e) { - return false; - } - } - - public String getUsername(String token) { - Claims claims = getAllClaimsFromToken(token); - return claims.get(AuthenticationConstants.USERNAME).toString(); - } - - public String getUserId(String token) { - Claims claims = getAllClaimsFromToken(token); - return String.valueOf(claims.get(AuthenticationConstants.USER_ID)); - } - - - private Claims getAllClaimsFromToken(String token) { - try { - return Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody(); - } catch (ExpiredJwtException e) { - return e.getClaims(); - } - } - - private SecretKey getKey(String secretKey64) { - byte[] decode64 = Decoders.BASE64.decode(secretKey64); - return Keys.hmacShaKeyFor(decode64); - } - - private String createToken(Map claims, String subject) { - return Jwts.builder() - .setClaims(claims) - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + jwtValidityInMilliseconds)) - .signWith(secretKey, SignatureAlgorithm.HS512) - .compact(); - } - - public String getSessionId(String token) { - Claims claims = getAllClaimsFromToken(token); - return claims.get(AuthenticationConstants.SESSION_ID, String.class); - } - -} - diff --git a/rag-service/src/main/java/com/balex/rag/security/filter/JwtRequestFilter.java b/rag-service/src/main/java/com/balex/rag/security/filter/JwtRequestFilter.java deleted file mode 100644 index a1b57ed..0000000 --- a/rag-service/src/main/java/com/balex/rag/security/filter/JwtRequestFilter.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.balex.rag.security.filter; - -import com.balex.rag.model.constants.ApiErrorMessage; -import com.balex.rag.security.JwtTokenProvider; -import com.balex.rag.service.impl.RefreshTokenServiceImpl; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureException; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.NotNull; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static com.balex.rag.model.constants.ApiConstants.USER_ROLE; - -@Slf4j -@Component -@RequiredArgsConstructor -public class JwtRequestFilter extends OncePerRequestFilter { - private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String BEARER_PREFIX = "Bearer "; - private static final String LOGIN_PATH = "/auth/login"; - private static final String REGISTER_PATH = "/auth/register"; - - private final JwtTokenProvider jwtTokenProvider; - private final RefreshTokenServiceImpl refreshTokenService; - - @Override - protected void doFilterInternal( - @NotNull HttpServletRequest request, - @NotNull HttpServletResponse response, - @NotNull FilterChain filterChain) - throws ServletException, IOException { - - log.info("JWT filter: method={}, uri={}, header={}", - request.getMethod(), - request.getRequestURI(), - request.getHeader(AUTHORIZATION_HEADER)); - - Optional authHeader = Optional.ofNullable(request.getHeader(AUTHORIZATION_HEADER)); - String requestURI = request.getRequestURI(); - - if (authHeader.isPresent() && authHeader.get().startsWith(BEARER_PREFIX)) { - String jwt = authHeader.get().substring(BEARER_PREFIX.length()); - try { - if (!jwtTokenProvider.validateToken(jwt)) { - throw new ExpiredJwtException(null, null, ApiErrorMessage.TOKEN_EXPIRED.getMessage()); - } - - Optional emailOpt = Optional.ofNullable(jwtTokenProvider.getUsername(jwt)); - Optional userIdOpt = Optional.ofNullable(jwtTokenProvider.getUserId(jwt)); - String sessionId = jwtTokenProvider.getSessionId(jwt); - - if (emailOpt.isPresent() && userIdOpt.isPresent()) { - // Check session validity - Optional activeSessionId = refreshTokenService.getSessionIdByEmail(emailOpt.get()); -// if (activeSessionId.isEmpty() || !activeSessionId.get().equals(sessionId)) { -// sendErrorResponse(response, HttpStatus.UNAUTHORIZED, "SESSION_INVALIDATED"); -// return; -// } - - if (SecurityContextHolder.getContext().getAuthentication() == null) { - List authorities = Collections.singletonList(new SimpleGrantedAuthority(USER_ROLE)); - - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( - emailOpt.get(), - jwt, - authorities - ); - SecurityContextHolder.getContext().setAuthentication(authenticationToken); - } - } - - } catch (ExpiredJwtException e) { - handleTokenExpiration(requestURI, jwt, response); - return; - } catch (SignatureException | MalformedJwtException e) { - handleSignatureException(response); - return; - } catch (Exception e) { - handleUnexpectedException(response, e); - return; - } - } - filterChain.doFilter(request, response); - } - - private void handleTokenExpiration(String requestURI, String jwt, HttpServletResponse response) throws IOException { - if (isAuthEndpoint(requestURI)) { - String refreshedToken = jwtTokenProvider.refreshToken(jwt); - response.setHeader(AUTHORIZATION_HEADER, BEARER_PREFIX + refreshedToken); - } else { - sendErrorResponse(response, HttpStatus.UNAUTHORIZED, ApiErrorMessage.TOKEN_EXPIRED.getMessage()); - } - } - - private void handleSignatureException(HttpServletResponse response) throws IOException { - sendErrorResponse(response, HttpStatus.UNAUTHORIZED, ApiErrorMessage.INVALID_TOKEN_SIGNATURE.getMessage()); - } - - private void handleUnexpectedException(HttpServletResponse response, Exception e) throws IOException { - log.error(ApiErrorMessage.ERROR_DURING_JWT_PROCESSING.getMessage(), e); - sendErrorResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, ApiErrorMessage.UNEXPECTED_ERROR_OCCURRED.getMessage()); - } - - private void sendErrorResponse(HttpServletResponse response, HttpStatus status, String message) throws IOException { - response.setStatus(status.value()); - response.getWriter().write(message); - } - - private boolean isAuthEndpoint(String uri) { - return uri.equals(LOGIN_PATH) || uri.equals(REGISTER_PATH); - } -} - - diff --git a/rag-service/src/main/java/com/balex/rag/security/handler/AccessRestrictionHandler.java b/rag-service/src/main/java/com/balex/rag/security/handler/AccessRestrictionHandler.java deleted file mode 100644 index 4bfeb02..0000000 --- a/rag-service/src/main/java/com/balex/rag/security/handler/AccessRestrictionHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.balex.rag.security.handler; - -import com.balex.rag.model.constants.ApiErrorMessage; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.SneakyThrows; -import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.stereotype.Component; - -@Component -public class AccessRestrictionHandler implements AccessDeniedHandler { - - @Override - @SneakyThrows - public void handle(HttpServletRequest request, - HttpServletResponse response, - AccessDeniedException accessDeniedException) { - response.setStatus(HttpStatus.FORBIDDEN.value()); - response.getWriter().write(ApiErrorMessage.HAVE_NO_ACCESS.getMessage()); - } - -} - diff --git a/rag-service/src/main/java/com/balex/rag/security/validation/AccessValidator.java b/rag-service/src/main/java/com/balex/rag/security/validation/AccessValidator.java index 90b692d..c035964 100644 --- a/rag-service/src/main/java/com/balex/rag/security/validation/AccessValidator.java +++ b/rag-service/src/main/java/com/balex/rag/security/validation/AccessValidator.java @@ -1,14 +1,7 @@ package com.balex.rag.security.validation; import com.balex.rag.model.constants.ApiErrorMessage; -import com.balex.rag.model.entity.User; -import com.balex.rag.model.exception.InvalidDataException; -import com.balex.rag.model.exception.InvalidPasswordException; -import com.balex.rag.model.exception.NotFoundException; import com.balex.rag.repo.UserRepository; -import com.balex.rag.service.model.exception.DataExistException; -import com.balex.rag.utils.ApiUtils; -import com.balex.rag.utils.PasswordUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.stereotype.Component; @@ -19,34 +12,11 @@ import java.nio.file.AccessDeniedException; @RequiredArgsConstructor public class AccessValidator { private final UserRepository userRepository; - private final ApiUtils apiUtils; - - public void validateNewUser(String username, String email, String password, String confirmPassword) { - userRepository.findByUsername(username).ifPresent(existingUser -> { - throw new DataExistException(ApiErrorMessage.USERNAME_ALREADY_EXISTS.getMessage(username)); - }); - - userRepository.findByEmail(email).ifPresent(existingUser -> { - throw new DataExistException(ApiErrorMessage.EMAIL_ALREADY_EXISTS.getMessage(email)); - }); - - if (!password.equals(confirmPassword)) { - throw new InvalidDataException(ApiErrorMessage.MISMATCH_PASSWORDS.getMessage()); - } - - if (PasswordUtils.isNotValidPassword(password)) { - throw new InvalidPasswordException(ApiErrorMessage.INVALID_PASSWORD.getMessage()); - } - } @SneakyThrows - public void validateOwnerAccess(Integer ownerId) { - Integer currentUserId = apiUtils.getUserIdFromAuthentication(); - + public void validateOwnerAccess(Integer ownerId, Integer currentUserId) { if (!currentUserId.equals(ownerId)) { throw new AccessDeniedException(ApiErrorMessage.HAVE_NO_ACCESS.getMessage()); } } - -} - +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/security/validation/PasswordMatchesValidator.java b/rag-service/src/main/java/com/balex/rag/security/validation/PasswordMatchesValidator.java deleted file mode 100644 index 7b58c2c..0000000 --- a/rag-service/src/main/java/com/balex/rag/security/validation/PasswordMatchesValidator.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.balex.rag.security.validation; - -import com.balex.rag.model.request.user.RegistrationUserRequest; -import com.balex.rag.utils.PasswordMatches; -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; - -public class PasswordMatchesValidator implements ConstraintValidator { - - @Override - public boolean isValid(RegistrationUserRequest request, ConstraintValidatorContext constraintValidatorContext) { - return request.getPassword().equals(request.getConfirmPassword()); - } - -} - diff --git a/rag-service/src/main/java/com/balex/rag/service/AuthService.java b/rag-service/src/main/java/com/balex/rag/service/AuthService.java deleted file mode 100644 index bb88578..0000000 --- a/rag-service/src/main/java/com/balex/rag/service/AuthService.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.balex.rag.service; - -import com.balex.rag.model.dto.UserProfileDTO; -import com.balex.rag.model.request.user.LoginRequest; -import com.balex.rag.model.request.user.RegistrationUserRequest; -import com.balex.rag.model.response.RagResponse; - -public interface AuthService { - - RagResponse login(LoginRequest request); - - RagResponse refreshAccessToken(String refreshToken); - - RagResponse registerUser(RegistrationUserRequest request); - -} diff --git a/rag-service/src/main/java/com/balex/rag/service/ChatService.java b/rag-service/src/main/java/com/balex/rag/service/ChatService.java index b4473c8..e07fc5f 100644 --- a/rag-service/src/main/java/com/balex/rag/service/ChatService.java +++ b/rag-service/src/main/java/com/balex/rag/service/ChatService.java @@ -7,7 +7,7 @@ import java.util.List; public interface ChatService { - Chat createNewChat(String title); + Chat createNewChat(String title, Long ownerId); List getAllChatsByOwner(Long ownerId); diff --git a/rag-service/src/main/java/com/balex/rag/service/RefreshTokenService.java b/rag-service/src/main/java/com/balex/rag/service/RefreshTokenService.java deleted file mode 100644 index 1e7b71c..0000000 --- a/rag-service/src/main/java/com/balex/rag/service/RefreshTokenService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.balex.rag.service; - -import com.balex.rag.model.entity.RefreshToken; -import com.balex.rag.model.entity.User; - -public interface RefreshTokenService { - - RefreshToken generateOrUpdateRefreshToken(User user); - - RefreshToken validateAndRefreshToken(String refreshToken); - -} diff --git a/rag-service/src/main/java/com/balex/rag/service/UserService.java b/rag-service/src/main/java/com/balex/rag/service/UserService.java index cd2ab88..87a8db1 100644 --- a/rag-service/src/main/java/com/balex/rag/service/UserService.java +++ b/rag-service/src/main/java/com/balex/rag/service/UserService.java @@ -9,9 +9,8 @@ import com.balex.rag.model.response.PaginationResponse; import com.balex.rag.model.response.RagResponse; import jakarta.validation.constraints.NotNull; import org.springframework.data.domain.Pageable; -import org.springframework.security.core.userdetails.UserDetailsService; -public interface UserService extends UserDetailsService { +public interface UserService { RagResponse getById(@NotNull Integer userId); @@ -19,15 +18,11 @@ public interface UserService extends UserDetailsService { RagResponse updateUser(@NotNull Integer postId, @NotNull UpdateUserRequest request); - void softDeleteUser(Integer userId); + void softDeleteUser(Integer userId, Integer currentUserId); RagResponse> findAllUsers(Pageable pageable); - RagResponse getUserInfo(String token); - - RagResponse deleteUserDocuments(String token); - -} - - + RagResponse getUserInfo(Integer userId); + RagResponse deleteUserDocuments(Integer userId); +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/service/impl/AuthServiceImpl.java b/rag-service/src/main/java/com/balex/rag/service/impl/AuthServiceImpl.java deleted file mode 100644 index cc04ba9..0000000 --- a/rag-service/src/main/java/com/balex/rag/service/impl/AuthServiceImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.balex.rag.service.impl; - -import com.balex.rag.mapper.UserMapper; -import com.balex.rag.model.constants.ApiErrorMessage; -import com.balex.rag.model.dto.UserProfileDTO; -import com.balex.rag.model.entity.RefreshToken; -import com.balex.rag.model.entity.User; -import com.balex.rag.model.exception.InvalidDataException; -import com.balex.rag.model.request.user.LoginRequest; -import com.balex.rag.model.request.user.RegistrationUserRequest; -import com.balex.rag.model.response.RagResponse; -import com.balex.rag.repo.UserRepository; -import com.balex.rag.security.JwtTokenProvider; -import com.balex.rag.security.validation.AccessValidator; -import com.balex.rag.service.AuthService; -import com.balex.rag.service.RefreshTokenService; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; - -import java.util.HashSet; -import java.util.Set; - -@Slf4j -@Service -@AllArgsConstructor -public class AuthServiceImpl implements AuthService { - private final UserRepository userRepository; - private final UserMapper userMapper; - private final JwtTokenProvider jwtTokenProvider; - private final AuthenticationManager authenticationManager; - private final RefreshTokenService refreshTokenService; - private final PasswordEncoder passwordEncoder; - private final AccessValidator accessValidator; - - - @Override - public RagResponse login(@NotNull LoginRequest request) { - try { - authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(request.getEmail(), request.getPassword()) - ); - } catch (BadCredentialsException e) { - throw new InvalidDataException(ApiErrorMessage.INVALID_USER_OR_PASSWORD.getMessage()); - } - - User user = userRepository.findUserByEmailAndDeletedFalse(request.getEmail()) - .orElseThrow(() -> new InvalidDataException(ApiErrorMessage.INVALID_USER_OR_PASSWORD.getMessage())); - - RefreshToken refreshToken = refreshTokenService.generateOrUpdateRefreshToken(user); - String token = jwtTokenProvider.generateToken(user, refreshToken.getSessionId()); - UserProfileDTO userProfileDTO = userMapper.toUserProfileDto(user, token, refreshToken.getToken()); - userProfileDTO.setToken(token); - - return RagResponse.createSuccessfulWithNewToken(userProfileDTO); - } - - @Override - public RagResponse refreshAccessToken(String refreshTokenValue) { - RefreshToken refreshToken = refreshTokenService.validateAndRefreshToken(refreshTokenValue); - User user = refreshToken.getUser(); - - String accessToken = jwtTokenProvider.generateToken(user, refreshToken.getSessionId()); - - return RagResponse.createSuccessfulWithNewToken( - userMapper.toUserProfileDto(user, accessToken, refreshToken.getToken()) - ); - } - - @Override - public RagResponse registerUser(@NotNull RegistrationUserRequest request) { - accessValidator.validateNewUser( - request.getUsername(), - request.getEmail(), - request.getPassword(), - request.getConfirmPassword() - ); - - User newUser = userMapper.fromDto(request); - newUser.setPassword(passwordEncoder.encode(request.getPassword())); - userRepository.save(newUser); - - RefreshToken refreshToken = refreshTokenService.generateOrUpdateRefreshToken(newUser); - String token = jwtTokenProvider.generateToken(newUser, refreshToken.getSessionId()); - UserProfileDTO userProfileDTO = userMapper.toUserProfileDto(newUser, token, refreshToken.getToken()); - userProfileDTO.setToken(token); - - return RagResponse.createSuccessfulWithNewToken(userProfileDTO); - } - -} - diff --git a/rag-service/src/main/java/com/balex/rag/service/impl/ChatServiceImpl.java b/rag-service/src/main/java/com/balex/rag/service/impl/ChatServiceImpl.java index 92941cf..cde046f 100644 --- a/rag-service/src/main/java/com/balex/rag/service/impl/ChatServiceImpl.java +++ b/rag-service/src/main/java/com/balex/rag/service/impl/ChatServiceImpl.java @@ -3,13 +3,11 @@ package com.balex.rag.service.impl; import com.balex.rag.model.entity.Chat; import com.balex.rag.repo.ChatRepository; import com.balex.rag.service.ChatService; -import com.balex.rag.utils.ApiUtils; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.model.ChatResponse; -import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -23,25 +21,19 @@ public class ChatServiceImpl implements ChatService { private final ChatRepository chatRepo; private final ChatClient chatClient; - private final ApiUtils apiUtils; public List getAllChatsByOwner(Long ownerId) { return chatRepo.findByIdOwnerOrderByCreatedAtDesc(ownerId); } - public Chat createNewChat(String title) { - Long ownerId = apiUtils.getUserIdFromAuthentication().longValue(); - Chat chat = Chat.builder() - .title(title) - .idOwner(ownerId) - .build(); + public Chat createNewChat(String title, Long ownerId) { + Chat chat = Chat.builder().title(title).idOwner(ownerId).build(); chatRepo.save(chat); return chat; } public Chat getChat(Long chatId, Long ownerId) { - Chat chat = chatRepo.findById(chatId).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); + Chat chat = chatRepo.findById(chatId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found")); if (!chat.getIdOwner().equals(ownerId)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied"); } @@ -57,15 +49,7 @@ public class ChatServiceImpl implements ChatService { SseEmitter sseEmitter = new SseEmitter(0L); final StringBuilder answer = new StringBuilder(); - chatClient - .prompt(userPrompt) - .advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId)) - .stream() - .chatResponse() - .subscribe( - (ChatResponse response) -> processToken(response, sseEmitter, answer), - sseEmitter::completeWithError, - sseEmitter::complete); + chatClient.prompt(userPrompt).advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId)).stream().chatResponse().subscribe((ChatResponse response) -> processToken(response, sseEmitter, answer), sseEmitter::completeWithError, sseEmitter::complete); return sseEmitter; } diff --git a/rag-service/src/main/java/com/balex/rag/service/impl/RefreshTokenServiceImpl.java b/rag-service/src/main/java/com/balex/rag/service/impl/RefreshTokenServiceImpl.java deleted file mode 100644 index f5fb107..0000000 --- a/rag-service/src/main/java/com/balex/rag/service/impl/RefreshTokenServiceImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.balex.rag.service.impl; - -import com.balex.rag.model.constants.ApiErrorMessage; -import com.balex.rag.model.entity.RefreshToken; -import com.balex.rag.model.entity.User; -import com.balex.rag.model.exception.NotFoundException; -import com.balex.rag.repo.RefreshTokenRepository; -import com.balex.rag.service.RefreshTokenService; -import com.balex.rag.utils.ApiUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; - -import java.time.LocalDateTime; -import java.util.Optional; - -@Slf4j -@Service -@RequiredArgsConstructor -public class RefreshTokenServiceImpl implements RefreshTokenService { - private final RefreshTokenRepository refreshTokenRepository; - - @Override - public RefreshToken generateOrUpdateRefreshToken(User user) { - String sessionId = ApiUtils.generateUuidWithoutDash(); - return refreshTokenRepository.findByUserId(user.getId()) - .map(refreshToken -> { - refreshToken.setCreated(LocalDateTime.now()); - refreshToken.setToken(ApiUtils.generateUuidWithoutDash()); - refreshToken.setSessionId(sessionId); - return refreshTokenRepository.save(refreshToken); - }) - .orElseGet(() -> { - RefreshToken newToken = new RefreshToken(); - newToken.setUser(user); - newToken.setCreated(LocalDateTime.now()); - newToken.setToken(ApiUtils.generateUuidWithoutDash()); - newToken.setSessionId(sessionId); - return refreshTokenRepository.save(newToken); - }); - } - - @Override - public RefreshToken validateAndRefreshToken(String requestRefreshToken) { - RefreshToken refreshToken = refreshTokenRepository.findByToken(requestRefreshToken) - .orElseThrow(() -> new NotFoundException(ApiErrorMessage.NOT_FOUND_REFRESH_TOKEN.getMessage())); - - refreshToken.setCreated(LocalDateTime.now()); - refreshToken.setToken(ApiUtils.generateUuidWithoutDash()); - return refreshTokenRepository.save(refreshToken); - } - - public Optional getSessionIdByEmail(String email) { - return refreshTokenRepository.findByUserEmail(email) - .map(RefreshToken::getSessionId); - } -} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/service/impl/UserServiceImpl.java b/rag-service/src/main/java/com/balex/rag/service/impl/UserServiceImpl.java index 24aed75..cba4559 100644 --- a/rag-service/src/main/java/com/balex/rag/service/impl/UserServiceImpl.java +++ b/rag-service/src/main/java/com/balex/rag/service/impl/UserServiceImpl.java @@ -8,7 +8,6 @@ import com.balex.rag.model.dto.UserSearchDTO; import com.balex.rag.model.entity.LoadedDocumentInfo; import com.balex.rag.model.entity.User; import com.balex.rag.model.entity.UserInfo; -import com.balex.rag.model.exception.InvalidTokenException; import com.balex.rag.model.exception.NotFoundException; import com.balex.rag.model.request.user.NewUserRequest; import com.balex.rag.model.request.user.UpdateUserRequest; @@ -17,7 +16,6 @@ import com.balex.rag.model.response.RagResponse; import com.balex.rag.repo.DocumentRepository; import com.balex.rag.repo.UserRepository; import com.balex.rag.repo.VectorStoreRepository; -import com.balex.rag.security.JwtTokenProvider; import com.balex.rag.security.validation.AccessValidator; import com.balex.rag.service.UserService; import com.balex.rag.service.model.exception.DataExistException; @@ -25,19 +23,12 @@ import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.Collections; import java.util.List; -import static com.balex.rag.model.constants.ApiConstants.USER_ROLE; - @Service @RequiredArgsConstructor public class UserServiceImpl implements UserService { @@ -45,7 +36,6 @@ public class UserServiceImpl implements UserService { private final UserMapper userMapper; private final PasswordEncoder passwordEncoder; private final AccessValidator accessValidator; - private final JwtTokenProvider jwtTokenProvider; private final DocumentRepository documentRepository; private final VectorStoreRepository vectorStoreRepository; @@ -98,15 +88,14 @@ public class UserServiceImpl implements UserService { @Override @Transactional - public void softDeleteUser(Integer userId) { + public void softDeleteUser(Integer userId, Integer currentUserId) { User user = userRepository.findByIdAndDeletedFalse(userId) .orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId))); - accessValidator.validateOwnerAccess(userId); + accessValidator.validateOwnerAccess(userId, currentUserId); user.setDeleted(true); userRepository.save(user); - } @Override @@ -128,28 +117,11 @@ public class UserServiceImpl implements UserService { return RagResponse.createSuccessful(paginationResponse); } - @Override - public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { - return getUserDetails(email, userRepository); - } - - static UserDetails getUserDetails(String email, UserRepository userRepository) { - User user = userRepository.findByEmail(email) - .orElseThrow(() -> new NotFoundException(ApiErrorMessage.EMAIL_NOT_FOUND.getMessage(email))); - - user.setLastLogin(LocalDateTime.now()); - userRepository.save(user); - return new org.springframework.security.core.userdetails.User( - user.getEmail(), - user.getPassword(), - Collections.singletonList(new SimpleGrantedAuthority(USER_ROLE)) - ); - } - @Override @Transactional(readOnly = true) - public RagResponse getUserInfo(String token) { - User user = getUserInfoFromToken(token); + public RagResponse getUserInfo(Integer userId) { + User user = userRepository.findByIdAndDeletedFalse(userId) + .orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId))); List loadedFiles = documentRepository .findByUserId(user.getId()) @@ -167,9 +139,9 @@ public class UserServiceImpl implements UserService { @Override @Transactional - public RagResponse deleteUserDocuments(String token) { - getUserInfoFromToken(token); - User user = getUserInfoFromToken(token); + public RagResponse deleteUserDocuments(Integer userId) { + User user = userRepository.findByIdAndDeletedFalse(userId) + .orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId))); List documents = documentRepository.findByUserId(user.getId()); @@ -177,28 +149,9 @@ public class UserServiceImpl implements UserService { return RagResponse.createSuccessful(0); } - // Удаляем чанки по user_id vectorStoreRepository.deleteByUserId(user.getId().longValue()); - - // Удаляем записи из loaded_document documentRepository.deleteAll(documents); return RagResponse.createSuccessful(documents.size()); } - - private User getUserInfoFromToken(String token) { - if (token == null || token.isBlank()) { - throw new InvalidTokenException("Token is empty or null"); - } - - String cleanToken = token.startsWith("Bearer ") - ? token.substring(7) - : token; - - String username = jwtTokenProvider.getUsername(cleanToken); - - return userRepository.findByUsername(username) - .orElseThrow(() -> new InvalidTokenException("User not found: " + username)); - } -} - +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/utils/ApiUtils.java b/rag-service/src/main/java/com/balex/rag/utils/ApiUtils.java index a6ef40b..a87affb 100644 --- a/rag-service/src/main/java/com/balex/rag/utils/ApiUtils.java +++ b/rag-service/src/main/java/com/balex/rag/utils/ApiUtils.java @@ -1,53 +1,16 @@ package com.balex.rag.utils; -import com.balex.rag.model.constants.ApiConstants; -import com.balex.rag.security.JwtTokenProvider; -import jakarta.servlet.http.Cookie; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.StringUtils; -import org.springframework.http.HttpHeaders; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; - -import java.util.UUID; - -@Component -@RequiredArgsConstructor -public class ApiUtils { - - private final JwtTokenProvider jwtTokenProvider; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class ApiUtils { public static String getMethodName() { try { return new Throwable().getStackTrace()[1].getMethodName(); } catch (Exception cause) { - return ApiConstants.UNDEFINED; + return "undefined"; } } - - public static Cookie createAuthCookie(String value) { - Cookie authorizationCookie = new Cookie(HttpHeaders.AUTHORIZATION, value); - authorizationCookie.setHttpOnly(true); - authorizationCookie.setSecure(true); - authorizationCookie.setPath("/"); - authorizationCookie.setMaxAge(300); - return authorizationCookie; - } - - public static String generateUuidWithoutDash() { - return UUID.randomUUID().toString().replace(ApiConstants.DASH, StringUtils.EMPTY); - } - - - public static String getCurrentUsername() { - return SecurityContextHolder.getContext().getAuthentication().getName(); - } - - public Integer getUserIdFromAuthentication() { - String jwtToken = SecurityContextHolder.getContext().getAuthentication().getCredentials().toString(); - return Integer.parseInt(jwtTokenProvider.getUserId(jwtToken)); - } - -} - +} \ No newline at end of file diff --git a/rag-service/src/main/java/com/balex/rag/utils/PasswordMatches.java b/rag-service/src/main/java/com/balex/rag/utils/PasswordMatches.java deleted file mode 100644 index 1f1ae9e..0000000 --- a/rag-service/src/main/java/com/balex/rag/utils/PasswordMatches.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.balex.rag.utils; - -import com.balex.rag.security.validation.PasswordMatchesValidator; -import jakarta.validation.Constraint; -import jakarta.validation.Payload; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Constraint(validatedBy = PasswordMatchesValidator.class) -@Target({ ElementType.TYPE }) -@Retention(RetentionPolicy.RUNTIME) -public @interface PasswordMatches { - String message() default "Passwords do not match"; - - Class[] groups() default {}; - - Class[] payload() default {}; -} - - diff --git a/rag-service/src/main/java/com/balex/rag/utils/PasswordUtils.java b/rag-service/src/main/java/com/balex/rag/utils/PasswordUtils.java index ebc73df..4c9602c 100644 --- a/rag-service/src/main/java/com/balex/rag/utils/PasswordUtils.java +++ b/rag-service/src/main/java/com/balex/rag/utils/PasswordUtils.java @@ -54,10 +54,6 @@ public final class PasswordUtils { int lettersUCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD; int lettersLCaseNumber = ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH - charactersNumber - digitsNumber - lettersUCaseNumber; -// String characters = RandomStringUtils.random(charactersNumber, ApiConstants.PASSWORD_CHARACTERS); -// String digits = RandomStringUtils.random(digitsNumber, ApiConstants.PASSWORD_DIGITS); -// String lettersUCase = RandomStringUtils.random(lettersUCaseNumber, ApiConstants.PASSWORD_LETTERS_UPPER_CASE); -// String lettersLCase = RandomStringUtils.random(lettersLCaseNumber, ApiConstants.PASSWORD_LETTERS_LOWER_CASE); String characters = randomFromChars(charactersNumber, ApiConstants.PASSWORD_CHARACTERS); String digits = randomFromChars(digitsNumber, ApiConstants.PASSWORD_DIGITS); String lettersUCase = randomFromChars(lettersUCaseNumber, ApiConstants.PASSWORD_LETTERS_UPPER_CASE); diff --git a/rag-service/src/main/java/com/balex/rag/utils/UserContext.java b/rag-service/src/main/java/com/balex/rag/utils/UserContext.java new file mode 100644 index 0000000..a6f8392 --- /dev/null +++ b/rag-service/src/main/java/com/balex/rag/utils/UserContext.java @@ -0,0 +1,25 @@ +package com.balex.rag.utils; + +import jakarta.servlet.http.HttpServletRequest; + +public final class UserContext { + + private UserContext() {} + + public static Integer getUserId(HttpServletRequest request) { + String userId = (String) request.getAttribute("userId"); + return userId != null ? Integer.parseInt(userId) : null; + } + + public static String getUserEmail(HttpServletRequest request) { + return (String) request.getAttribute("userEmail"); + } + + public static String getUserName(HttpServletRequest request) { + return (String) request.getAttribute("userName"); + } + + public static String getUserRole(HttpServletRequest request) { + return (String) request.getAttribute("userRole"); + } +} \ No newline at end of file diff --git a/rag-service/src/main/resources/application.properties b/rag-service/src/main/resources/application.properties index 00befa7..91b6e94 100644 --- a/rag-service/src/main/resources/application.properties +++ b/rag-service/src/main/resources/application.properties @@ -23,8 +23,6 @@ spring.cloud.consul.discovery.health-check-interval=10s management.endpoints.web.exposure.include=health,info management.endpoint.health.show-details=always -jwt.secret=${JWT_SECRET:ywfI6dBznYmHbokihB/OBzZz6E0Fj+6PiqrM8dQ5c3t0HeYarblCbOGM8vQtOt472AtQ+MsCH7OVIKHOzjrPsQ==} -jwt.expiration=103600000 spring.datasource.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/ragdb} spring.datasource.username=${SPRING_DATASOURCE_USERNAME:postgres} spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:postgres}