diff --git a/README.md b/README.md index 72cd929..aa5e26c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,18 @@ +Создаем сеть: +docker network create app-network + +Войти в БД: +docker exec -it postgres psql -U ncp_db -d no_copy_ + +Выполнить команду: +\du + +Должен быть один пользователь: + List of roles +Role name | Attributes +-----------+------------------------------------------------------------ +ncp_db | Superuser, Create role, Create DB, Replication, Bypass RLS + суперпользователь БД: postgres/postgres пользователь бд для бэка @@ -46,3 +61,18 @@ GRANT ALL ON TABLES TO ncp_db; ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ncp_db; + + +Зайти в контейнер: +docker exec -it {name} bash + +------ + +Создать для сущности FileEntity sequence для икремента индетификатора + +CREATE SEQUENCE IF NOT EXISTS file_support_id_seq START 1; + +ALTER TABLE file_entities +ALTER COLUMN support_id +SET DEFAULT nextval('file_support_id_seq'); + diff --git a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java index 6c435d6..69bbe52 100644 --- a/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java +++ b/src/main/java/ru/soune/nocopy/configuration/HandlerConfig.java @@ -18,7 +18,8 @@ public class HandlerConfig { FileEntityHandler file, LogoutRequestHandler logoutHandler, ImageFoundRequestHandler imageFoundRequestHandler, - VerifyRegisterUserHandler verifyRegisterUser + VerifyRegisterUserHandler verifyRegisterUser, + AuthRequestHandler authRequestHandler ) { Map map = new HashMap<>(); map.put(20001, login); @@ -27,6 +28,7 @@ public class HandlerConfig { map.put(20005, file); map.put(20006, logoutHandler); map.put(20007, imageFoundRequestHandler); + map.put(20008, authRequestHandler); map.put(20009, verifyRegisterUser); 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 92557a1..ca16d18 100644 --- a/src/main/java/ru/soune/nocopy/controller/ApiController.java +++ b/src/main/java/ru/soune/nocopy/controller/ApiController.java @@ -24,7 +24,7 @@ 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 +48,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 +71,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 +260,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)) { @@ -368,10 +371,15 @@ public class ApiController { return contentType; } - 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(); + private boolean isPreviewSupported(String mimeType) { + if (mimeType == null) { + return false; + } + + return mimeType.startsWith("image") || + mimeType.startsWith("text") || + mimeType.equals("pdf") || + mimeType.startsWith("video") || + mimeType.startsWith("audio"); } } \ 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 e927005..78612e6 100644 --- a/src/main/java/ru/soune/nocopy/dto/MessageCode.java +++ b/src/main/java/ru/soune/nocopy/dto/MessageCode.java @@ -21,6 +21,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/dto/file/FileApiResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java deleted file mode 100644 index 9feb546..0000000 --- a/src/main/java/ru/soune/nocopy/dto/file/FileApiResponse.java +++ /dev/null @@ -1,41 +0,0 @@ -package ru.soune.nocopy.dto.file; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class FileApiResponse { - - private boolean success; - private String message; - private T data; - private String error; - - public static FileApiResponse success(T data) { - return FileApiResponse.builder() - .success(true) - .message("Operation completed successfully") - .data(data) - .build(); - } - - public static FileApiResponse success(String message, T data) { - return FileApiResponse.builder() - .success(true) - .message(message) - .data(data) - .build(); - } - - public static FileApiResponse error(String error) { - return FileApiResponse.builder() - .success(false) - .error(error) - .build(); - } -} diff --git a/src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java b/src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java index 3e89725..cbfae88 100644 --- a/src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java +++ b/src/main/java/ru/soune/nocopy/dto/file/FileEntityResponse.java @@ -30,4 +30,5 @@ public class FileEntityResponse { private String formattedSize; private String downloadUrl; private boolean existsOnDisk; + private Integer supportId; } diff --git a/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java index ffdd985..c39862a 100644 --- a/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java @@ -2,10 +2,13 @@ package ru.soune.nocopy.entity.file; import jakarta.persistence.*; import lombok.*; +import org.hibernate.annotations.GenerationTime; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import java.sql.DriverManager; +import java.sql.SQLException; import java.time.LocalDateTime; @Data @@ -16,11 +19,13 @@ import java.time.LocalDateTime; @Table(name = "file_entities") @EntityListeners(AuditingEntityListener.class) public class FileEntity { - @Id @GeneratedValue(strategy = GenerationType.UUID) private String id; + @Column(name = "support_id", unique = true, updatable = false) + private Integer supportId; + @Column(name = "user_id", nullable = false) private Long userId; @@ -70,9 +75,4 @@ public class FileEntity { } this.updatedAt = LocalDateTime.now(); } - - @PreUpdate - public void preUpdate() { - this.updatedAt = LocalDateTime.now(); - } } diff --git a/src/main/java/ru/soune/nocopy/entity/user/AuthToken.java b/src/main/java/ru/soune/nocopy/entity/user/AuthToken.java index aa10036..803a904 100644 --- a/src/main/java/ru/soune/nocopy/entity/user/AuthToken.java +++ b/src/main/java/ru/soune/nocopy/entity/user/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 61a1850..807f21e 100644 --- a/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/FileEntityHandler.java @@ -11,8 +11,8 @@ 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.register.AuthService; import ru.soune.nocopy.service.file.FileEntityService; import ru.soune.nocopy.service.file.FileStatsService; @@ -26,9 +26,9 @@ public class FileEntityHandler implements RequestHandler { private final FileEntityService fileEntityService; - private final FileStatsService fileStatsService; + private final AuthService authService; - private final AuthTokenRepository authTokenRepository; + private final FileStatsService fileStatsService; private final ObjectMapper objectMapper; @@ -81,7 +81,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(), @@ -92,7 +92,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)) { @@ -123,7 +123,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()); @@ -155,7 +155,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; @@ -182,7 +182,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; @@ -229,7 +229,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() @@ -259,7 +259,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; @@ -306,20 +306,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 a2036a1..193f01b 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 2df819b..27bfd0f 100644 --- a/src/main/java/ru/soune/nocopy/handler/LoginRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/LoginRequestHandler.java @@ -21,7 +21,9 @@ import java.util.Optional; public class LoginRequestHandler implements RequestHandler { private final UserRepository userRepository; + private final AuthService authService; + private final ObjectMapper objectMapper; @Override @@ -50,9 +52,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/RegRequestHandler.java b/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java index 7eb6fe0..b220ca5 100644 --- a/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java +++ b/src/main/java/ru/soune/nocopy/handler/RegRequestHandler.java @@ -62,9 +62,11 @@ public class RegRequestHandler implements RequestHandler { EmailVerificationToken emailToken = emailService.createEmailVerificationToken(authToken); emailService.sendVerificationEmail(authToken.getUser(), emailToken.getToken()); + String token = authToken.getToken(); + authService.useUserAuthToken(token); RegAnswer regAnswer = new RegAnswer(); - regAnswer.setToken(authToken.getToken()); + regAnswer.setToken(token); regAnswer.setVerified(false); regAnswer.setActive(false); diff --git a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java index 66995d1..9dbc08e 100644 --- a/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/AuthTokenRepository.java @@ -4,12 +4,14 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import ru.soune.nocopy.entity.user.AuthToken; -import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; @Repository public interface AuthTokenRepository extends JpaRepository { - Optional findByTokenAndExpiresAtAfter(String token, LocalDate expiresAtAfter); + List findByExpiresAtBefore(LocalDateTime expiresAtBefore); + Optional findByLastUsedAtBefore(LocalDateTime lastUsedAt); Optional findByToken(String token); Optional findByUserIdAndIsActive(Long userId, boolean isActive); } diff --git a/src/main/java/ru/soune/nocopy/service/auth/AuthService.java b/src/main/java/ru/soune/nocopy/service/auth/AuthService.java new file mode 100644 index 0000000..e8d3259 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/auth/AuthService.java @@ -0,0 +1,104 @@ +package ru.soune.nocopy.service.register; + +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.dto.register.LoginAnswer; +import ru.soune.nocopy.dto.register.LoginRequest; +import ru.soune.nocopy.dto.register.RegRequest; +import ru.soune.nocopy.entity.user.AuthToken; +import ru.soune.nocopy.entity.user.User; +import ru.soune.nocopy.exception.NotValidFieldException; +import ru.soune.nocopy.repository.AuthTokenRepository; +import ru.soune.nocopy.repository.UserRepository; + +import java.security.SecureRandom; +import java.time.LocalDateTime; +import java.util.*; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final UserRepository userRepository; + + private final AuthTokenRepository authTokenRepository; + + private final PasswordEncoder passwordEncoder; + + private final MessageSource messageSource; + + private final SecureRandom secureRandom = new SecureRandom(); + + @Transactional + public AuthToken register(RegRequest registerRequest) { + User user = new User(); + user.setFullName(registerRequest.getFullName()); + user.setEmail(registerRequest.getEmail()); + user.setPassword(passwordEncoder.encode(registerRequest.getPassword())); + + if (registerRequest.getCompanyName() != null) { + user.setCompany(registerRequest.getCompanyName()); + } + + if (registerRequest.getPhone() != null) { + user.setPhone(registerRequest.getPhone()); + } + + User savedUser = userRepository.save(user); + + AuthToken authToken = genereateAuthToken(savedUser); + + return authTokenRepository.save(authToken); + } + + @Transactional + public AuthToken login(LoginRequest request) { + User user = userRepository.findByEmail(request.getEmail()); + + if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { + LoginAnswer loginAnswer = new LoginAnswer(); + loginAnswer.setFieldErrors(Arrays.asList(Map.of("password", request.getPassword()))); + + throw new NotValidFieldException("Invalid password", new BaseResponse(20003, + MessageCode.AUTH_PASSWORD_NOT_MATCHES.getCode(), + MessageCode.AUTH_PASSWORD_NOT_MATCHES.getDescription(), loginAnswer)); + } + + user.setLastLoginAt(LocalDateTime.now()); + User savedUser = userRepository.save(user); + + AuthToken authToken = new AuthToken(); + authToken.setToken(generateAuthToken()); + authToken.setUser(savedUser); + + return authTokenRepository.save(authToken); + } + + @Transactional + public void logout(String token) { + authTokenRepository.findByToken(token) + .ifPresent(authTokenRepository::delete); + } + + public AuthToken generateAuthToken(User user) { + return authTokenRepository.save(genereateAuthToken(user)); + } + + private AuthToken genereateAuthToken(User user) { + AuthToken authToken = new AuthToken(); + authToken.setToken(generateAuthToken()); + authToken.setUser(user); + + return authToken; + } + + private String generateAuthToken() { + byte[] bytes = new byte[32]; + secureRandom.nextBytes(bytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } +} 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..5ac5128 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/auth/CleanupTokenSessionsService.java @@ -0,0 +1,37 @@ +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 cleanupExpiredTokens() { + LocalDateTime now = LocalDateTime.now(); + + List expiredTokens = authTokenRepository.findByExpiresAtBefore(now); + + if (!expiredTokens.isEmpty()) { + authTokenRepository.deleteAll(expiredTokens); + } + } +} diff --git a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java index 617eeda..3eac369 100644 --- a/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java +++ b/src/main/java/ru/soune/nocopy/service/file/FileEntityService.java @@ -17,7 +17,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.List; import java.util.stream.Collectors; @@ -223,6 +222,7 @@ public class FileEntityService { .formattedSize(formatFileSize(fileEntity.getFileSize())) .downloadUrl("/api/v" + version + "/files/download/" + fileEntity.getId()) .existsOnDisk(existsOnDisk) + .supportId(fileEntity.getSupportId()) .build(); } diff --git a/src/main/java/ru/soune/nocopy/service/register/AuthService.java b/src/main/java/ru/soune/nocopy/service/register/AuthService.java index e8d3259..e69de29 100644 --- a/src/main/java/ru/soune/nocopy/service/register/AuthService.java +++ b/src/main/java/ru/soune/nocopy/service/register/AuthService.java @@ -1,104 +0,0 @@ -package ru.soune.nocopy.service.register; - -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.dto.register.LoginAnswer; -import ru.soune.nocopy.dto.register.LoginRequest; -import ru.soune.nocopy.dto.register.RegRequest; -import ru.soune.nocopy.entity.user.AuthToken; -import ru.soune.nocopy.entity.user.User; -import ru.soune.nocopy.exception.NotValidFieldException; -import ru.soune.nocopy.repository.AuthTokenRepository; -import ru.soune.nocopy.repository.UserRepository; - -import java.security.SecureRandom; -import java.time.LocalDateTime; -import java.util.*; - -@Service -@RequiredArgsConstructor -public class AuthService { - - private final UserRepository userRepository; - - private final AuthTokenRepository authTokenRepository; - - private final PasswordEncoder passwordEncoder; - - private final MessageSource messageSource; - - private final SecureRandom secureRandom = new SecureRandom(); - - @Transactional - public AuthToken register(RegRequest registerRequest) { - User user = new User(); - user.setFullName(registerRequest.getFullName()); - user.setEmail(registerRequest.getEmail()); - user.setPassword(passwordEncoder.encode(registerRequest.getPassword())); - - if (registerRequest.getCompanyName() != null) { - user.setCompany(registerRequest.getCompanyName()); - } - - if (registerRequest.getPhone() != null) { - user.setPhone(registerRequest.getPhone()); - } - - User savedUser = userRepository.save(user); - - AuthToken authToken = genereateAuthToken(savedUser); - - return authTokenRepository.save(authToken); - } - - @Transactional - public AuthToken login(LoginRequest request) { - User user = userRepository.findByEmail(request.getEmail()); - - if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { - LoginAnswer loginAnswer = new LoginAnswer(); - loginAnswer.setFieldErrors(Arrays.asList(Map.of("password", request.getPassword()))); - - throw new NotValidFieldException("Invalid password", new BaseResponse(20003, - MessageCode.AUTH_PASSWORD_NOT_MATCHES.getCode(), - MessageCode.AUTH_PASSWORD_NOT_MATCHES.getDescription(), loginAnswer)); - } - - user.setLastLoginAt(LocalDateTime.now()); - User savedUser = userRepository.save(user); - - AuthToken authToken = new AuthToken(); - authToken.setToken(generateAuthToken()); - authToken.setUser(savedUser); - - return authTokenRepository.save(authToken); - } - - @Transactional - public void logout(String token) { - authTokenRepository.findByToken(token) - .ifPresent(authTokenRepository::delete); - } - - public AuthToken generateAuthToken(User user) { - return authTokenRepository.save(genereateAuthToken(user)); - } - - private AuthToken genereateAuthToken(User user) { - AuthToken authToken = new AuthToken(); - authToken.setToken(generateAuthToken()); - authToken.setUser(user); - - return authToken; - } - - private String generateAuthToken() { - byte[] bytes = new byte[32]; - secureRandom.nextBytes(bytes); - return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); - } -} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index b509055..171cca5 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -46,13 +46,12 @@ spring: file: storage: base-path: ${FILE_STORAGE_PATH:/data/uploads} - # chunk-size: ${FILE_CHUNK_SIZE:5242880} # 5MB -# chunk-size: ${FILE_CHUNK_SIZE:1048576} # 1MB chunk-size: ${FILE_CHUNK_SIZE:1000000} # 1MB max-file-size: ${MAX_FILE_SIZE:10737418240} # 10GB 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: