From 0da04044888a82e0ec81a9b7f640121f4d6b1224 Mon Sep 17 00:00:00 2001 From: vladp Date: Mon, 5 Jan 2026 16:26:46 +0700 Subject: [PATCH 1/4] add token life by hour --- .../nocopy/configuration/HandlerConfig.java | 4 +- .../nocopy/controller/ApiController.java | 17 +++------ .../java/ru/soune/nocopy/dto/MessageCode.java | 1 + .../soune/nocopy/dto/TokenProcessRequest.java | 11 ++++++ .../ru/soune/nocopy/entity/AuthToken.java | 6 +-- .../nocopy/handler/AuthRequestHandler.java | 37 ++++++++++++++++++ .../nocopy/handler/FileEntityHandler.java | 30 +++++---------- .../nocopy/handler/FileUploadHandler.java | 10 +++-- .../nocopy/handler/LoginRequestHandler.java | 9 ++++- .../nocopy/handler/LogoutRequestHandler.java | 2 +- .../nocopy/handler/RegRequestHandler.java | 6 ++- .../repository/AuthTokenRepository.java | 5 ++- .../service/{ => auth}/AuthService.java | 25 ++++++++++-- .../auth/CleanupTokenSessionsService.java | 38 +++++++++++++++++++ src/main/resources/application.yaml | 1 + .../soune/nocopy/service/AuthServiceTest.java | 1 + 16 files changed, 150 insertions(+), 53 deletions(-) create mode 100644 src/main/java/ru/soune/nocopy/dto/TokenProcessRequest.java create mode 100644 src/main/java/ru/soune/nocopy/handler/AuthRequestHandler.java rename src/main/java/ru/soune/nocopy/service/{ => auth}/AuthService.java (81%) create mode 100644 src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java index 5b3c744..f1577c2 100644 --- a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -16,7 +16,8 @@ public class HandlerConfig { LoginRequestHandler login, FileUploadHandler upload, FileEntityHandler file, - LogoutRequestHandler logoutHandler + LogoutRequestHandler logoutHandler, + AuthRequestHandler authRequestHandler ) { Map map = new HashMap<>(); map.put(20001, login); @@ -24,6 +25,7 @@ public class HandlerConfig { map.put(20004, upload); map.put(20005, file); map.put(20006, logoutHandler); + map.put(20007, authRequestHandler); return map; } diff --git a/src/main/java/ru/soune/nocopy/controller/ApiController.java b/src/main/java/ru/soune/nocopy/controller/ApiController.java index dbbe9df..9e5d257 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -19,12 +19,11 @@ import ru.soune.nocopy.dto.file.ChunkUploadResponse; import ru.soune.nocopy.dto.file.CompleteUploadResponse; import ru.soune.nocopy.dto.file.FileEntityResponse; import ru.soune.nocopy.dto.file.UploadProgress; -import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.entity.file.UploadStatus; import ru.soune.nocopy.exception.*; import ru.soune.nocopy.handler.*; -import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.service.auth.AuthService; import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileUploadService; @@ -48,7 +47,7 @@ public class ApiController { private final FileEntityService fileEntityService; - private final AuthTokenRepository authTokenRepository; + private final AuthService authService; @PostMapping("/v{version}/data") public ResponseEntity handlePostRequest(@RequestBody BaseRequest request, @@ -71,6 +70,9 @@ public class ApiController { return ResponseEntity.ok().body(response); } catch (ValidationException e) { return createValidationErrorResponse(e.getBindingResult(), e.getMsgId()); + } catch (NotFoundAuthToken e) { + return ResponseEntity.ok().body(new BaseResponse(msgId, MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(), + MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(), new HashMap<>())); } catch (NotValidFieldException e) { throw e; } catch (Exception e) { @@ -257,7 +259,7 @@ public class ApiController { errorData)); } - Long userId = getUserIdFromToken(tokenHeader); + Long userId = authService.useUserAuthToken(tokenHeader); FileEntityResponse entityResponse = fileEntityService.getById(fileId, version); if (!entityResponse.getUserId().equals(userId)) { @@ -379,11 +381,4 @@ public class ApiController { mimeType.startsWith("video") || mimeType.startsWith("audio"); } - - private Long getUserIdFromToken(String tokenHeader) { - String token = tokenHeader.replace("Bearer ", ""); - AuthToken authToken = authTokenRepository.findByToken(token) - .orElseThrow(() -> new NotFoundAuthToken("Token not found")); - return authToken.getUser().getId(); - } } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/dto/MessageCode.java b/src/main/java/ru/soune/nocopy/dto/MessageCode.java index b945f64..36d8d9f 100644 --- a/src/main/java/ru/soune/nocopy/dto/MessageCode.java +++ b/src/main/java/ru/soune/nocopy/dto/MessageCode.java @@ -18,6 +18,7 @@ public enum MessageCode { AUTH_EMAIL_NOT_FOUND(4, "Email not found"), AUTH_EMAIL_OR_TOKEN_NOT_FOUND(4, "Email or Token not found "), AUTH_TOKEN_MISMATCH(4, "Token mismatch"), + AUTH_TOKEN_NOT_FOUND(4, "Token not found"), FILE_NOT_FOUND(4, "File not found"), AUTH_PASSWORD_NOT_MATCHES(2, "Password does not match"); diff --git a/src/main/java/ru/soune/nocopy/dto/TokenProcessRequest.java b/src/main/java/ru/soune/nocopy/dto/TokenProcessRequest.java new file mode 100644 index 0000000..646f9c6 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/dto/TokenProcessRequest.java @@ -0,0 +1,11 @@ +package ru.soune.nocopy.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class TokenProcessRequest { + + @JsonProperty("token") + private String token; +} diff --git a/src/main/java/ru/soune/nocopy/entity/AuthToken.java b/src/main/java/ru/soune/nocopy/entity/AuthToken.java index 63cf34a..6fa1a9c 100644 --- a/src/main/java/ru/soune/nocopy/entity/AuthToken.java +++ b/src/main/java/ru/soune/nocopy/entity/AuthToken.java @@ -30,7 +30,7 @@ public class AuthToken { private String token; @Column(name = "expires_at", nullable = false) - private LocalDateTime expiresAt = LocalDateTime.now().plusDays(30); + private LocalDateTime expiresAt = LocalDateTime.now().plusHours(1); @CreatedDate @Column(name = "created_at", updatable = false, nullable = false) @@ -41,8 +41,4 @@ public class AuthToken { @Column(name = "is_active") private Boolean isActive = true; - - public boolean isValid() { - return Boolean.TRUE.equals(isActive) && expiresAt.isAfter(LocalDateTime.now()); - } } diff --git a/src/main/java/ru/soune/nocopy/handler/AuthRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/AuthRequestHandler.java new file mode 100644 index 0000000..c79ec06 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/handler/AuthRequestHandler.java @@ -0,0 +1,37 @@ +package ru.soune.nocopy.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import ru.soune.nocopy.dto.BaseRequest; +import ru.soune.nocopy.dto.BaseResponse; +import ru.soune.nocopy.dto.MessageCode; +import ru.soune.nocopy.dto.TokenProcessRequest; +import ru.soune.nocopy.exception.NotFoundAuthToken; +import ru.soune.nocopy.service.auth.AuthService; + +import java.util.Map; + +@Slf4j +@Component +@RequiredArgsConstructor +public class AuthRequestHandler implements RequestHandler { + + private final ObjectMapper objectMapper; + + private final AuthService authService; + + @Override + public BaseResponse handle(BaseRequest request) throws NotFoundAuthToken { + TokenProcessRequest tokenProcessRequest = objectMapper.convertValue(request.getMessageBody(), + TokenProcessRequest.class); + + authService.useUserAuthToken(tokenProcessRequest.getToken()); + + return new BaseResponse(request.getMsgId(), + MessageCode.SUCCESS.getCode(), + "Token processed successfully", + Map.of("token", tokenProcessRequest.getToken())); + } +} diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java index 968a3b7..526ff9f 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -6,13 +6,13 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import ru.soune.nocopy.dto.*; import ru.soune.nocopy.dto.file.*; -import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.exception.FileEntityNotFoundException; import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.FileEntityRepository; +import ru.soune.nocopy.service.auth.AuthService; import ru.soune.nocopy.service.file.FileEntityService; import java.util.Arrays; @@ -24,6 +24,8 @@ import java.util.List; public class FileEntityHandler implements RequestHandler { private final FileEntityService fileEntityService; + private final AuthService authService; + private final AuthTokenRepository authTokenRepository; private final ObjectMapper objectMapper; @@ -75,7 +77,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) { try { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion()); if (!fileInfo.getUserId().equals(userId)) { @@ -106,7 +108,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) { try { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(), request.getVersion()); @@ -138,7 +140,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) { try { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; @@ -165,7 +167,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) { try { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1; int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20; @@ -212,7 +214,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) { try { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); long usage = fileEntityService.getUserStorageUsed(userId); StorageUsageResponse response = StorageUsageResponse.builder() @@ -242,7 +244,7 @@ public class FileEntityHandler implements RequestHandler { private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) { try { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); String fileId = fileRequest.getFileId(); FileEntityResponse fileInfo = fileEntityService.getById(fileId, request.getVersion()); DeleteFileResponse response; @@ -289,20 +291,6 @@ public class FileEntityHandler implements RequestHandler { } } - private Long getUserIdFromToken(String token) { - if (token == null || token.isBlank()) { - throw new NotFoundAuthToken("Token is required"); - } - - if (token.startsWith("Bearer ")) { - token = token.replace("Bearer ", ""); - } - - AuthToken authToken = authTokenRepository.findByToken(token) - .orElseThrow(() -> new NotFoundAuthToken("Token not found")); - return authToken.getUser().getId(); - } - private String formatFileSize(long size) { if (size < 1024) return size + " B"; int exp = (int) (Math.log(size) / Math.log(1024)); diff --git a/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java b/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java index 4cb33b5..d2bbf4c 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileUploadHandler.java @@ -17,6 +17,7 @@ import ru.soune.nocopy.exception.*; import ru.soune.nocopy.handler.validator.FileUploadRequestValidator; import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.FileUploadSessionRepository; +import ru.soune.nocopy.service.auth.AuthService; import ru.soune.nocopy.service.file.FileUploadService; import java.util.*; @@ -28,6 +29,8 @@ import java.util.stream.Collectors; public class FileUploadHandler implements RequestHandler { private final FileUploadService fileUploadService; + private final AuthService authService; + private final AuthTokenRepository authTokenRepository; private final FileUploadSessionRepository fileUploadSessionRepository; @@ -86,10 +89,9 @@ public class FileUploadHandler implements RequestHandler { MessageCode.INVALID_TOKEN.getDescription(), Map.of("token", token)); } - AuthToken authToken = tokenOptional.orElseThrow(() -> new NotFoundAuthToken("Token not found")); - + Long userId = authService.useUserAuthToken(token); BindingResult bindingResult = new BeanPropertyBindingResult(fileRequest, "fileRequest"); - fileUploadRequestValidator.validate(fileRequest, bindingResult); + fileUploadRequestValidator.validate(fileRequest, bindingResult); if (bindingResult.hasErrors()) { Map fieldErrors = bindingResult.getFieldErrors() @@ -105,7 +107,7 @@ public class FileUploadHandler implements RequestHandler { } FileUploadSession session = fileUploadService.initUpload( - authToken.getUser().getId(), + userId, fileRequest.getFileName(), fileRequest.getFileType(), fileRequest.getExtension(), diff --git a/src/main/java/ru/soune/nocopy/handler/LoginRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/LoginRequestHandler.java index 95abf63..6add2a0 100644 --- a/src/main/java/ru/soune/nocopy/handler/LoginRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/LoginRequestHandler.java @@ -7,7 +7,7 @@ import ru.soune.nocopy.dto.*; import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.repository.UserRepository; -import ru.soune.nocopy.service.AuthService; +import ru.soune.nocopy.service.auth.AuthService; import java.util.Arrays; import java.util.Map; @@ -17,7 +17,9 @@ import java.util.Map; public class LoginRequestHandler implements RequestHandler { private final UserRepository userRepository; + private final AuthService authService; + private final ObjectMapper objectMapper; @Override @@ -34,9 +36,12 @@ public class LoginRequestHandler implements RequestHandler { } AuthToken authToken = authService.login(loginRequest); + String token = authToken.getToken(); LoginAnswer loginAnswer = new LoginAnswer(); - loginAnswer.setToken(authToken.getToken()); + loginAnswer.setToken(token); + + authService.useUserAuthToken(token); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), loginAnswer); diff --git a/src/main/java/ru/soune/nocopy/handler/LogoutRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/LogoutRequestHandler.java index 44fa253..e12284c 100644 --- a/src/main/java/ru/soune/nocopy/handler/LogoutRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/LogoutRequestHandler.java @@ -9,7 +9,7 @@ import ru.soune.nocopy.entity.User; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.UserRepository; -import ru.soune.nocopy.service.AuthService; +import ru.soune.nocopy.service.auth.AuthService; import java.util.Objects; import java.util.Optional; diff --git a/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java index 349f0a7..86fcbc7 100644 --- a/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java @@ -12,7 +12,7 @@ import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.exception.ValidationException; import ru.soune.nocopy.handler.validator.RegRequestValidator; import ru.soune.nocopy.repository.UserRepository; -import ru.soune.nocopy.service.AuthService; +import ru.soune.nocopy.service.auth.AuthService; import java.util.Arrays; import java.util.Map; @@ -52,9 +52,11 @@ public class RegRequestHandler implements RequestHandler { } AuthToken authToken = authService.register(regRequest); + String token = authToken.getToken(); + authService.useUserAuthToken(token); RegAnswer regAnswer = new RegAnswer(); - regAnswer.setToken(authToken.getToken()); + regAnswer.setToken(token); return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(), MessageCode.SUCCESS.getDescription(), regAnswer); diff --git a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java index 74826fd..9328753 100644 --- a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java @@ -4,11 +4,12 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import ru.soune.nocopy.entity.AuthToken; -import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Optional; @Repository public interface AuthTokenRepository extends JpaRepository { - Optional findByTokenAndExpiresAtAfter(String token, LocalDate expiresAtAfter); + Optional findByLastUsedAtAndExpiresAtBefore(LocalDateTime lastUsedAt, LocalDateTime expiresAtBefore); + Optional findByLastUsedAtBefore(LocalDateTime lastUsedAt); Optional findByToken(String token); } diff --git a/src/main/java/ru/soune/nocopy/service/AuthService.java b/src/main/java/ru/soune/nocopy/service/auth/AuthService.java similarity index 81% rename from src/main/java/ru/soune/nocopy/service/AuthService.java rename to src/main/java/ru/soune/nocopy/service/auth/AuthService.java index 2377b2b..cf8b18c 100644 --- a/src/main/java/ru/soune/nocopy/service/AuthService.java +++ b/src/main/java/ru/soune/nocopy/service/auth/AuthService.java @@ -1,13 +1,13 @@ -package ru.soune.nocopy.service; +package ru.soune.nocopy.service.auth; import lombok.RequiredArgsConstructor; -import org.springframework.context.MessageSource; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.soune.nocopy.dto.*; import ru.soune.nocopy.entity.AuthToken; import ru.soune.nocopy.entity.User; +import ru.soune.nocopy.exception.NotFoundAuthToken; import ru.soune.nocopy.exception.NotValidFieldException; import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.UserRepository; @@ -29,8 +29,6 @@ public class AuthService { private final PasswordEncoder passwordEncoder; - private final MessageSource messageSource; - private final SecureRandom secureRandom = new SecureRandom(); @Transactional @@ -81,6 +79,25 @@ public class AuthService { return authTokenRepository.save(authToken); } + @Transactional + public Long useUserAuthToken(String token) { + if (token == null || token.isBlank()) { + throw new NotFoundAuthToken("Token is required"); + } + + if (token.startsWith("Bearer ")) { + token = token.replace("Bearer ", ""); + } + + AuthToken authToken = authTokenRepository.findByToken(token) + .orElseThrow(() -> new NotFoundAuthToken("Token not found")); + authToken.setLastUsedAt(LocalDateTime.now()); + + authTokenRepository.save(authToken); + + return authToken.getUser().getId(); + } + @Transactional public void logout(String token) { authTokenRepository.findByToken(token) diff --git a/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java b/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java new file mode 100644 index 0000000..c18cda3 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java @@ -0,0 +1,38 @@ +package ru.soune.nocopy.service.auth; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import ru.soune.nocopy.entity.AuthToken; +import ru.soune.nocopy.repository.AuthTokenRepository; + +import java.time.LocalDateTime; +import java.util.List; + +@Slf4j +@Component +@RequiredArgsConstructor +public class CleanupTokenSessionsService { + @Autowired + private AuthTokenRepository authTokenRepository; + + @Value("${file.storage.auth-token-life-hours}") + private int authTokenLife; + + @Transactional + @Scheduled(fixedDelay = 30000) + public void cleanupExpiredFiles() { + LocalDateTime hourBefore = LocalDateTime.now().minusHours(authTokenLife); + + List tokensForDelete = authTokenRepository.findByLastUsedAtBefore(hourBefore).stream().toList(); + List tokensDeleteWithNullUpdate = authTokenRepository.findByLastUsedAtAndExpiresAtBefore( + null, hourBefore).stream().toList(); + + authTokenRepository.deleteAll(tokensForDelete); + authTokenRepository.deleteAll(tokensDeleteWithNullUpdate); + } +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1fd4fab..68701ca 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -30,6 +30,7 @@ file: max-retry-attempts: ${MAX_RETRY_ATTEMPTS:3} chunk-timeout-ms: ${CHUNK_TIMEOUT_MS:300000} # 5 минут temp-ttl-hours: ${TEMP_TTL_HOURS:72} # 3 дня + auth-token-life-hours: ${AUTH_TOKEN_LIFE:1} # 1 час session-expiry-hours: ${SESSION_EXPIRY_HOURS:24} security: diff --git a/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java index 499d694..a16af7d 100644 --- a/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java +++ b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java @@ -15,6 +15,7 @@ import ru.soune.nocopy.exception.NotValidationPasswordException; import ru.soune.nocopy.exception.UserNotFoundException; import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.UserRepository; +import ru.soune.nocopy.service.auth.AuthService; import java.util.Optional; -- 2.54.0 From f1222ede57b2c5d01cd2331b7187f39af944f9d4 Mon Sep 17 00:00:00 2001 From: vladp Date: Mon, 5 Jan 2026 16:31:40 +0700 Subject: [PATCH 2/4] add token life by hour --- src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java index f1577c2..4ba97da 100644 --- a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -25,7 +25,7 @@ public class HandlerConfig { map.put(20004, upload); map.put(20005, file); map.put(20006, logoutHandler); - map.put(20007, authRequestHandler); + map.put(20008, authRequestHandler); return map; } -- 2.54.0 From 3e06040123c7a39579068f8cf046e44d18c8eeaf Mon Sep 17 00:00:00 2001 From: vladp Date: Fri, 9 Jan 2026 22:55:06 +0700 Subject: [PATCH 3/4] NCBACK-27 fix build --- .../nocopy/handler/FileEntityHandler.java | 5 +- .../soune/nocopy/service/AuthServiceTest.java | 102 ------------------ 2 files changed, 1 insertion(+), 106 deletions(-) delete mode 100644 src/test/java/ru/soune/nocopy/service/AuthServiceTest.java diff --git a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java index 040b037..a869f40 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -10,7 +10,6 @@ import ru.soune.nocopy.entity.file.FileEntity; import ru.soune.nocopy.entity.file.FileStatus; import ru.soune.nocopy.exception.FileEntityNotFoundException; import ru.soune.nocopy.exception.NotFoundAuthToken; -import ru.soune.nocopy.repository.AuthTokenRepository; import ru.soune.nocopy.repository.FileEntityRepository; import ru.soune.nocopy.service.auth.AuthService; import ru.soune.nocopy.service.file.FileEntityService; @@ -30,8 +29,6 @@ public class FileEntityHandler implements RequestHandler { private final FileStatsService fileStatsService; - private final AuthTokenRepository authTokenRepository; - private final ObjectMapper objectMapper; private final FileEntityRepository fileEntityRepository; @@ -82,7 +79,7 @@ public class FileEntityHandler implements RequestHandler { } private BaseResponse handleGetFilesUserInfo(BaseRequest request, FileEntityRequest fileRequest) { - Long userId = getUserIdFromToken(fileRequest.getToken()); + Long userId = authService.useUserAuthToken(fileRequest.getToken()); FileInfoUserResponse userFileStats = fileStatsService.getUserFileStats(userId); return new BaseResponse(request.getMsgId(), diff --git a/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java b/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java deleted file mode 100644 index a16af7d..0000000 --- a/src/test/java/ru/soune/nocopy/service/AuthServiceTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package ru.soune.nocopy.service; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.springframework.context.MessageSource; -import org.springframework.security.crypto.password.PasswordEncoder; -import ru.soune.nocopy.dto.LoginRequest; -import ru.soune.nocopy.entity.AuthToken; -import ru.soune.nocopy.entity.User; -import ru.soune.nocopy.exception.NotValidationPasswordException; -import ru.soune.nocopy.exception.UserNotFoundException; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.repository.UserRepository; -import ru.soune.nocopy.service.auth.AuthService; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class AuthServiceTest { - - @Mock - private UserRepository userRepository; - - @Mock - private AuthTokenRepository authTokenRepository; - - @Mock - private PasswordEncoder passwordEncoder; - - @Mock - private MessageSource messageSource; - - @InjectMocks - private AuthService authService; - - @BeforeEach - void setUp() { - MockitoAnnotations.openMocks(this); - } - - @Test - void login_success() { - LoginRequest request = new LoginRequest("test@mail.com", "password"); - - User user = new User(); - user.setId(1L); - user.setEmail("test@mail.com"); - user.setPassword("encoded_pass"); - - when(userRepository.findByEmail("test@mail.com")) - .thenReturn(Optional.of(user)); - when(passwordEncoder.matches("password", "encoded_pass")) - .thenReturn(true); - when(authTokenRepository.save(any(AuthToken.class))) - .thenAnswer(invocation -> invocation.getArgument(0)); - - AuthToken token = authService.login(request); - - assertNotNull(token); - assertNotNull(token.getToken()); -// assertEquals(user.getTokens().get(0), token.getUser()); - - ArgumentCaptor captor = ArgumentCaptor.forClass(AuthToken.class); - verify(authTokenRepository).save(captor.capture()); - -// assertEquals(user, captor.getValue().getUser()); - assertNotNull(captor.getValue().getToken()); - } - - - @Test - void login_userNotFound() { - LoginRequest request = new LoginRequest("missing@mail.com", "pwd"); - - when(userRepository.findByEmail("missing@mail.com")).thenReturn(Optional.empty()); - - assertThrows(UserNotFoundException.class, () -> authService.login(request)); - } - - - @Test - void login_invalidPassword() { - LoginRequest request = new LoginRequest("test@mail.com", "wrong"); - - User user = new User(); - user.setEmail("test@mail.com"); - user.setPassword("encoded_pass"); - - when(userRepository.findByEmail("test@mail.com")).thenReturn(Optional.of(user)); - when(passwordEncoder.matches("wrong", "encoded_pass")).thenReturn(false); - - assertThrows(NotValidationPasswordException.class, () -> authService.login(request)); - } -} -- 2.54.0 From 2b7a8b97e1a0d5834586a1eb2e77f21c676a3848 Mon Sep 17 00:00:00 2001 From: vladp Date: Sat, 10 Jan 2026 02:09:39 +0700 Subject: [PATCH 4/4] NCBACK-27 fix token life --- .../nocopy/repository/AuthTokenRepository.java | 3 ++- .../ru/soune/nocopy/service/auth/AuthService.java | 1 + .../service/auth/CleanupTokenSessionsService.java | 13 ++++++------- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java index 9328753..cbd7529 100644 --- a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java @@ -5,11 +5,12 @@ import org.springframework.stereotype.Repository; import ru.soune.nocopy.entity.AuthToken; import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; @Repository public interface AuthTokenRepository extends JpaRepository { - Optional findByLastUsedAtAndExpiresAtBefore(LocalDateTime lastUsedAt, LocalDateTime expiresAtBefore); + List findByExpiresAtBefore(LocalDateTime expiresAtBefore); Optional findByLastUsedAtBefore(LocalDateTime lastUsedAt); Optional findByToken(String token); } diff --git a/src/main/java/ru/soune/nocopy/service/auth/AuthService.java b/src/main/java/ru/soune/nocopy/service/auth/AuthService.java index cf8b18c..f14d6d1 100644 --- a/src/main/java/ru/soune/nocopy/service/auth/AuthService.java +++ b/src/main/java/ru/soune/nocopy/service/auth/AuthService.java @@ -92,6 +92,7 @@ public class AuthService { AuthToken authToken = authTokenRepository.findByToken(token) .orElseThrow(() -> new NotFoundAuthToken("Token not found")); authToken.setLastUsedAt(LocalDateTime.now()); + authToken.setExpiresAt(LocalDateTime.now().plusHours(1)); authTokenRepository.save(authToken); diff --git a/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java b/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java index c18cda3..5ac5128 100644 --- a/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java +++ b/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java @@ -25,14 +25,13 @@ public class CleanupTokenSessionsService { @Transactional @Scheduled(fixedDelay = 30000) - public void cleanupExpiredFiles() { - LocalDateTime hourBefore = LocalDateTime.now().minusHours(authTokenLife); + public void cleanupExpiredTokens() { + LocalDateTime now = LocalDateTime.now(); - List tokensForDelete = authTokenRepository.findByLastUsedAtBefore(hourBefore).stream().toList(); - List tokensDeleteWithNullUpdate = authTokenRepository.findByLastUsedAtAndExpiresAtBefore( - null, hourBefore).stream().toList(); + List expiredTokens = authTokenRepository.findByExpiresAtBefore(now); - authTokenRepository.deleteAll(tokensForDelete); - authTokenRepository.deleteAll(tokensDeleteWithNullUpdate); + if (!expiredTokens.isEmpty()) { + authTokenRepository.deleteAll(expiredTokens); + } } } -- 2.54.0