forbidden(ServerWebExchange exchange) {
+ ServerHttpResponse response = exchange.getResponse();
+ response.setStatusCode(HttpStatus.FORBIDDEN);
+ return response.setComplete();
+ }
+
+ @Override
+ public int getOrder() {
+ // После RequestLoggingFilter (HIGHEST_PRECEDENCE), но до route filters
+ return Ordered.HIGHEST_PRECEDENCE + 1;
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java
new file mode 100644
index 0000000..9348a01
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiConstants.java
@@ -0,0 +1,21 @@
+package com.posthub.gateway.model.constants;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class ApiConstants {
+
+ public static final String DASH = "-";
+
+ public static final String PASSWORD_ALL_CHARACTERS =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~`!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
+ public static final String PASSWORD_LETTERS_UPPER_CASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ public static final String PASSWORD_LETTERS_LOWER_CASE = "abcdefghijklmnopqrstuvwxyz";
+ public static final String PASSWORD_DIGITS = "0123456789";
+ public static final String PASSWORD_CHARACTERS = "~`!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?";
+ public static final Integer REQUIRED_MIN_PASSWORD_LENGTH = 8;
+ public static final Integer REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD = 1;
+ public static final Integer REQUIRED_MIN_DIGITS_NUMBER_IN_PASSWORD = 1;
+ public static final Integer REQUIRED_MIN_CHARACTERS_NUMBER_IN_PASSWORD = 1;
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java
new file mode 100644
index 0000000..42304ac
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/constants/ApiErrorMessage.java
@@ -0,0 +1,29 @@
+package com.posthub.gateway.model.constants;
+
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+public enum ApiErrorMessage {
+
+ USERNAME_ALREADY_EXISTS("Username: %s already exists"),
+ EMAIL_ALREADY_EXISTS("Email: %s already exists"),
+ INVALID_USER_OR_PASSWORD("Invalid email or password. Try again"),
+ NOT_FOUND_REFRESH_TOKEN("Refresh token not found"),
+ MISMATCH_PASSWORDS("Password does not match"),
+ INVALID_PASSWORD("Invalid password. It must have: "
+ + "length at least " + ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH + ", including "
+ + ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD + " letter(s) in upper and lower cases, "
+ + ApiConstants.REQUIRED_MIN_CHARACTERS_NUMBER_IN_PASSWORD + " character(s), "
+ + ApiConstants.REQUIRED_MIN_DIGITS_NUMBER_IN_PASSWORD + " digit(s). "),
+ ACCOUNT_NOT_ACTIVE("Account is not active"),
+ ;
+
+ private final String message;
+
+ public String getMessage(Object... args) {
+ return String.format(message, args);
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java b/gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java
new file mode 100644
index 0000000..e98c9a2
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/entity/RefreshToken.java
@@ -0,0 +1,30 @@
+package com.posthub.gateway.model.entity;
+
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.relational.core.mapping.Column;
+import org.springframework.data.relational.core.mapping.Table;
+
+import java.time.LocalDateTime;
+
+@Table("refresh_token")
+@Getter
+@Setter
+@NoArgsConstructor
+public class RefreshToken {
+
+ @Id
+ private Integer id;
+
+ private String token;
+
+ private LocalDateTime created;
+
+ @Column("session_id")
+ private String sessionId;
+
+ @Column("user_id")
+ private Integer userId;
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/entity/User.java b/gateway-service/src/main/java/com/posthub/gateway/model/entity/User.java
new file mode 100644
index 0000000..0b99db9
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/entity/User.java
@@ -0,0 +1,43 @@
+package com.posthub.gateway.model.entity;
+
+import com.posthub.gateway.model.enums.RegistrationStatus;
+import com.posthub.gateway.model.enums.UserRole;
+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("users")
+@Getter
+@Setter
+@NoArgsConstructor
+public class User {
+
+ @Id
+ private Integer id;
+
+ private String username;
+
+ private String password;
+
+ private String email;
+
+ private LocalDateTime created;
+
+ private LocalDateTime updated;
+
+ @Column("last_login")
+ private LocalDateTime lastLogin;
+
+ private Boolean deleted;
+
+ @Column("registration_status")
+ private RegistrationStatus registrationStatus;
+
+ @Column("role")
+ private UserRole role;
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/enums/RegistrationStatus.java b/gateway-service/src/main/java/com/posthub/gateway/model/enums/RegistrationStatus.java
new file mode 100644
index 0000000..3dd2fec
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/enums/RegistrationStatus.java
@@ -0,0 +1,7 @@
+package com.posthub.gateway.model.enums;
+
+public enum RegistrationStatus {
+ ACTIVE,
+ BLOCKED,
+ DELETED
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/enums/UserRole.java b/gateway-service/src/main/java/com/posthub/gateway/model/enums/UserRole.java
new file mode 100644
index 0000000..88104e9
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/enums/UserRole.java
@@ -0,0 +1,6 @@
+package com.posthub.gateway.model.enums;
+
+public enum UserRole {
+ USER,
+ ADMIN
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginRequest.java b/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginRequest.java
new file mode 100644
index 0000000..1fb3fc9
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/request/LoginRequest.java
@@ -0,0 +1,19 @@
+package com.posthub.gateway.model.request;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class LoginRequest {
+
+ @Email
+ @NotNull
+ private String email;
+
+ @NotEmpty
+ private String password;
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/request/RegistrationUserRequest.java b/gateway-service/src/main/java/com/posthub/gateway/model/request/RegistrationUserRequest.java
new file mode 100644
index 0000000..d138928
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/request/RegistrationUserRequest.java
@@ -0,0 +1,28 @@
+package com.posthub.gateway.model.request;
+
+import jakarta.validation.constraints.Email;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class RegistrationUserRequest {
+
+ @NotBlank
+ @Size(min = 2, max = 30)
+ private String username;
+
+ @NotBlank
+ @Email
+ @Size(max = 50)
+ private String email;
+
+ @NotBlank
+ @Size(min = 6, max = 80)
+ private String password;
+
+ @NotBlank
+ private String confirmPassword;
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java b/gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java
new file mode 100644
index 0000000..2618109
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/response/RagResponse.java
@@ -0,0 +1,25 @@
+package com.posthub.gateway.model.response;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class RagResponse {
+
+ private String message;
+ private P payload;
+ private boolean success;
+
+ public static
RagResponse
createSuccessful(P payload) {
+ return new RagResponse<>("", payload, true);
+ }
+
+ public static
RagResponse
createSuccessfulWithNewToken(P payload) {
+ return new RagResponse<>("Token created or updated", payload, true);
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java b/gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java
new file mode 100644
index 0000000..cdd099e
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/model/response/UserProfileDTO.java
@@ -0,0 +1,22 @@
+package com.posthub.gateway.model.response;
+
+import com.posthub.gateway.model.enums.RegistrationStatus;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.time.LocalDateTime;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class UserProfileDTO {
+
+ private Integer id;
+ private String username;
+ private String email;
+ private RegistrationStatus registrationStatus;
+ private LocalDateTime lastLogin;
+ private String token;
+ private String refreshToken;
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java b/gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java
new file mode 100644
index 0000000..4a9b32a
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/repository/RefreshTokenRepository.java
@@ -0,0 +1,12 @@
+package com.posthub.gateway.repository;
+
+import com.posthub.gateway.model.entity.RefreshToken;
+import org.springframework.data.repository.reactive.ReactiveCrudRepository;
+import reactor.core.publisher.Mono;
+
+public interface RefreshTokenRepository extends ReactiveCrudRepository {
+
+ Mono findByToken(String token);
+
+ Mono findByUserId(Integer userId);
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/repository/UserRepository.java b/gateway-service/src/main/java/com/posthub/gateway/repository/UserRepository.java
new file mode 100644
index 0000000..871eda4
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/repository/UserRepository.java
@@ -0,0 +1,14 @@
+package com.posthub.gateway.repository;
+
+import com.posthub.gateway.model.entity.User;
+import org.springframework.data.repository.reactive.ReactiveCrudRepository;
+import reactor.core.publisher.Mono;
+
+public interface UserRepository extends ReactiveCrudRepository {
+
+ Mono findByEmail(String email);
+
+ Mono findByUsername(String username);
+
+ Mono existsByEmail(String email);
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/security/JwtTokenProvider.java b/gateway-service/src/main/java/com/posthub/gateway/security/JwtTokenProvider.java
new file mode 100644
index 0000000..8c71a21
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/security/JwtTokenProvider.java
@@ -0,0 +1,111 @@
+package com.posthub.gateway.security;
+
+import com.posthub.gateway.model.entity.User;
+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 static final String USER_ID = "userId";
+ private static final String USERNAME = "username";
+ private static final String USER_EMAIL = "email";
+ private static final String USER_ROLE = "role";
+ private static final String USER_REGISTRATION_STATUS = "registrationStatus";
+ private static final String SESSION_ID = "sessionId";
+ private static final String LAST_UPDATE = "lastUpdate";
+
+ private final SecretKey secretKey;
+ private final long jwtValidityInMilliseconds;
+
+ public JwtTokenProvider(@Value("${jwt.secret}") String secret,
+ @Value("${jwt.expiration:103600000}") long jwtValidityInMilliseconds) {
+ byte[] decoded = Decoders.BASE64.decode(secret);
+ this.secretKey = Keys.hmacShaKeyFor(decoded);
+ this.jwtValidityInMilliseconds = jwtValidityInMilliseconds;
+ }
+
+ public String generateToken(@NonNull User user, String sessionId) {
+ Map claims = new HashMap<>();
+ claims.put(USER_ID, user.getId());
+ claims.put(USERNAME, user.getUsername());
+ claims.put(USER_EMAIL, user.getEmail());
+ claims.put(USER_ROLE, user.getRole().name());
+ claims.put(USER_REGISTRATION_STATUS, user.getRegistrationStatus().name());
+ claims.put(SESSION_ID, sessionId);
+ claims.put(LAST_UPDATE, LocalDateTime.now().toString());
+ return createToken(claims, user.getEmail());
+ }
+
+ public String refreshToken(String token) {
+ Claims claims = getAllClaimsFromToken(token);
+ return createToken(claims, claims.getSubject());
+ }
+
+ public boolean validateToken(String token) {
+ try {
+ Jws claims = Jwts.parser()
+ .verifyWith(secretKey)
+ .build()
+ .parseSignedClaims(token);
+ return !claims.getPayload().getExpiration().before(new Date());
+ } catch (JwtException | IllegalArgumentException e) {
+ log.warn("Invalid JWT token: {}", e.getMessage());
+ return false;
+ }
+ }
+
+ public String getUsername(String token) {
+ return getAllClaimsFromToken(token).get(USERNAME, String.class);
+ }
+
+ public String getUserId(String token) {
+ return String.valueOf(getAllClaimsFromToken(token).get(USER_ID));
+ }
+
+ public String getUserEmail(String token) {
+ return getAllClaimsFromToken(token).get(USER_EMAIL, String.class);
+ }
+
+ public String getUserRole(String token) {
+ return getAllClaimsFromToken(token).get(USER_ROLE, String.class);
+ }
+
+ public String getSessionId(String token) {
+ return getAllClaimsFromToken(token).get(SESSION_ID, String.class);
+ }
+
+ private Claims getAllClaimsFromToken(String token) {
+ try {
+ return Jwts.parser()
+ .verifyWith(secretKey)
+ .build()
+ .parseSignedClaims(token)
+ .getPayload();
+ } catch (ExpiredJwtException e) {
+ return e.getClaims();
+ }
+ }
+
+ private String createToken(Map claims, String subject) {
+ return Jwts.builder()
+ .claims(claims)
+ .subject(subject)
+ .issuedAt(new Date())
+ .expiration(new Date(System.currentTimeMillis() + jwtValidityInMilliseconds))
+ .signWith(secretKey)
+ .compact();
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java b/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java
new file mode 100644
index 0000000..f8fa9c2
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/service/AuthService.java
@@ -0,0 +1,146 @@
+package com.posthub.gateway.service;
+
+import com.posthub.gateway.model.constants.ApiErrorMessage;
+import com.posthub.gateway.model.entity.RefreshToken;
+import com.posthub.gateway.model.entity.User;
+import com.posthub.gateway.model.enums.RegistrationStatus;
+import com.posthub.gateway.model.enums.UserRole;
+import com.posthub.gateway.model.request.LoginRequest;
+import com.posthub.gateway.model.request.RegistrationUserRequest;
+import com.posthub.gateway.model.response.RagResponse;
+import com.posthub.gateway.model.response.UserProfileDTO;
+import com.posthub.gateway.repository.RefreshTokenRepository;
+import com.posthub.gateway.repository.UserRepository;
+import com.posthub.gateway.security.JwtTokenProvider;
+import com.posthub.gateway.util.PasswordUtils;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.web.server.ResponseStatusException;
+import reactor.core.publisher.Mono;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class AuthService {
+
+ private final UserRepository userRepository;
+ private final RefreshTokenRepository refreshTokenRepository;
+ private final JwtTokenProvider jwtTokenProvider;
+ private final PasswordEncoder passwordEncoder;
+
+ public Mono> login(LoginRequest request) {
+ return userRepository.findByEmail(request.getEmail())
+ .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);
+ }
+
+ public Mono> register(RegistrationUserRequest request) {
+ if (!request.getPassword().equals(request.getConfirmPassword())) {
+ return Mono.error(new ResponseStatusException(
+ HttpStatus.BAD_REQUEST, ApiErrorMessage.MISMATCH_PASSWORDS.getMessage()));
+ }
+ if (PasswordUtils.isNotValidPassword(request.getPassword())) {
+ return Mono.error(new ResponseStatusException(
+ HttpStatus.BAD_REQUEST, ApiErrorMessage.INVALID_PASSWORD.getMessage()));
+ }
+
+ return userRepository.findByUsername(request.getUsername())
+ .flatMap(existing -> Mono.error(new ResponseStatusException(
+ HttpStatus.CONFLICT, ApiErrorMessage.USERNAME_ALREADY_EXISTS.getMessage(request.getUsername()))))
+ .switchIfEmpty(userRepository.existsByEmail(request.getEmail())
+ .flatMap(exists -> {
+ if (exists) {
+ return Mono.error(new ResponseStatusException(
+ HttpStatus.CONFLICT, ApiErrorMessage.EMAIL_ALREADY_EXISTS.getMessage(request.getEmail())));
+ }
+ User user = new User();
+ user.setUsername(request.getUsername());
+ user.setEmail(request.getEmail());
+ user.setPassword(passwordEncoder.encode(request.getPassword()));
+ user.setRegistrationStatus(RegistrationStatus.ACTIVE);
+ user.setRole(UserRole.USER);
+ user.setCreated(LocalDateTime.now());
+ user.setUpdated(LocalDateTime.now());
+ user.setDeleted(false);
+ return userRepository.save(user);
+ }))
+ .flatMap(this::generateTokensAndBuildResponse);
+ }
+
+ public Mono> refreshAccessToken(String refreshTokenValue) {
+ return refreshTokenRepository.findByToken(refreshTokenValue)
+ .switchIfEmpty(Mono.error(new ResponseStatusException(
+ HttpStatus.UNAUTHORIZED, ApiErrorMessage.NOT_FOUND_REFRESH_TOKEN.getMessage())))
+ .flatMap(refreshToken -> {
+ refreshToken.setCreated(LocalDateTime.now());
+ refreshToken.setToken(generateUuid());
+ return refreshTokenRepository.save(refreshToken)
+ .flatMap(saved -> userRepository.findById(saved.getUserId())
+ .flatMap(user -> {
+ String accessToken = jwtTokenProvider.generateToken(user, saved.getSessionId());
+ UserProfileDTO dto = toUserProfileDto(user, accessToken, saved.getToken());
+ return Mono.just(RagResponse.createSuccessfulWithNewToken(dto));
+ }));
+ });
+ }
+
+ private Mono> generateTokensAndBuildResponse(User user) {
+ return refreshTokenRepository.findByUserId(user.getId())
+ .flatMap(existing -> {
+ existing.setCreated(LocalDateTime.now());
+ existing.setToken(generateUuid());
+ existing.setSessionId(generateUuid());
+ return refreshTokenRepository.save(existing);
+ })
+ .switchIfEmpty(Mono.defer(() -> {
+ RefreshToken newToken = new RefreshToken();
+ newToken.setUserId(user.getId());
+ newToken.setCreated(LocalDateTime.now());
+ newToken.setToken(generateUuid());
+ newToken.setSessionId(generateUuid());
+ return refreshTokenRepository.save(newToken);
+ }))
+ .map(refreshToken -> {
+ String accessToken = jwtTokenProvider.generateToken(user, refreshToken.getSessionId());
+ UserProfileDTO dto = toUserProfileDto(user, accessToken, refreshToken.getToken());
+ log.info("Auth success for user: {}", user.getEmail());
+ return RagResponse.createSuccessfulWithNewToken(dto);
+ });
+ }
+
+ private UserProfileDTO toUserProfileDto(User user, String token, String refreshToken) {
+ return new UserProfileDTO(
+ user.getId(),
+ user.getUsername(),
+ user.getEmail(),
+ user.getRegistrationStatus(),
+ user.getLastLogin(),
+ token,
+ refreshToken
+ );
+ }
+
+ private String generateUuid() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java b/gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java
new file mode 100644
index 0000000..a86d8a8
--- /dev/null
+++ b/gateway-service/src/main/java/com/posthub/gateway/util/PasswordUtils.java
@@ -0,0 +1,34 @@
+package com.posthub.gateway.util;
+
+import com.posthub.gateway.model.constants.ApiConstants;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public final class PasswordUtils {
+
+ public static boolean isNotValidPassword(String password) {
+ if (password == null || password.isEmpty() || password.trim().isEmpty()) {
+ return true;
+ }
+ String trim = password.trim();
+ if (trim.length() < ApiConstants.REQUIRED_MIN_PASSWORD_LENGTH) {
+ return true;
+ }
+ int charactersNumber = ApiConstants.REQUIRED_MIN_CHARACTERS_NUMBER_IN_PASSWORD;
+ int lettersUCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD;
+ int lettersLCaseNumber = ApiConstants.REQUIRED_MIN_LETTERS_NUMBER_EVERY_CASE_IN_PASSWORD;
+ int digitsNumber = ApiConstants.REQUIRED_MIN_DIGITS_NUMBER_IN_PASSWORD;
+ for (int i = 0; i < trim.length(); i++) {
+ String currentLetter = String.valueOf(trim.charAt(i));
+ if (!ApiConstants.PASSWORD_ALL_CHARACTERS.contains(currentLetter)) {
+ return true;
+ }
+ charactersNumber -= ApiConstants.PASSWORD_CHARACTERS.contains(currentLetter) ? 1 : 0;
+ lettersUCaseNumber -= ApiConstants.PASSWORD_LETTERS_UPPER_CASE.contains(currentLetter) ? 1 : 0;
+ lettersLCaseNumber -= ApiConstants.PASSWORD_LETTERS_LOWER_CASE.contains(currentLetter) ? 1 : 0;
+ digitsNumber -= ApiConstants.PASSWORD_DIGITS.contains(currentLetter) ? 1 : 0;
+ }
+ return ((charactersNumber > 0) || (lettersUCaseNumber > 0) || (lettersLCaseNumber > 0) || (digitsNumber > 0));
+ }
+}
\ No newline at end of file
diff --git a/gateway-service/src/main/resources/application.yml b/gateway-service/src/main/resources/application.yml
index 75b9c48..0ff4bd1 100644
--- a/gateway-service/src/main/resources/application.yml
+++ b/gateway-service/src/main/resources/application.yml
@@ -6,6 +6,16 @@ spring:
application:
name: gateway-service
+ # ---- R2DBC (reactive DB) ----
+ r2dbc:
+ url: r2dbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:appdb}
+ username: ${DB_USERNAME:app}
+ password: ${DB_PASSWORD:}
+ pool:
+ initial-size: 2
+ max-size: 10
+ max-idle-time: 30m
+
cloud:
consul:
host: ${CONSUL_HOST:localhost}
@@ -18,27 +28,21 @@ spring:
prefer-ip-address: true
instance-id: ${spring.application.name}:${random.value}
- # Spring Cloud Gateway 2025.0 — new prefix: spring.cloud.gateway.server.webflux
gateway:
server:
webflux:
- # Trust Nginx reverse proxy for forwarded headers
trusted-proxies: 127\.0\.0\.1|10\.0\.0\..*|172\.1[6-9]\..*|172\.2[0-9]\..*|172\.3[0-1]\..*|192\.168\..*
-
discovery:
locator:
enabled: true
lower-case-service-id: true
-
httpclient:
connect-timeout: 5000
response-timeout: 60s
-
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
-
routes:
- # RAG Service - actuator (health, info)
+ # RAG Service - actuator
- id: rag-service-actuator
uri: lb://rag-service
predicates:
@@ -57,19 +61,29 @@ spring:
- RewritePath=/api/rag(?/?.*), ${segment}
- AddRequestHeader=X-Forwarded-Prefix, /api/rag
- # Analytics Service (will be added later)
- # - id: analytics-service-api
- # uri: lb://analytics-service
- # predicates:
- # - Path=/api/analytics/**
- # - Method=GET,POST
- # filters:
- # - RewritePath=/api/analytics(?/?.*), ${segment}
+# ---- JWT ----
+jwt:
+ secret: ${JWT_SECRET:}
+ expiration: ${JWT_EXPIRATION:103600000}
+# ---- Auth path config ----
+auth:
+ public-paths:
+ - /api/auth/login
+ - /api/auth/register
+ - /api/auth/refresh/token
+ - /actuator/**
+ - /api/*/v3/api-docs/**
+ - /api/*/swagger-ui/**
+ admin-paths:
+ - /api/*/admin/**
+
+# ---- CORS ----
gateway:
cors:
allowed-origins: ${CORS_ORIGINS:*}
+# ---- Actuator ----
management:
endpoints:
web:
@@ -81,8 +95,10 @@ management:
gateway:
enabled: true
+# ---- Logging ----
logging:
level:
root: INFO
com.posthub.gateway: DEBUG
org.springframework.cloud.gateway: INFO
+ org.springframework.r2dbc: INFO
\ No newline at end of file
diff --git a/rag-service/pom.xml b/rag-service/pom.xml
index 96d3c12..ceef7bc 100644
--- a/rag-service/pom.xml
+++ b/rag-service/pom.xml
@@ -128,23 +128,6 @@
spring-security-test
test