From 3f225d217fa5c5423db4867020e09f48e70fab9c Mon Sep 17 00:00:00 2001 From: Chanhae Lee Date: Mon, 1 Jun 2026 01:42:55 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(social):=20=EB=8C=93=EA=B8=80=C2=B7?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=EC=97=90=20=EC=98=A4=EB=8A=98=20=EA=B3=B5=EC=9C=A0=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 access check가 isShared=true만 검사해 친구가 어제 공유 후 끄지 않은 리포트는 dailyReportId 추측만으로 댓글·좋아요 진입이 가능하던 문제 해결, existsByIdAndIsSharedTrue → existsByIdAndIsSharedTrueAndDate 변경하고 메서드 쓰인 곳 모두 변경 --- .../domain/comment/application/CommentCommandService.java | 3 ++- .../domain/comment/application/CommentQueryService.java | 3 ++- .../dailyreport/core/repository/DailyReportRepository.java | 2 +- .../nadab/domain/like/application/LikeCommandService.java | 5 +++-- .../nadab/domain/like/application/LikeQueryService.java | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentCommandService.java b/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentCommandService.java index 07be458f..1d1d7ff2 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentCommandService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentCommandService.java @@ -15,6 +15,7 @@ import com.devkor.ifive.nadab.global.exception.ConflictException; import com.devkor.ifive.nadab.global.exception.ForbiddenException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -103,7 +104,7 @@ private void checkNotSuspended(Long userId) { private void checkCommentWriteAccess(Long dailyReportId, Long reportOwnerId, Long currentUserId) { if (currentUserId.equals(reportOwnerId)) return; - if (!dailyReportRepository.existsByIdAndIsSharedTrue(dailyReportId)) { + if (!dailyReportRepository.existsByIdAndIsSharedTrueAndDate(dailyReportId, TodayDateTimeProvider.getTodayDate())) { throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); } long smallerId = Math.min(currentUserId, reportOwnerId); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentQueryService.java b/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentQueryService.java index 03814595..5ef5500e 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentQueryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/comment/application/CommentQueryService.java @@ -16,6 +16,7 @@ import com.devkor.ifive.nadab.global.exception.ConflictException; import com.devkor.ifive.nadab.global.exception.ForbiddenException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @@ -187,7 +188,7 @@ private Map buildSubCountMap(List comments, List excl private void checkCommentViewAccess(Long dailyReportId, Long reportOwnerId, Long currentUserId) { if (currentUserId.equals(reportOwnerId)) return; - if (!dailyReportRepository.existsByIdAndIsSharedTrue(dailyReportId)) { + if (!dailyReportRepository.existsByIdAndIsSharedTrueAndDate(dailyReportId, TodayDateTimeProvider.getTodayDate())) { throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); } long smallerId = Math.min(currentUserId, reportOwnerId); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/DailyReportRepository.java b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/DailyReportRepository.java index f4bfab72..710907b9 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/DailyReportRepository.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/dailyreport/core/repository/DailyReportRepository.java @@ -164,7 +164,7 @@ Optional findByUserIdAndDate( @Query("select ae.user.id from DailyReport dr join dr.answerEntry ae where dr.id = :reportId") Optional findReportOwnerIdById(@Param("reportId") Long reportId); - boolean existsByIdAndIsSharedTrue(Long id); + boolean existsByIdAndIsSharedTrueAndDate(Long id, LocalDate date); @Query(""" select new com.devkor.ifive.nadab.domain.dailyreport.core.dto.FeedDto( diff --git a/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeCommandService.java b/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeCommandService.java index 4de28493..34f2ad06 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeCommandService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeCommandService.java @@ -17,6 +17,7 @@ import com.devkor.ifive.nadab.global.exception.ConflictException; import com.devkor.ifive.nadab.global.exception.ForbiddenException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -112,7 +113,7 @@ private void checkNotSuspended(Long userId) { } private void checkReportLikeAccess(Long dailyReportId, Long reportOwnerId, Long currentUserId) { - if (!dailyReportRepository.existsByIdAndIsSharedTrue(dailyReportId)) { + if (!dailyReportRepository.existsByIdAndIsSharedTrueAndDate(dailyReportId, TodayDateTimeProvider.getTodayDate())) { throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); } long smallerId = Math.min(currentUserId, reportOwnerId); @@ -124,7 +125,7 @@ private void checkReportLikeAccess(Long dailyReportId, Long reportOwnerId, Long private void checkCommentLikeAccess(Long dailyReportId, Long reportOwnerId, Long currentUserId) { if (currentUserId.equals(reportOwnerId)) return; - if (!dailyReportRepository.existsByIdAndIsSharedTrue(dailyReportId)) { + if (!dailyReportRepository.existsByIdAndIsSharedTrueAndDate(dailyReportId, TodayDateTimeProvider.getTodayDate())) { throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); } long smallerId = Math.min(currentUserId, reportOwnerId); diff --git a/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeQueryService.java b/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeQueryService.java index 28005d1e..f51a3fe6 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeQueryService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/like/application/LikeQueryService.java @@ -19,6 +19,7 @@ import com.devkor.ifive.nadab.global.exception.ConflictException; import com.devkor.ifive.nadab.global.exception.ForbiddenException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -100,7 +101,7 @@ public LikeListResponse getCommentLikers(Long commentId, Long currentUserId) { private void checkCommentViewAccess(Long dailyReportId, Long reportOwnerId, Long currentUserId) { if (currentUserId.equals(reportOwnerId)) return; - if (!dailyReportRepository.existsByIdAndIsSharedTrue(dailyReportId)) { + if (!dailyReportRepository.existsByIdAndIsSharedTrueAndDate(dailyReportId, TodayDateTimeProvider.getTodayDate())) { throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); } long smallerId = Math.min(currentUserId, reportOwnerId); From 32b0c7b6a5631818b15e2604391266a084e45e2e Mon Sep 17 00:00:00 2001 From: Chanhae Lee Date: Mon, 1 Jun 2026 01:44:56 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(moderation):=20=EC=8B=A0=EA=B3=A0=20API?= =?UTF-8?q?=EC=97=90=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C=EC=96=B4=20=EC=8B=A0?= =?UTF-8?q?=EA=B7=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ContentReportCommandService.checkReportAccess 메서드로 본인 글 early return, 그 외 오늘 공유된 친구 리포트만 신고 가능 권한 체크, 비밀 댓글 신고 시 추가 권한 체크, Swagger 403 응답 명세 추가 --- .../moderation/api/ModerationController.java | 7 ++++ .../ContentReportCommandService.java | 39 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/main/java/com/devkor/ifive/nadab/domain/moderation/api/ModerationController.java b/src/main/java/com/devkor/ifive/nadab/domain/moderation/api/ModerationController.java index 0a166755..f64a5d0b 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/moderation/api/ModerationController.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/moderation/api/ModerationController.java @@ -69,6 +69,13 @@ public class ModerationController { content = @Content ), @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content), + @ApiResponse( + responseCode = "403", + description = """ + - ErrorCode: AUTH_ACCESS_DENIED - 신고 권한 없음 (친구가 아니거나, 오늘 공유된 게시글이 아님, 비밀 댓글 열람 권한 없음) + """, + content = @Content + ), @ApiResponse( responseCode = "404", description = """ diff --git a/src/main/java/com/devkor/ifive/nadab/domain/moderation/application/ContentReportCommandService.java b/src/main/java/com/devkor/ifive/nadab/domain/moderation/application/ContentReportCommandService.java index d1e80bc2..545a5ee8 100644 --- a/src/main/java/com/devkor/ifive/nadab/domain/moderation/application/ContentReportCommandService.java +++ b/src/main/java/com/devkor/ifive/nadab/domain/moderation/application/ContentReportCommandService.java @@ -4,6 +4,7 @@ import com.devkor.ifive.nadab.domain.comment.core.repository.CommentRepository; import com.devkor.ifive.nadab.domain.dailyreport.core.entity.DailyReport; import com.devkor.ifive.nadab.domain.dailyreport.core.repository.DailyReportRepository; +import com.devkor.ifive.nadab.domain.friend.core.repository.FriendshipRepository; import com.devkor.ifive.nadab.domain.moderation.core.entity.ContentReport; import com.devkor.ifive.nadab.domain.moderation.core.entity.ReportReason; import com.devkor.ifive.nadab.domain.moderation.core.repository.ContentReportRepository; @@ -12,7 +13,9 @@ import com.devkor.ifive.nadab.global.core.response.ErrorCode; import com.devkor.ifive.nadab.global.exception.BadRequestException; import com.devkor.ifive.nadab.global.exception.ConflictException; +import com.devkor.ifive.nadab.global.exception.ForbiddenException; import com.devkor.ifive.nadab.global.exception.NotFoundException; +import com.devkor.ifive.nadab.global.shared.util.TodayDateTimeProvider; import lombok.RequiredArgsConstructor; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; @@ -27,6 +30,7 @@ public class ContentReportCommandService { private final DailyReportRepository dailyReportRepository; private final CommentRepository commentRepository; private final UserRepository userRepository; + private final FriendshipRepository friendshipRepository; private final SharingSuspensionService sharingSuspensionService; public void reportContent(Long reporterId, Long dailyReportId, Long commentId, @@ -74,6 +78,10 @@ private ContentReport buildDailyReportReport(User reporter, Long reporterId, Lon if (reporterId.equals(reportedUser.getId())) { throw new BadRequestException(ErrorCode.CONTENT_REPORT_SELF_REPORT_FORBIDDEN); } + + // 접근 권한 검증 (오늘 공유된 친구 리포트만 신고 가능, 본인 글은 early return) + checkReportAccess(dailyReportId, reportedUser.getId(), reporterId); + return ContentReport.createForDailyReport(reporter, dailyReport, reportedUser, reason, customReason); } @@ -95,9 +103,40 @@ private ContentReport buildCommentReport(User reporter, Long reporterId, Long co if (reporterId.equals(reportedUser.getId())) { throw new BadRequestException(ErrorCode.CONTENT_REPORT_SELF_REPORT_FORBIDDEN); } + + // 게시글 접근 권한 검증 (오늘 공유된 친구 리포트만 신고 가능, 본인 글은 early return) + Long dailyReportId = comment.getDailyReport().getId(); + Long reportOwnerId = dailyReportRepository.findReportOwnerIdById(dailyReportId) + .orElseThrow(() -> new NotFoundException(ErrorCode.DAILY_REPORT_NOT_FOUND)); + checkReportAccess(dailyReportId, reportOwnerId, reporterId); + + // 비밀 댓글은 열람 권한자만 신고 가능 (작성자는 자기 신고 방지로 이미 차단됨) + if (comment.isSecret()) { + boolean isParentAuthor = !comment.isTopLevel() && + commentRepository.findParentAuthorIdById(commentId) + .map(id -> id.equals(reporterId)) + .orElse(false); + boolean canView = reportOwnerId.equals(reporterId) || isParentAuthor; + if (!canView) { + throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); + } + } + return ContentReport.createForComment(reporter, comment, reportedUser, reason, customReason); } + private void checkReportAccess(Long dailyReportId, Long reportOwnerId, Long reporterId) { + if (reporterId.equals(reportOwnerId)) return; + if (!dailyReportRepository.existsByIdAndIsSharedTrueAndDate(dailyReportId, TodayDateTimeProvider.getTodayDate())) { + throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); + } + long smallerId = Math.min(reporterId, reportOwnerId); + long largerId = Math.max(reporterId, reportOwnerId); + if (!friendshipRepository.existsAcceptedByUserIds(smallerId, largerId)) { + throw new ForbiddenException(ErrorCode.AUTH_ACCESS_DENIED); + } + } + private void validateTarget(Long dailyReportId, Long commentId) { if (dailyReportId == null && commentId == null) { throw new BadRequestException(ErrorCode.CONTENT_REPORT_INVALID);