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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.global.support.CursorResponse;
import life.mosu.mosuserver.infra.persistence.s3.FileUploadHelper;
import life.mosu.mosuserver.infra.persistence.s3.S3Service;
import life.mosu.mosuserver.presentation.event.dto.EventRequest;
import life.mosu.mosuserver.presentation.event.dto.EventResponse;
Expand All @@ -22,12 +23,13 @@ public class EventService {

private final EventJpaRepository eventJpaRepository;
private final EventQueryRepository eventQueryRepository;
// private final EventAttachmentService attachmentService;
private final FileUploadHelper uploadHelper;
private final S3Service s3Service;

@Transactional
public void createEvent(EventRequest request) {
EventJpaEntity eventEntity = eventJpaRepository.save(request.toEntity());
uploadHelper.updateTag(eventEntity.getS3Key());
// attachmentService.createAttachment(request.optionalAttachment(), eventEntity);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicat
examApplicationInfo.schoolName(),
AddressResponse.from(examApplicationInfo.address()),
subjects,
examApplicationInfo.isLunchChecked() ? examApplicationInfo.lunchName() : "신청 안 함",
examApplicationInfo.isLunchChecked() ? examApplicationInfo.lunchName() : "도시락 X",
paymentAmount,
discountAmount,
examApplicationInfo.paymentMethod().getName()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@Getter
@RequiredArgsConstructor
public enum Lunch {
NONE("선택 안 함"),
NONE("도시락 X"),
OPTION1("도시락 A"),
OPTION2("도시락 B"),
OPTION3("비건 도시락"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public enum ErrorCode {
INVALID_SIGN_UP_TOKEN(HttpStatus.UNAUTHORIZED, "유효하지 않은 회원가입 인증 토큰입니다."),
MISSING_SIGNUP_TOKEN(HttpStatus.BAD_REQUEST, "회원가입 인증 토큰이 누락되었습니다."),
MISSING_PASSWORD_TOKEN(HttpStatus.BAD_REQUEST, "비밀번호 변경 토큰이 누락되었습니다."),
COOKIE_NOT_FOUND(HttpStatus.NOT_FOUND, "쿠키가 존재하지 않습니다."),

NOT_FOUND_TOKEN(HttpStatus.NOT_FOUND, "인증 토큰을 찾을 수 없습니다."),
NOT_FOUND_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "액세스 토큰을 찾을 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class AuthConstants {
public static final String AUTH_PREFIX = API_PREFIX + "/auth";
public static final String PATH_REISSUE = AUTH_PREFIX + "/reissue";
public static final String PATH_SIGNUP = AUTH_PREFIX + "/signup";
public static final String COOKIE_ACCESS = AUTH_PREFIX + "/check-cookie";

public static final String USER_PREFIX = API_PREFIX + "/user";
public static final String PATH_PASSWORD_CHANGE = USER_PREFIX + "/me/password";
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/life/mosu/mosuserver/global/filter/TokenFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,26 @@ protected void doFilterInternal(
return;
}

if (requestUri.startsWith(AuthConstants.COOKIE_ACCESS)) {

final TokenCookies tokenCookies = tokenResolver.resolveTokens(request);
String accessToken = tokenCookies.getAccessToken().orElseThrow(
() -> new CustomRuntimeException(ErrorCode.COOKIE_NOT_FOUND)
);
try {
setAuthentication(accessToken);

response.setStatus(HttpServletResponse.SC_NO_CONTENT);
return;
} catch (CustomRuntimeException e) {
log.warn("쿠키 토큰 검증 실패: {}", e.getMessage());
throw e;
} catch (Exception e) {
log.error("쿠키 토큰 검증 중 예외 발생", e);
throw new CustomRuntimeException(ErrorCode.INVALID_TOKEN);
}
}
Comment on lines +81 to +99

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is significant code duplication between this new block for handling AuthConstants.COOKIE_ACCESS and the existing token validation logic that follows (lines 101-114). Both blocks resolve tokens, get the access token, and call setAuthentication within a try-catch block.

This duplication makes the code harder to maintain. For example:

  • tokenResolver.resolveTokens(request) is called twice.
  • The exception handling logic is similar but inconsistent. The new block re-throws CustomRuntimeException while the subsequent block wraps it. The logging levels and messages also differ.

Consider refactoring to a single token validation flow that handles both cases to improve readability and maintainability.


final TokenCookies tokenCookies = tokenResolver.resolveTokens(request);
String accessToken = tokenCookies.getAccessToken().orElseThrow(
() -> new CustomRuntimeException(ErrorCode.NOT_FOUND_ACCESS_TOKEN)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public static ResponseCookie createDevelopResponseCookie(String name, String val
*/
public static Cookie createDevelopCookie(String name, String value, Long maxAge) {
Cookie cookie = createBaseServletCookie(name, value, maxAge);
cookie.setSecure(true);
cookie.setSecure(false);
cookie.setDomain(".mosuedu.com");
return cookie;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@
@RequiredArgsConstructor
public class S3Service {

private static final int MAX_FILENAME_LENGTH = 150;
private static final int MAX_S3_KEY_LENGTH = 255;

private final S3Client s3Client;
private final S3Presigner s3Presigner;
private final S3Properties s3Properties;

public FileUploadResponse uploadFile(MultipartFile file, Folder folder) {
String sanitizedName = sanitizeFileName(file.getOriginalFilename());
String s3Key = folder.getPath() + "/" + UUID.randomUUID() + "_" + sanitizedName;
String randomPrefix = UUID.randomUUID().toString();
String s3Key = folder.getPath() + "/" + randomPrefix + "_" + sanitizedName;

if (s3Key.length() > MAX_S3_KEY_LENGTH) {
int excess = s3Key.length() - MAX_S3_KEY_LENGTH;
sanitizedName = sanitizedName.substring(0, sanitizedName.length() - excess);
s3Key = folder.getPath() + "/" + randomPrefix + "_" + sanitizedName;
}

try {
s3Client.putObject(
Expand Down Expand Up @@ -108,12 +118,23 @@ public String getPreSignedUrl(String s3Key) {
}

private String sanitizeFileName(String originalFilename) {
try {
return URLEncoder.encode(originalFilename, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20");
} catch (Exception e) {
throw new RuntimeException("파일 이름 인코딩 실패", e);

String encoded = URLEncoder.encode(originalFilename, StandardCharsets.UTF_8)
.replaceAll("\\+", "%20");

// 파일명만 잘라내기 (확장자 유지)
String extension = "";
int dotIndex = encoded.lastIndexOf('.');
if (dotIndex != -1) {
extension = encoded.substring(dotIndex);
encoded = encoded.substring(0, dotIndex);
}

if (encoded.length() > MAX_FILENAME_LENGTH) {
encoded = encoded.substring(0, MAX_FILENAME_LENGTH);
}

return encoded;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The sanitizeFileName method incorrectly drops the file extension. The extension is extracted from the encoded filename but is never appended back to the result. This will cause all uploaded files to lose their extensions. The comment on line 125, "파일명만 잘라내기 (확장자 유지)" (Cut out filename only (preserve extension)), indicates the intention was to preserve the extension.

Suggested change
return encoded;
return encoded + extension;

}

private String shortenKey(String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -42,15 +43,22 @@ public ResponseEntity<ApiResponseWrapper<LoginResponse>> login(
));
}

@GetMapping("/check-cookie")
public ResponseEntity<Void> checkToken() {
return ResponseEntity.ok().build();
}

;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a stray semicolon here that is unnecessary and should be removed.


private HttpHeaders applyTokenHeader(Token token) {
HttpHeaders headers = new HttpHeaders();

headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createDevelopCookieString(
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createLocalCookieString(
CookieBuilderUtil.ACCESS_TOKEN_COOKIE_NAME,
token.accessToken(),
token.accessTokenExpireTime()
));
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createDevelopCookieString(
headers.add(HttpHeaders.SET_COOKIE, CookieBuilderUtil.createLocalCookieString(
CookieBuilderUtil.REFRESH_TOKEN_COOKIE_NAME,
token.refreshToken(),
token.refreshTokenExpireTime()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ public interface AuthControllerDocs {
@Operation(description = "로그인 API 지금은 쿠키와 response 둘다 반환하는데 곧 쿠키로만 작동하게 할 것 입니다. <프론트하고 변경하려고 Response 이렇게 만들었는데 나중에 같이 맞춥시다!>", summary = "사용자가 로그인합니다.")
public ResponseEntity<ApiResponseWrapper<LoginResponse>> login(
@RequestBody @Valid final LoginRequest request);

@Operation(description = "쿠키 검증용 API", summary = "쿠키가 유효한지 확인합니다.")
public ResponseEntity<Void> checkToken();
}