Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rentplace/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies {
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
implementation 'org.flywaydb:flyway-core'
implementation 'com.github.vladimir-bukhtoyarov:bucket4j-core:8.0.1'
implementation("commons-codec:commons-codec:1.17.2")

implementation("org.springframework.boot:spring-boot-starter-mail:3.4.3")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public CorsConfigurationSource corsConfigurationSource() {
config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
config.setAllowedHeaders(List.of("*"));
config.setExposedHeaders(List.of("Authorization", "Cache-Control", "Content-Type", "Set-Cookie"));
config.setExposedHeaders(List.of("Authorization", "Cache-Control", "Content-Type", "Set-Cookie", "User-Agent", "X-Forwarded-For"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.security.auth.message.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import kattsyn.dev.rentplace.dtos.requests.CodeRequest;
import kattsyn.dev.rentplace.dtos.requests.JwtRequest;
import kattsyn.dev.rentplace.dtos.requests.RefreshJwtRequest;
Expand Down Expand Up @@ -38,9 +39,11 @@ public ResponseEntity<CodeResponse> requestCode(@RequestBody CodeRequest codeReq
description = "Получает email и код с почты. Возвращает JWT токены"
)
@PostMapping("/login")
public ResponseEntity<JwtResponse> login(@RequestBody JwtRequest authRequest/*,
public ResponseEntity<JwtResponse> login(HttpServletRequest request, @RequestBody JwtRequest authRequest/*,
HttpServletResponse response*/) throws AuthException {
JwtResponse tokens = authService.login(authRequest);


JwtResponse tokens = authService.login(authRequest, request);

/*
ResponseCookie refreshCookie = ResponseCookie.from("refreshToken", tokens.getRefreshToken())
Expand Down Expand Up @@ -76,10 +79,9 @@ public ResponseEntity<JwtResponse> login(@RequestBody JwtRequest authRequest/*,
description = "Получает email и код с почты. Возвращает JWT токены. Пускает только администраторов."
)
@PostMapping("/admin/login")
public ResponseEntity<JwtResponse> adminLogin(@RequestBody JwtRequest authRequest/*,
public ResponseEntity<JwtResponse> adminLogin(@RequestBody JwtRequest authRequest, HttpServletRequest httpServletRequest/*,
HttpServletResponse response*/) throws AuthException {
JwtResponse tokens = authService.adminLogin(authRequest);

JwtResponse tokens = authService.adminLogin(authRequest, httpServletRequest);
return ResponseEntity.ok()
.body(tokens);
}
Expand All @@ -89,9 +91,9 @@ public ResponseEntity<JwtResponse> adminLogin(@RequestBody JwtRequest authReques
description = "Получает email и код с почты, а также имя и фамилию пользователя. Возвращает JWT токены"
)
@PostMapping("/register")
public ResponseEntity<JwtResponse> register(@RequestBody RegisterRequest registerRequest/*,
public ResponseEntity<JwtResponse> register(@RequestBody RegisterRequest registerRequest, HttpServletRequest httpServletRequest/*,
HttpServletResponse response*/) throws AuthException {
JwtResponse tokens = authService.register(registerRequest);
JwtResponse tokens = authService.register(registerRequest, httpServletRequest);

return ResponseEntity.ok()
.body(tokens);
Expand All @@ -109,14 +111,13 @@ public ResponseEntity<Void> checkCode(@RequestBody JwtRequest authRequest/*,
}



@Operation(
summary = "Запрос на обновление AccessToken'а",
description = "Получает RefreshToken, возвращает новый AccessToken"
)
@PostMapping("/token")
public ResponseEntity<JwtResponse> getNewAccessToken(@RequestBody RefreshJwtRequest request) {
final JwtResponse token = authService.getAccessToken(request.getRefreshToken());
public ResponseEntity<JwtResponse> getNewAccessToken(@RequestBody RefreshJwtRequest request, HttpServletRequest httpServletRequest) throws AuthException {
final JwtResponse token = authService.getAccessToken(request.getRefreshToken(), httpServletRequest);
return ResponseEntity.ok(token);
}

Expand All @@ -125,8 +126,8 @@ public ResponseEntity<JwtResponse> getNewAccessToken(@RequestBody RefreshJwtRequ
description = "Принимает еще не истекший RefreshToken и возвращает новый, продленный."
)
@PostMapping("/refresh")
public ResponseEntity<JwtResponse> refresh(/*@CookieValue(name = "refreshToken") String refreshToken, HttpServletResponse response*/ @RequestBody RefreshJwtRequest request) throws AuthException {
JwtResponse jwtResponse = authService.refresh(request.getRefreshToken());
public ResponseEntity<JwtResponse> refresh(/*@CookieValue(name = "refreshToken") String refreshToken, HttpServletResponse response*/ @RequestBody RefreshJwtRequest refreshJwtRequest, HttpServletRequest request) throws AuthException {
JwtResponse jwtResponse = authService.refresh(refreshJwtRequest.getRefreshToken(), request);

/*
Cookie refreshCookie = new Cookie("refreshToken", jwtResponse.getRefreshToken());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package kattsyn.dev.rentplace.entities;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "refresh_tokens")
public class RefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "refresh_token_id")
private Long refreshTokenId;
@Column(name = "token")
private String token;
@Column(name = "device_hash")
private String deviceHash;
@Column(name = "created_at")
private LocalDateTime createdAt;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,7 @@ public class User {

@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Property> properties = new HashSet<>();

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<RefreshToken> tokens = new HashSet<>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package kattsyn.dev.rentplace.repositories;

import kattsyn.dev.rentplace.entities.RefreshToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {

@Query("""
SELECT DISTINCT r
FROM RefreshToken r
WHERE r.user.userId=:userId
""")
List<RefreshToken> findAllByUserId(Long userId);

@Query("""
SELECT DISTINCT r
FROM RefreshToken r
WHERE r.user.userId=:userId AND r.token=:refreshToken
""")
Optional<RefreshToken> findByUserAndToken(Long userId, String refreshToken);

void deleteByCreatedAtBefore(LocalDateTime createdAt);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kattsyn.dev.rentplace.schedulers;

import jakarta.transaction.Transactional;
import kattsyn.dev.rentplace.repositories.RefreshTokenRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Slf4j
@Component
@RequiredArgsConstructor
public class ExpiredRefreshTokenCleanupTask {

private final RefreshTokenRepository refreshTokenRepository;
@Value("${jwt.expiration-time-in-days.refresh}")
private int refreshTokenExpTimeInDays;

@Scheduled(cron = "0 0 3 * * ?")
@Transactional
public void cleanExpiredRefreshTokens() {
LocalDateTime cutoff = LocalDateTime.now().minusDays(refreshTokenExpTimeInDays);
refreshTokenRepository.deleteByCreatedAtBefore(cutoff);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package kattsyn.dev.rentplace.services;

import jakarta.security.auth.message.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import kattsyn.dev.rentplace.dtos.requests.JwtRequest;
import kattsyn.dev.rentplace.dtos.responses.CodeResponse;
import kattsyn.dev.rentplace.dtos.responses.JwtResponse;
Expand All @@ -11,15 +12,15 @@ public interface AuthService {

CodeResponse getCodeResponse(String email);

JwtResponse login(JwtRequest authRequest) throws AuthException;
JwtResponse login(JwtRequest authRequest, HttpServletRequest httpServletRequest) throws AuthException;

JwtResponse adminLogin(JwtRequest authRequest) throws AuthException;
JwtResponse adminLogin(JwtRequest authRequest, HttpServletRequest httpServletRequest) throws AuthException;

JwtResponse register(RegisterRequest registerRequest) throws AuthException;
JwtResponse register(RegisterRequest registerRequest, HttpServletRequest httpServletRequest) throws AuthException;

JwtResponse getAccessToken(String refreshToken);
JwtResponse getAccessToken(String refreshToken, HttpServletRequest httpServletRequest) throws AuthException;

JwtResponse refresh(String refreshToken) throws AuthException;
JwtResponse refresh(String refreshToken, HttpServletRequest request) throws AuthException;

UserDTO getUserInfo() throws AuthException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kattsyn.dev.rentplace.services;

import io.micrometer.common.lang.NonNull;
import jakarta.security.auth.message.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import kattsyn.dev.rentplace.dtos.responses.JwtResponse;
import kattsyn.dev.rentplace.entities.User;

public interface RefreshTokenService {

void put(String refreshToken, User user, HttpServletRequest httpServletRequest);

JwtResponse refresh(String refreshToken, HttpServletRequest httpServletRequest) throws AuthException;

JwtResponse refreshAccessToken(@NonNull String refreshToken, HttpServletRequest request) throws AuthException;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package kattsyn.dev.rentplace.services.impl;

import io.jsonwebtoken.Claims;
import io.micrometer.common.lang.NonNull;
import jakarta.security.auth.message.AuthException;
import jakarta.servlet.http.HttpServletRequest;
import kattsyn.dev.rentplace.dtos.requests.JwtRequest;
import kattsyn.dev.rentplace.dtos.responses.CodeResponse;
import kattsyn.dev.rentplace.dtos.responses.JwtResponse;
Expand All @@ -14,6 +14,7 @@
import kattsyn.dev.rentplace.enums.UserStatus;
import kattsyn.dev.rentplace.exceptions.ForbiddenException;
import kattsyn.dev.rentplace.services.AuthService;
import kattsyn.dev.rentplace.services.RefreshTokenService;
import kattsyn.dev.rentplace.services.UserService;
import kattsyn.dev.rentplace.auth.JwtProvider;
import kattsyn.dev.rentplace.services.VerificationCodeService;
Expand All @@ -23,28 +24,23 @@
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl implements AuthService {

//todo: сделать хранение Refresh Токенов в БД, вместе с ip, либо именами устройств.
//TODO: также при превышении кол-ва макс устройств разлогинить везде пользователя

private final UserService userService;
private final Map<String, String> refreshStorage = new HashMap<>();
private final JwtProvider jwtProvider;
private final VerificationCodeService verificationCodeService;
private final RefreshTokenService refreshTokenService;

@Override
public JwtResponse register(@NonNull RegisterRequest registerRequest) throws AuthException {
public JwtResponse register(@NonNull RegisterRequest registerRequest, HttpServletRequest httpServletRequest) throws AuthException {
User user = userService.createUserWithRegisterRequest(registerRequest);

return getJwtResponse(user, registerRequest.getEmail(), registerRequest.getCode());
return getJwtResponse(user, registerRequest.getEmail(), registerRequest.getCode(), httpServletRequest);
}

@Override
Expand All @@ -65,28 +61,28 @@ public CodeResponse getCodeResponse(String email) {
}

@Override
public JwtResponse login(@NonNull JwtRequest authRequest) throws AuthException {
public JwtResponse login(@NonNull JwtRequest authRequest, HttpServletRequest httpServletRequest) throws AuthException {
final User user = userService.getUserByEmail(authRequest.getEmail());

return getJwtResponse(user, authRequest.getEmail(), authRequest.getCode());
return getJwtResponse(user, authRequest.getEmail(), authRequest.getCode(), httpServletRequest);
}

@Override
public JwtResponse adminLogin(@NonNull JwtRequest authRequest) throws AuthException {
public JwtResponse adminLogin(@NonNull JwtRequest authRequest, HttpServletRequest httpServletRequest) throws AuthException {
final User user = userService.getUserByEmail(authRequest.getEmail());

if (user.getRole() != Role.ROLE_ADMIN) {
throw new ForbiddenException("You are not allowed to access admin-panel.");
}

return getJwtResponse(user, authRequest.getEmail(), authRequest.getCode());
return getJwtResponse(user, authRequest.getEmail(), authRequest.getCode(), httpServletRequest);
}

private JwtResponse getJwtResponse(User user, String email, String code) throws AuthException {
private JwtResponse getJwtResponse(User user, String email, String code, HttpServletRequest httpServletRequest) throws AuthException {
if ((email.equals("testadmin@gmail.com") && code.equals("12345")) || verificationCodeService.validateCode(email, code)) { //todo: delete test user
final String accessToken = jwtProvider.generateAccessToken(user);
final String refreshToken = jwtProvider.generateRefreshToken(user);
refreshStorage.put(user.getEmail(), refreshToken);
refreshTokenService.put(refreshToken, user, httpServletRequest);
return new JwtResponse(accessToken, refreshToken);
} else {
throw new AuthException("Код неправильный");
Expand All @@ -98,34 +94,16 @@ public void validateCode(JwtRequest request) {
verificationCodeService.validateCode(request.getEmail(), request.getCode());
}

public JwtResponse getAccessToken(@NonNull String refreshToken) {
if (jwtProvider.validateRefreshToken(refreshToken)) {
final Claims claims = jwtProvider.getRefreshClaims(refreshToken);
final String email = claims.getSubject();
final String saveRefreshToken = refreshStorage.get(email);
if (saveRefreshToken != null && saveRefreshToken.equals(refreshToken)) {
final User user = userService.getUserByEmail(email);
final String accessToken = jwtProvider.generateAccessToken(user);
return new JwtResponse(accessToken, null);
}
public JwtResponse getAccessToken(@NonNull String refreshToken, HttpServletRequest httpServletRequest) {
try {
return refreshTokenService.refreshAccessToken(refreshToken, httpServletRequest);
} catch (AuthException e) {
return new JwtResponse(null, null);
}
return new JwtResponse(null, null);
}

public JwtResponse refresh(@NonNull String refreshToken) throws AuthException {
if (jwtProvider.validateRefreshToken(refreshToken)) {
final Claims claims = jwtProvider.getRefreshClaims(refreshToken);
final String email = claims.getSubject();
final String saveRefreshToken = refreshStorage.get(email);
if (saveRefreshToken != null && saveRefreshToken.equals(refreshToken)) {
final User user = userService.getUserByEmail(email);
final String accessToken = jwtProvider.generateAccessToken(user);
final String newRefreshToken = jwtProvider.generateRefreshToken(user);
refreshStorage.put(user.getEmail(), newRefreshToken);
return new JwtResponse(accessToken, newRefreshToken);
}
}
throw new AuthException("Невалидный JWT токен");
public JwtResponse refresh(@NonNull String refreshToken, HttpServletRequest request) throws AuthException {
return refreshTokenService.refresh(refreshToken, request);
}

public UserDTO getUserInfo() throws AuthException {
Expand All @@ -137,6 +115,4 @@ public UserDTO getUserInfo() throws AuthException {
String email = authentication.getName();
return userService.getUserDTOByEmail(email);
}


}
Loading
Loading