auth gateway refact
This commit is contained in:
@@ -19,7 +19,7 @@ public class R2dbcConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public R2dbcCustomConversions r2dbcCustomConversions() {
|
public R2dbcCustomConversions r2dbcCustomConversions() {
|
||||||
return R2dbcCustomConversions.of(
|
return R2dbcCustomConversions.of(
|
||||||
PostgresDialect.INSTANCE.getStoreConversions(),
|
PostgresDialect.INSTANCE,
|
||||||
List.of(
|
List.of(
|
||||||
new RegistrationStatusReadConverter(),
|
new RegistrationStatusReadConverter(),
|
||||||
new RegistrationStatusWriteConverter(),
|
new RegistrationStatusWriteConverter(),
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package com.posthub.gateway.controller;
|
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.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 com.posthub.gateway.service.AuthService;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseCookie;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@@ -19,22 +21,46 @@ public class AuthController {
|
|||||||
|
|
||||||
private final AuthService authService;
|
private final AuthService authService;
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/login")
|
||||||
public Mono<ResponseEntity<JwtResponse>> register(@Valid @RequestBody RegistrationUserRequest request) {
|
public Mono<ResponseEntity<RagResponse<UserProfileDTO>>> login(
|
||||||
return authService.register(request)
|
@RequestBody @Valid LoginRequest request,
|
||||||
.map(response -> ResponseEntity.status(HttpStatus.CREATED).body(response));
|
ServerHttpResponse response) {
|
||||||
|
return authService.login(request)
|
||||||
|
.map(result -> {
|
||||||
|
addAuthCookie(response, result.getPayload().getToken());
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/register")
|
||||||
public Mono<ResponseEntity<JwtResponse>> login(@Valid @RequestBody LoginUserRequest request) {
|
public Mono<ResponseEntity<RagResponse<UserProfileDTO>>> register(
|
||||||
return authService.login(request)
|
@RequestBody @Valid RegistrationUserRequest request,
|
||||||
.map(ResponseEntity::ok);
|
ServerHttpResponse response) {
|
||||||
|
return authService.register(request)
|
||||||
|
.map(result -> {
|
||||||
|
addAuthCookie(response, result.getPayload().getToken());
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/refresh/token")
|
@GetMapping("/refresh/token")
|
||||||
public Mono<ResponseEntity<JwtResponse>> refreshToken(
|
public Mono<ResponseEntity<RagResponse<UserProfileDTO>>> refreshToken(
|
||||||
@RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader) {
|
@RequestParam(name = "token") String refreshToken,
|
||||||
return authService.refreshToken(authHeader)
|
ServerHttpResponse response) {
|
||||||
.map(ResponseEntity::ok);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
package com.posthub.gateway.model.request;
|
package com.posthub.gateway.model.request;
|
||||||
|
|
||||||
import jakarta.validation.constraints.Email;
|
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.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
public class LoginUserRequest {
|
public class LoginRequest {
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Email
|
@Email
|
||||||
|
@NotNull
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
@NotBlank
|
@NotEmpty
|
||||||
private String password;
|
private String password;
|
||||||
}
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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<P> {
|
||||||
|
|
||||||
|
private String message;
|
||||||
|
private P payload;
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
public static <P> RagResponse<P> createSuccessful(P payload) {
|
||||||
|
return new RagResponse<>("", payload, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <P> RagResponse<P> createSuccessfulWithNewToken(P payload) {
|
||||||
|
return new RagResponse<>("Token created or updated", payload, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<RefreshToken, Integer> {
|
||||||
|
|
||||||
|
Mono<RefreshToken> findByToken(String token);
|
||||||
|
|
||||||
|
Mono<RefreshToken> findByUserId(Integer userId);
|
||||||
|
}
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
package com.posthub.gateway.service;
|
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.entity.User;
|
||||||
import com.posthub.gateway.model.enums.RegistrationStatus;
|
import com.posthub.gateway.model.enums.RegistrationStatus;
|
||||||
import com.posthub.gateway.model.enums.UserRole;
|
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.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.repository.UserRepository;
|
||||||
import com.posthub.gateway.security.JwtTokenProvider;
|
import com.posthub.gateway.security.JwtTokenProvider;
|
||||||
|
import com.posthub.gateway.util.PasswordUtils;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@@ -25,20 +30,49 @@ import java.util.UUID;
|
|||||||
public class AuthService {
|
public class AuthService {
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final RefreshTokenRepository refreshTokenRepository;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
public Mono<JwtResponse> register(RegistrationUserRequest request) {
|
public Mono<RagResponse<UserProfileDTO>> login(LoginRequest request) {
|
||||||
if (!request.getPassword().equals(request.getConfirmPassword())) {
|
return userRepository.findByEmail(request.getEmail())
|
||||||
return Mono.error(new ResponseStatusException(HttpStatus.BAD_REQUEST, "Passwords do not match"));
|
.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, ApiErrorMessage.INVALID_USER_OR_PASSWORD.getMessage()));
|
||||||
|
}
|
||||||
|
if (user.getRegistrationStatus() != RegistrationStatus.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);
|
||||||
|
})
|
||||||
|
.flatMap(this::generateTokensAndBuildResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
return userRepository.existsByEmail(request.getEmail())
|
public Mono<RagResponse<UserProfileDTO>> 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.<User>error(new ResponseStatusException(
|
||||||
|
HttpStatus.CONFLICT, ApiErrorMessage.USERNAME_ALREADY_EXISTS.getMessage(request.getUsername()))))
|
||||||
|
.switchIfEmpty(userRepository.existsByEmail(request.getEmail())
|
||||||
.flatMap(exists -> {
|
.flatMap(exists -> {
|
||||||
if (exists) {
|
if (exists) {
|
||||||
return Mono.error(new ResponseStatusException(HttpStatus.CONFLICT, "Email already registered"));
|
return Mono.<User>error(new ResponseStatusException(
|
||||||
|
HttpStatus.CONFLICT, ApiErrorMessage.EMAIL_ALREADY_EXISTS.getMessage(request.getEmail())));
|
||||||
}
|
}
|
||||||
|
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setUsername(request.getUsername());
|
user.setUsername(request.getUsername());
|
||||||
user.setEmail(request.getEmail());
|
user.setEmail(request.getEmail());
|
||||||
@@ -48,61 +82,65 @@ public class AuthService {
|
|||||||
user.setCreated(LocalDateTime.now());
|
user.setCreated(LocalDateTime.now());
|
||||||
user.setUpdated(LocalDateTime.now());
|
user.setUpdated(LocalDateTime.now());
|
||||||
user.setDeleted(false);
|
user.setDeleted(false);
|
||||||
|
|
||||||
return userRepository.save(user);
|
return userRepository.save(user);
|
||||||
})
|
}))
|
||||||
.map(savedUser -> {
|
.flatMap(this::generateTokensAndBuildResponse);
|
||||||
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<JwtResponse> login(LoginUserRequest request) {
|
public Mono<RagResponse<UserProfileDTO>> refreshAccessToken(String refreshTokenValue) {
|
||||||
return userRepository.findByEmail(request.getEmail())
|
return refreshTokenRepository.findByToken(refreshTokenValue)
|
||||||
.switchIfEmpty(Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials")))
|
.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 -> {
|
.flatMap(user -> {
|
||||||
if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) {
|
String accessToken = jwtTokenProvider.generateToken(user, saved.getSessionId());
|
||||||
return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid credentials"));
|
UserProfileDTO dto = toUserProfileDto(user, accessToken, saved.getToken());
|
||||||
}
|
return Mono.just(RagResponse.createSuccessfulWithNewToken(dto));
|
||||||
if (user.getRegistrationStatus() != RegistrationStatus.ACTIVE) {
|
}));
|
||||||
return Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN, "Account is not active"));
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
user.setLastLogin(LocalDateTime.now());
|
private Mono<RagResponse<UserProfileDTO>> generateTokensAndBuildResponse(User user) {
|
||||||
user.setUpdated(LocalDateTime.now());
|
return refreshTokenRepository.findByUserId(user.getId())
|
||||||
|
.flatMap(existing -> {
|
||||||
return userRepository.save(user);
|
existing.setCreated(LocalDateTime.now());
|
||||||
|
existing.setToken(generateUuid());
|
||||||
|
existing.setSessionId(generateUuid());
|
||||||
|
return refreshTokenRepository.save(existing);
|
||||||
})
|
})
|
||||||
.map(user -> {
|
.switchIfEmpty(Mono.defer(() -> {
|
||||||
String sessionId = UUID.randomUUID().toString();
|
RefreshToken newToken = new RefreshToken();
|
||||||
String token = jwtTokenProvider.generateToken(user, sessionId);
|
newToken.setUserId(user.getId());
|
||||||
String refreshToken = jwtTokenProvider.refreshToken(token);
|
newToken.setCreated(LocalDateTime.now());
|
||||||
log.info("User logged in: {}", user.getEmail());
|
newToken.setToken(generateUuid());
|
||||||
return new JwtResponse(token, refreshToken, user.getEmail(), user.getUsername());
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<JwtResponse> refreshToken(String authHeader) {
|
private UserProfileDTO toUserProfileDto(User user, String token, String refreshToken) {
|
||||||
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
|
return new UserProfileDTO(
|
||||||
return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Missing or invalid Authorization header"));
|
user.getId(),
|
||||||
|
user.getUsername(),
|
||||||
|
user.getEmail(),
|
||||||
|
user.getRegistrationStatus(),
|
||||||
|
user.getLastLogin(),
|
||||||
|
token,
|
||||||
|
refreshToken
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String token = authHeader.substring(7);
|
private String generateUuid() {
|
||||||
if (!jwtTokenProvider.validateToken(token)) {
|
return UUID.randomUUID().toString().replace("-", "");
|
||||||
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());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,23 +128,6 @@
|
|||||||
<artifactId>spring-security-test</artifactId>
|
<artifactId>spring-security-test</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt-api</artifactId>
|
|
||||||
<version>0.11.5</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt-impl</artifactId>
|
|
||||||
<version>0.11.5</version>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
|
||||||
<artifactId>jjwt-jackson</artifactId>
|
|
||||||
<version>0.11.5</version>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
|||||||
@@ -1,99 +1,26 @@
|
|||||||
package com.balex.rag.config;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
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.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
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.bcrypt.BCryptPasswordEncoder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
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
|
@Configuration
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableMethodSecurity
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class SecurityConfig {
|
public class SecurityConfig {
|
||||||
private final JwtRequestFilter jwtRequestFilter;
|
|
||||||
private final AccessRestrictionHandler accessRestrictionHandler;
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
return http
|
||||||
.cors(Customizer.withDefaults())
|
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
|
||||||
.dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
|
.build();
|
||||||
.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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -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<UserProfileDTO> 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<RagResponse<UserProfileDTO>> refreshToken(
|
|
||||||
@RequestParam(name = "token") String refreshToken,
|
|
||||||
HttpServletResponse response) {
|
|
||||||
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
|
||||||
|
|
||||||
RagResponse<UserProfileDTO> 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<UserProfileDTO> result = authService.registerUser(request);
|
|
||||||
Cookie authorizationCookie = ApiUtils.createAuthCookie(result.getPayload().getToken());
|
|
||||||
response.addCookie(authorizationCookie);
|
|
||||||
|
|
||||||
eventPublisher.publishUserCreated(result.getPayload().getId().toString());
|
|
||||||
|
|
||||||
return ResponseEntity.ok(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package com.balex.rag.controller;
|
package com.balex.rag.controller;
|
||||||
|
|
||||||
import com.balex.rag.model.constants.ApiLogMessage;
|
|
||||||
import com.balex.rag.model.entity.Chat;
|
import com.balex.rag.model.entity.Chat;
|
||||||
import com.balex.rag.service.ChatService;
|
import com.balex.rag.service.ChatService;
|
||||||
import com.balex.rag.service.EventPublisher;
|
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.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -22,25 +22,25 @@ public class ChatController {
|
|||||||
|
|
||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
private final EventPublisher eventPublisher;
|
private final EventPublisher eventPublisher;
|
||||||
private final ApiUtils apiUtils;
|
|
||||||
|
|
||||||
@GetMapping("")
|
@GetMapping("")
|
||||||
public ResponseEntity<List<Chat>> mainPage() {
|
public ResponseEntity<List<Chat>> mainPage(HttpServletRequest request) {
|
||||||
Long ownerId = apiUtils.getUserIdFromAuthentication().longValue();
|
Long ownerId = UserContext.getUserId(request).longValue();
|
||||||
List<Chat> response = chatService.getAllChatsByOwner(ownerId);
|
List<Chat> response = chatService.getAllChatsByOwner(ownerId);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{chatId}")
|
@GetMapping("/{chatId}")
|
||||||
public ResponseEntity<Chat> showChat(@PathVariable Long chatId) {
|
public ResponseEntity<Chat> showChat(@PathVariable Long chatId, HttpServletRequest request) {
|
||||||
Long ownerId = apiUtils.getUserIdFromAuthentication().longValue();
|
Long ownerId = UserContext.getUserId(request).longValue();
|
||||||
Chat response = chatService.getChat(chatId, ownerId);
|
Chat response = chatService.getChat(chatId, ownerId);
|
||||||
return ResponseEntity.ok(response);
|
return ResponseEntity.ok(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/new")
|
@PostMapping("/new")
|
||||||
public ResponseEntity<Chat> newChat(@RequestParam String title) {
|
public ResponseEntity<Chat> newChat(@RequestParam String title, HttpServletRequest request) {
|
||||||
Chat chat = chatService.createNewChat(title);
|
Long ownerId = UserContext.getUserId(request).longValue();
|
||||||
|
Chat chat = chatService.createNewChat(title, ownerId);
|
||||||
|
|
||||||
eventPublisher.publishChatCreated(
|
eventPublisher.publishChatCreated(
|
||||||
chat.getIdOwner().toString(),
|
chat.getIdOwner().toString(),
|
||||||
@@ -50,8 +50,8 @@ public class ChatController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/{chatId}")
|
@DeleteMapping("/{chatId}")
|
||||||
public ResponseEntity<Void> deleteChat(@PathVariable Long chatId) {
|
public ResponseEntity<Void> deleteChat(@PathVariable Long chatId, HttpServletRequest request) {
|
||||||
Long ownerId = apiUtils.getUserIdFromAuthentication().longValue();
|
Long ownerId = UserContext.getUserId(request).longValue();
|
||||||
Chat chat = chatService.getChat(chatId, ownerId);
|
Chat chat = chatService.getChat(chatId, ownerId);
|
||||||
chatService.deleteChat(chatId, ownerId);
|
chatService.deleteChat(chatId, ownerId);
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import com.balex.rag.service.ChatEntryService;
|
|||||||
import com.balex.rag.service.ChatService;
|
import com.balex.rag.service.ChatService;
|
||||||
import com.balex.rag.service.EventPublisher;
|
import com.balex.rag.service.EventPublisher;
|
||||||
import com.balex.rag.utils.ApiUtils;
|
import com.balex.rag.utils.ApiUtils;
|
||||||
|
import com.balex.rag.utils.UserContext;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@@ -26,15 +28,15 @@ public class ChatEntryController {
|
|||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
private final RagDefaultsProperties ragDefaults;
|
private final RagDefaultsProperties ragDefaults;
|
||||||
private final EventPublisher eventPublisher;
|
private final EventPublisher eventPublisher;
|
||||||
private final ApiUtils apiUtils;
|
|
||||||
|
|
||||||
@PostMapping("/{chatId}")
|
@PostMapping("/{chatId}")
|
||||||
public ResponseEntity<ChatEntry> addUserEntry(
|
public ResponseEntity<ChatEntry> addUserEntry(
|
||||||
@PathVariable Long chatId,
|
@PathVariable Long chatId,
|
||||||
@RequestBody UserEntryRequest request) {
|
@RequestBody UserEntryRequest request,
|
||||||
|
HttpServletRequest httpRequest) {
|
||||||
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
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();
|
boolean onlyContext = request.onlyContext() != null ? request.onlyContext() : ragDefaults.onlyContext();
|
||||||
double topP = request.topP() != null ? request.topP() : ragDefaults.topP();
|
double topP = request.topP() != null ? request.topP() : ragDefaults.topP();
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package com.balex.rag.controller;
|
package com.balex.rag.controller;
|
||||||
|
|
||||||
import com.balex.rag.service.UserDocumentService;
|
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.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -28,7 +29,6 @@ import java.util.List;
|
|||||||
public class DocumentUploadStreamController {
|
public class DocumentUploadStreamController {
|
||||||
|
|
||||||
private final UserDocumentService userDocumentService;
|
private final UserDocumentService userDocumentService;
|
||||||
private final ApiUtils apiUtils;
|
|
||||||
|
|
||||||
@ApiResponses(value = {
|
@ApiResponses(value = {
|
||||||
@ApiResponse(responseCode = "200", description = "Document processing progress stream",
|
@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)
|
@PostMapping(value = "/upload-stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
|
||||||
public SseEmitter uploadDocumentsWithProgress(
|
public SseEmitter uploadDocumentsWithProgress(
|
||||||
@RequestPart("files") @Valid List<MultipartFile> files
|
@RequestPart("files") @Valid List<MultipartFile> files,
|
||||||
) {
|
HttpServletRequest request) {
|
||||||
Integer userId = apiUtils.getUserIdFromAuthentication();
|
Integer userId = UserContext.getUserId(request);
|
||||||
return userDocumentService.processUploadedFilesWithSse(files, userId.longValue());
|
return userDocumentService.processUploadedFilesWithSse(files, userId.longValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,8 @@ import com.balex.rag.model.response.RagResponse;
|
|||||||
import com.balex.rag.service.EventPublisher;
|
import com.balex.rag.service.EventPublisher;
|
||||||
import com.balex.rag.service.UserService;
|
import com.balex.rag.service.UserService;
|
||||||
import com.balex.rag.utils.ApiUtils;
|
import com.balex.rag.utils.ApiUtils;
|
||||||
|
import com.balex.rag.utils.UserContext;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -51,26 +53,25 @@ public class UserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("${end.points.userinfo}")
|
@GetMapping("${end.points.userinfo}")
|
||||||
public ResponseEntity<RagResponse<UserInfo>> getUserInfo(
|
public ResponseEntity<RagResponse<UserInfo>> getUserInfo(HttpServletRequest request) {
|
||||||
@RequestHeader("Authorization") String token) {
|
|
||||||
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
||||||
|
|
||||||
RagResponse<UserInfo> userInfo = userService.getUserInfo(token);
|
Integer userId = UserContext.getUserId(request);
|
||||||
|
RagResponse<UserInfo> userInfo = userService.getUserInfo(userId);
|
||||||
|
|
||||||
return ResponseEntity.ok(userInfo);
|
return ResponseEntity.ok(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("${end.points.userinfo}")
|
@DeleteMapping("${end.points.userinfo}")
|
||||||
public ResponseEntity<RagResponse<Integer>> deleteUserDocuments(
|
public ResponseEntity<RagResponse<Integer>> deleteUserDocuments(HttpServletRequest request) {
|
||||||
@RequestHeader("Authorization") String token) {
|
|
||||||
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
||||||
|
|
||||||
RagResponse<Integer> deletedCount = userService.deleteUserDocuments(token);
|
Integer userId = UserContext.getUserId(request);
|
||||||
|
RagResponse<Integer> deletedCount = userService.deleteUserDocuments(userId);
|
||||||
|
|
||||||
return ResponseEntity.ok(deletedCount);
|
return ResponseEntity.ok(deletedCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@PutMapping("${end.points.id}")
|
@PutMapping("${end.points.id}")
|
||||||
public ResponseEntity<RagResponse<UserDTO>> updateUserById(
|
public ResponseEntity<RagResponse<UserDTO>> updateUserById(
|
||||||
@PathVariable(name = "id") Integer userId,
|
@PathVariable(name = "id") Integer userId,
|
||||||
@@ -80,14 +81,14 @@ public class UserController {
|
|||||||
RagResponse<UserDTO> updatedPost = userService.updateUser(userId, request);
|
RagResponse<UserDTO> updatedPost = userService.updateUser(userId, request);
|
||||||
return ResponseEntity.ok(updatedPost);
|
return ResponseEntity.ok(updatedPost);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("${end.points.id}")
|
@DeleteMapping("${end.points.id}")
|
||||||
public ResponseEntity<Void> softDeleteUser(
|
public ResponseEntity<Void> softDeleteUser(
|
||||||
@PathVariable(name = "id") Integer userId
|
@PathVariable(name = "id") Integer userId,
|
||||||
) {
|
HttpServletRequest request) {
|
||||||
log.trace(ApiLogMessage.NAME_OF_CURRENT_METHOD.getValue(), ApiUtils.getMethodName());
|
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();
|
return ResponseEntity.ok().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
package com.balex.rag.mapper;
|
package com.balex.rag.mapper;
|
||||||
|
|
||||||
import com.balex.rag.model.dto.UserDTO;
|
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.dto.UserSearchDTO;
|
||||||
import com.balex.rag.model.entity.User;
|
import com.balex.rag.model.entity.User;
|
||||||
import com.balex.rag.model.enums.RegistrationStatus;
|
import com.balex.rag.model.enums.RegistrationStatus;
|
||||||
import com.balex.rag.model.request.user.NewUserRequest;
|
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 com.balex.rag.model.request.user.UpdateUserRequest;
|
||||||
import org.hibernate.type.descriptor.DateTimeUtils;
|
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
import org.mapstruct.Mapping;
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.MappingTarget;
|
import org.mapstruct.MappingTarget;
|
||||||
@@ -19,7 +16,7 @@ import java.util.Objects;
|
|||||||
@Mapper(
|
@Mapper(
|
||||||
componentModel = "spring",
|
componentModel = "spring",
|
||||||
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
|
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
|
||||||
imports = {RegistrationStatus.class, Objects.class, DateTimeUtils.class}
|
imports = {RegistrationStatus.class, Objects.class}
|
||||||
)
|
)
|
||||||
public interface UserMapper {
|
public interface UserMapper {
|
||||||
|
|
||||||
@@ -36,16 +33,4 @@ public interface UserMapper {
|
|||||||
|
|
||||||
@Mapping(source = "deleted", target = "isDeleted")
|
@Mapping(source = "deleted", target = "isDeleted")
|
||||||
UserSearchDTO toUserSearchDto(User user);
|
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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ public final class ApiConstants {
|
|||||||
|
|
||||||
public static final String UNDEFINED = "undefined";
|
public static final String UNDEFINED = "undefined";
|
||||||
public static final String EMPTY_FILENAME = "unknown";
|
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_RED = "\u001B[31m";
|
||||||
public static final String ANSI_WHITE = "\u001B[37m";
|
public static final String ANSI_WHITE = "\u001B[37m";
|
||||||
public static final String BREAK_LINE = "\n";
|
public static final String BREAK_LINE = "\n";
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package com.balex.rag.model.exception;
|
|
||||||
|
|
||||||
public class InvalidTokenException extends RuntimeException {
|
|
||||||
|
|
||||||
public InvalidTokenException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<RefreshToken, Integer> {
|
|
||||||
|
|
||||||
Optional<RefreshToken> findByToken(String token);
|
|
||||||
|
|
||||||
Optional<RefreshToken> findByUserId(Integer userId);
|
|
||||||
|
|
||||||
Optional<RefreshToken> findByUserEmail(String email);
|
|
||||||
}
|
|
||||||
@@ -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<String, Object> 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> 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<String, Object> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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<String> 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<String> emailOpt = Optional.ofNullable(jwtTokenProvider.getUsername(jwt));
|
|
||||||
Optional<String> userIdOpt = Optional.ofNullable(jwtTokenProvider.getUserId(jwt));
|
|
||||||
String sessionId = jwtTokenProvider.getSessionId(jwt);
|
|
||||||
|
|
||||||
if (emailOpt.isPresent() && userIdOpt.isPresent()) {
|
|
||||||
// Check session validity
|
|
||||||
Optional<String> 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<SimpleGrantedAuthority> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
package com.balex.rag.security.validation;
|
package com.balex.rag.security.validation;
|
||||||
|
|
||||||
import com.balex.rag.model.constants.ApiErrorMessage;
|
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.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.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -19,34 +12,11 @@ import java.nio.file.AccessDeniedException;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AccessValidator {
|
public class AccessValidator {
|
||||||
private final UserRepository userRepository;
|
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
|
@SneakyThrows
|
||||||
public void validateOwnerAccess(Integer ownerId) {
|
public void validateOwnerAccess(Integer ownerId, Integer currentUserId) {
|
||||||
Integer currentUserId = apiUtils.getUserIdFromAuthentication();
|
|
||||||
|
|
||||||
if (!currentUserId.equals(ownerId)) {
|
if (!currentUserId.equals(ownerId)) {
|
||||||
throw new AccessDeniedException(ApiErrorMessage.HAVE_NO_ACCESS.getMessage());
|
throw new AccessDeniedException(ApiErrorMessage.HAVE_NO_ACCESS.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<PasswordMatches, RegistrationUserRequest> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValid(RegistrationUserRequest request, ConstraintValidatorContext constraintValidatorContext) {
|
|
||||||
return request.getPassword().equals(request.getConfirmPassword());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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<UserProfileDTO> login(LoginRequest request);
|
|
||||||
|
|
||||||
RagResponse<UserProfileDTO> refreshAccessToken(String refreshToken);
|
|
||||||
|
|
||||||
RagResponse<UserProfileDTO> registerUser(RegistrationUserRequest request);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ import java.util.List;
|
|||||||
|
|
||||||
public interface ChatService {
|
public interface ChatService {
|
||||||
|
|
||||||
Chat createNewChat(String title);
|
Chat createNewChat(String title, Long ownerId);
|
||||||
|
|
||||||
List<Chat> getAllChatsByOwner(Long ownerId);
|
List<Chat> getAllChatsByOwner(Long ownerId);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -9,9 +9,8 @@ import com.balex.rag.model.response.PaginationResponse;
|
|||||||
import com.balex.rag.model.response.RagResponse;
|
import com.balex.rag.model.response.RagResponse;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
|
|
||||||
public interface UserService extends UserDetailsService {
|
public interface UserService {
|
||||||
|
|
||||||
RagResponse<UserDTO> getById(@NotNull Integer userId);
|
RagResponse<UserDTO> getById(@NotNull Integer userId);
|
||||||
|
|
||||||
@@ -19,15 +18,11 @@ public interface UserService extends UserDetailsService {
|
|||||||
|
|
||||||
RagResponse<UserDTO> updateUser(@NotNull Integer postId, @NotNull UpdateUserRequest request);
|
RagResponse<UserDTO> updateUser(@NotNull Integer postId, @NotNull UpdateUserRequest request);
|
||||||
|
|
||||||
void softDeleteUser(Integer userId);
|
void softDeleteUser(Integer userId, Integer currentUserId);
|
||||||
|
|
||||||
RagResponse<PaginationResponse<UserSearchDTO>> findAllUsers(Pageable pageable);
|
RagResponse<PaginationResponse<UserSearchDTO>> findAllUsers(Pageable pageable);
|
||||||
|
|
||||||
RagResponse<UserInfo> getUserInfo(String token);
|
RagResponse<UserInfo> getUserInfo(Integer userId);
|
||||||
|
|
||||||
RagResponse<Integer> deleteUserDocuments(String token);
|
|
||||||
|
|
||||||
|
RagResponse<Integer> deleteUserDocuments(Integer userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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<UserProfileDTO> 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<UserProfileDTO> 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<UserProfileDTO> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -3,13 +3,11 @@ package com.balex.rag.service.impl;
|
|||||||
import com.balex.rag.model.entity.Chat;
|
import com.balex.rag.model.entity.Chat;
|
||||||
import com.balex.rag.repo.ChatRepository;
|
import com.balex.rag.repo.ChatRepository;
|
||||||
import com.balex.rag.service.ChatService;
|
import com.balex.rag.service.ChatService;
|
||||||
import com.balex.rag.utils.ApiUtils;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.ai.chat.client.ChatClient;
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
import org.springframework.ai.chat.memory.ChatMemory;
|
import org.springframework.ai.chat.memory.ChatMemory;
|
||||||
import org.springframework.ai.chat.model.ChatResponse;
|
import org.springframework.ai.chat.model.ChatResponse;
|
||||||
import org.springframework.data.domain.Sort;
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
@@ -23,25 +21,19 @@ public class ChatServiceImpl implements ChatService {
|
|||||||
|
|
||||||
private final ChatRepository chatRepo;
|
private final ChatRepository chatRepo;
|
||||||
private final ChatClient chatClient;
|
private final ChatClient chatClient;
|
||||||
private final ApiUtils apiUtils;
|
|
||||||
|
|
||||||
public List<Chat> getAllChatsByOwner(Long ownerId) {
|
public List<Chat> getAllChatsByOwner(Long ownerId) {
|
||||||
return chatRepo.findByIdOwnerOrderByCreatedAtDesc(ownerId);
|
return chatRepo.findByIdOwnerOrderByCreatedAtDesc(ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Chat createNewChat(String title) {
|
public Chat createNewChat(String title, Long ownerId) {
|
||||||
Long ownerId = apiUtils.getUserIdFromAuthentication().longValue();
|
Chat chat = Chat.builder().title(title).idOwner(ownerId).build();
|
||||||
Chat chat = Chat.builder()
|
|
||||||
.title(title)
|
|
||||||
.idOwner(ownerId)
|
|
||||||
.build();
|
|
||||||
chatRepo.save(chat);
|
chatRepo.save(chat);
|
||||||
return chat;
|
return chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Chat getChat(Long chatId, Long ownerId) {
|
public Chat getChat(Long chatId, Long ownerId) {
|
||||||
Chat chat = chatRepo.findById(chatId).orElseThrow(
|
Chat chat = chatRepo.findById(chatId).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
|
||||||
() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
|
|
||||||
if (!chat.getIdOwner().equals(ownerId)) {
|
if (!chat.getIdOwner().equals(ownerId)) {
|
||||||
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied");
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied");
|
||||||
}
|
}
|
||||||
@@ -57,15 +49,7 @@ public class ChatServiceImpl implements ChatService {
|
|||||||
SseEmitter sseEmitter = new SseEmitter(0L);
|
SseEmitter sseEmitter = new SseEmitter(0L);
|
||||||
final StringBuilder answer = new StringBuilder();
|
final StringBuilder answer = new StringBuilder();
|
||||||
|
|
||||||
chatClient
|
chatClient.prompt(userPrompt).advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, chatId)).stream().chatResponse().subscribe((ChatResponse response) -> processToken(response, sseEmitter, answer), sseEmitter::completeWithError, sseEmitter::complete);
|
||||||
.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;
|
return sseEmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<String> getSessionIdByEmail(String email) {
|
|
||||||
return refreshTokenRepository.findByUserEmail(email)
|
|
||||||
.map(RefreshToken::getSessionId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ import com.balex.rag.model.dto.UserSearchDTO;
|
|||||||
import com.balex.rag.model.entity.LoadedDocumentInfo;
|
import com.balex.rag.model.entity.LoadedDocumentInfo;
|
||||||
import com.balex.rag.model.entity.User;
|
import com.balex.rag.model.entity.User;
|
||||||
import com.balex.rag.model.entity.UserInfo;
|
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.exception.NotFoundException;
|
||||||
import com.balex.rag.model.request.user.NewUserRequest;
|
import com.balex.rag.model.request.user.NewUserRequest;
|
||||||
import com.balex.rag.model.request.user.UpdateUserRequest;
|
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.DocumentRepository;
|
||||||
import com.balex.rag.repo.UserRepository;
|
import com.balex.rag.repo.UserRepository;
|
||||||
import com.balex.rag.repo.VectorStoreRepository;
|
import com.balex.rag.repo.VectorStoreRepository;
|
||||||
import com.balex.rag.security.JwtTokenProvider;
|
|
||||||
import com.balex.rag.security.validation.AccessValidator;
|
import com.balex.rag.security.validation.AccessValidator;
|
||||||
import com.balex.rag.service.UserService;
|
import com.balex.rag.service.UserService;
|
||||||
import com.balex.rag.service.model.exception.DataExistException;
|
import com.balex.rag.service.model.exception.DataExistException;
|
||||||
@@ -25,19 +23,12 @@ import jakarta.validation.constraints.NotNull;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.Pageable;
|
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.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static com.balex.rag.model.constants.ApiConstants.USER_ROLE;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class UserServiceImpl implements UserService {
|
public class UserServiceImpl implements UserService {
|
||||||
@@ -45,7 +36,6 @@ public class UserServiceImpl implements UserService {
|
|||||||
private final UserMapper userMapper;
|
private final UserMapper userMapper;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final AccessValidator accessValidator;
|
private final AccessValidator accessValidator;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
|
||||||
private final DocumentRepository documentRepository;
|
private final DocumentRepository documentRepository;
|
||||||
private final VectorStoreRepository vectorStoreRepository;
|
private final VectorStoreRepository vectorStoreRepository;
|
||||||
|
|
||||||
@@ -98,15 +88,14 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void softDeleteUser(Integer userId) {
|
public void softDeleteUser(Integer userId, Integer currentUserId) {
|
||||||
User user = userRepository.findByIdAndDeletedFalse(userId)
|
User user = userRepository.findByIdAndDeletedFalse(userId)
|
||||||
.orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId)));
|
.orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId)));
|
||||||
|
|
||||||
accessValidator.validateOwnerAccess(userId);
|
accessValidator.validateOwnerAccess(userId, currentUserId);
|
||||||
|
|
||||||
user.setDeleted(true);
|
user.setDeleted(true);
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -128,28 +117,11 @@ public class UserServiceImpl implements UserService {
|
|||||||
return RagResponse.createSuccessful(paginationResponse);
|
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
|
@Override
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
public RagResponse<UserInfo> getUserInfo(String token) {
|
public RagResponse<UserInfo> getUserInfo(Integer userId) {
|
||||||
User user = getUserInfoFromToken(token);
|
User user = userRepository.findByIdAndDeletedFalse(userId)
|
||||||
|
.orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId)));
|
||||||
|
|
||||||
List<LoadedDocumentInfo> loadedFiles = documentRepository
|
List<LoadedDocumentInfo> loadedFiles = documentRepository
|
||||||
.findByUserId(user.getId())
|
.findByUserId(user.getId())
|
||||||
@@ -167,9 +139,9 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public RagResponse<Integer> deleteUserDocuments(String token) {
|
public RagResponse<Integer> deleteUserDocuments(Integer userId) {
|
||||||
getUserInfoFromToken(token);
|
User user = userRepository.findByIdAndDeletedFalse(userId)
|
||||||
User user = getUserInfoFromToken(token);
|
.orElseThrow(() -> new NotFoundException(ApiErrorMessage.USER_NOT_FOUND_BY_ID.getMessage(userId)));
|
||||||
|
|
||||||
List<LoadedDocument> documents = documentRepository.findByUserId(user.getId());
|
List<LoadedDocument> documents = documentRepository.findByUserId(user.getId());
|
||||||
|
|
||||||
@@ -177,28 +149,9 @@ public class UserServiceImpl implements UserService {
|
|||||||
return RagResponse.createSuccessful(0);
|
return RagResponse.createSuccessful(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удаляем чанки по user_id
|
|
||||||
vectorStoreRepository.deleteByUserId(user.getId().longValue());
|
vectorStoreRepository.deleteByUserId(user.getId().longValue());
|
||||||
|
|
||||||
// Удаляем записи из loaded_document
|
|
||||||
documentRepository.deleteAll(documents);
|
documentRepository.deleteAll(documents);
|
||||||
|
|
||||||
return RagResponse.createSuccessful(documents.size());
|
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,16 @@
|
|||||||
package com.balex.rag.utils;
|
package com.balex.rag.utils;
|
||||||
|
|
||||||
import com.balex.rag.model.constants.ApiConstants;
|
import lombok.AccessLevel;
|
||||||
import com.balex.rag.security.JwtTokenProvider;
|
import lombok.NoArgsConstructor;
|
||||||
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;
|
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public final class ApiUtils {
|
||||||
|
|
||||||
public static String getMethodName() {
|
public static String getMethodName() {
|
||||||
try {
|
try {
|
||||||
return new Throwable().getStackTrace()[1].getMethodName();
|
return new Throwable().getStackTrace()[1].getMethodName();
|
||||||
} catch (Exception cause) {
|
} 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -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<? extends Payload>[] payload() default {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -54,10 +54,6 @@ public final class PasswordUtils {
|
|||||||
int lettersUCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD;
|
int lettersUCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD;
|
||||||
int lettersLCaseNumber = ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH
|
int lettersLCaseNumber = ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH
|
||||||
- charactersNumber - digitsNumber - lettersUCaseNumber;
|
- 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 characters = randomFromChars(charactersNumber, ApiConstants.PASSWORD_CHARACTERS);
|
||||||
String digits = randomFromChars(digitsNumber, ApiConstants.PASSWORD_DIGITS);
|
String digits = randomFromChars(digitsNumber, ApiConstants.PASSWORD_DIGITS);
|
||||||
String lettersUCase = randomFromChars(lettersUCaseNumber, ApiConstants.PASSWORD_LETTERS_UPPER_CASE);
|
String lettersUCase = randomFromChars(lettersUCaseNumber, ApiConstants.PASSWORD_LETTERS_UPPER_CASE);
|
||||||
|
|||||||
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,8 +23,6 @@ spring.cloud.consul.discovery.health-check-interval=10s
|
|||||||
management.endpoints.web.exposure.include=health,info
|
management.endpoints.web.exposure.include=health,info
|
||||||
management.endpoint.health.show-details=always
|
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.url=${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/ragdb}
|
||||||
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:postgres}
|
spring.datasource.username=${SPRING_DATASOURCE_USERNAME:postgres}
|
||||||
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:postgres}
|
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD:postgres}
|
||||||
|
|||||||
Reference in New Issue
Block a user