Skip to content

[View] 공통 요소 정적 뷰에 적용, 에러 팝업 개발#57

Merged
codingbaraGo merged 7 commits intomainfrom
enhance/view/#56
Jan 13, 2026
Merged

[View] 공통 요소 정적 뷰에 적용, 에러 팝업 개발#57
codingbaraGo merged 7 commits intomainfrom
enhance/view/#56

Conversation

@codingbaraGo
Copy link
Owner

💻 작업 내용

  • 정적 뷰에서 공통 요소 분리
  • 정적 뷰를 /static -> /templates 하위로 이동
  • json 타입 에러를 받아 팝업을 생성하는 공통 요소 추가

✨ 리뷰 포인트

뷰의 공통 요소에 취약점이 있는지 체크 부탁합니다.

🎯 관련 이슈

closed #56

… (see PR #54)

develop <- feat/authentication/logout/#51
- 공통 요소 반영을 위해 DynamicViewResponse를 반환하는 기본 뷰 핸들러 추가
- 해더에 "글쓰기" 버튼 추가
- 헤더의 닉네임에 /mypage 리다이렉트 기능 추가
- `/static` 하위에 있던 정적 뷰들을 `/templates`하위로 이동
- 이동한 정적 뷰들에 공통 컴포넌트 반영
@codingbaraGo codingbaraGo self-assigned this Jan 13, 2026
@codingbaraGo codingbaraGo added the enhancement Enhance existing feature label Jan 13, 2026
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

공통 요소에서 발견된 보안 및 안정성 문제들을 정리했습니다. 주요 관심사는 경로 검증(Path Traversal), CSRF 보호, AJAX 폼 처리 로직의 유연성입니다. 인라인 댓글을 참고하세요.

Comment on lines 71 to 108
document.addEventListener("submit", async (e) => {
const form = e.target;
if (!(form instanceof HTMLFormElement)) return;

// 안전하게: data-ajax="true"인 폼만 가로챔
if (form.dataset.ajax !== "true") return;

e.preventDefault();

// multipart/form-data는 여기서 처리 안 함(파일 업로드 등)
const enctype = (form.enctype || "").toLowerCase();
if (enctype.includes("multipart/form-data")) {
window.showErrorPopup("파일 업로드 폼은 지원하지 않습니다.", "오류");
return;
}

const body = new URLSearchParams(new FormData(form));

try {
const res = await fetch(form.action, {
method: (form.method || "POST").toUpperCase(),
headers: {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
body,
credentials: "same-origin",
});

if (res.ok) {
// 성공 처리: 서버가 리다이렉트를 주는 구조면 여기서 location 설정
// 우선 가장 단순하게는 reload 혹은 홈 이동
window.location.href = "/";
return;
}

let err = null;
try { err = await res.json(); } catch {}

Choose a reason for hiding this comment

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

XSS 취약점: msgEl.textContent = message를 사용하고 있는 것은 좋지만, 문제는 message가 서버 응답에서 온 데이터인 경우 유효성 검증이 없다는 점입니다. 또한 form.actionfetch()의 대상으로 사용하는데, form.action이 신뢰할 수 없는 출처인 경우 CSRF 위험이 있습니다.

제안:

  1. 폼의 action 속성이 같은 출처(same-origin)인지 명시적으로 검증
  2. credentials: "same-origin"만으로는 부족하므로, URL의 호스트/프로토콜 일치 확인 추가

예시:

const formUrl = new URL(form.action, window.location.origin);
if (formUrl.origin !== window.location.origin) {
  window.showErrorPopup("유효하지 않은 폼 대상입니다.", "오류");
  return;
}

Comment on lines 98 to 105
});

if (res.ok) {
// 성공 처리: 서버가 리다이렉트를 주는 구조면 여기서 location 설정
// 우선 가장 단순하게는 reload 혹은 홈 이동
window.location.href = "/";
return;
}

Choose a reason for hiding this comment

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

폼 제출 후 처리 로직 불명확: res.ok인 경우 무조건 window.location.href = "/"로 이동시키는데, 이는 모든 AJAX 폼 제출에 동일하게 적용됩니다. 로그인, 회원가입, 댓글 등 다양한 폼이 있을 텐데, 성공 후 이동 경로가 고정되어 있어 유연성이 없습니다.

제안: 폼에 data-redirect-url 같은 속성을 추가하여 성공 후 이동 경로를 명시적으로 지정하거나, 서버에서 리다이렉트 정보를 응답에 포함시키세요.

Comment on lines +28 to +35
public boolean checkEndpoint(HttpMethod method, String path) {
if(!method.equals(this.method)) return false;
return roots.stream().anyMatch(root ->{
File requestedFile = new File(root + path);
String indexFilePath = path + (path.endsWith("/") ? "index.html" : "/index.html");
File indexFile = new File(root + indexFilePath);
return (requestedFile.exists() && requestedFile.isFile()) || (indexFile.exists() && indexFile.isFile());
});

Choose a reason for hiding this comment

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

디렉토리 트래버설(Path Traversal) 위험: checkEndpoint() 메서드에서 경로 검증이 불충분합니다. 사용자가 ../../../etc/passwd 같은 경로를 요청할 경우, new File(root + path).exists() 검사만으로는 보호되지 않습니다. (Java의 File API는 심볼릭 링크나 경로 정규화 문제가 있을 수 있음)

제안:

try {
  File requestedFile = new File(root + path).getCanonicalFile();
  File rootFile = new File(root).getCanonicalFile();
  if (!requestedFile.getAbsolutePath().startsWith(rootFile.getAbsolutePath())) {
    return false; // 루트 범위 밖
  }
} catch (IOException e) {
  return false; // 경로 정규화 실패
}

Comment on lines +39 to +42
public HandlerResponse handle(HttpRequest request) {
String path = request.getPath() + (request.getPath().endsWith("/") ? "index.html" : "/index.html");
return DynamicViewResponse.of(HttpStatus.OK, path);
}

Choose a reason for hiding this comment

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

인덱스 파일 로직 중복: handle() 메서드에서 경로에 /index.html을 추가하는데, checkEndpoint()에서 이미 인덱스 파일 존재 여부를 확인했습니다. 이 두 메서드 간 로직이 일치하지 않으면 버그가 발생할 수 있습니다. 예를 들어, checkEndpoint()에서 /index.html을 찾았다면, handle()에서 다시 /index.html을 추가할 때 중복되지 않도록 주의해야 합니다.

제안: 경로 정규화 로직을 한 곳(예: static 메서드)에 집중시키세요.

Comment on lines +77 to +84

e.preventDefault();

// multipart/form-data는 여기서 처리 안 함(파일 업로드 등)
const enctype = (form.enctype || "").toLowerCase();
if (enctype.includes("multipart/form-data")) {
window.showErrorPopup("파일 업로드 폼은 지원하지 않습니다.", "오류");
return;

Choose a reason for hiding this comment

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

폼 필터링 문제: if (form.dataset.ajax !== "true") return로 AJAX 처리 여부를 결정하는데, 이렇게 하면 data-ajax 속성이 없는 폼은 기본 form submit 동작을 합니다. 만약 템플릿 변경 시 실수로 data-ajax="true"를 빠뜨리면, AJAX 없이 전체 페이지 새로고침이 되어 사용 경험이 다음 수 있습니다.

제안: AJAX 처리 대상 폼을 명시적으로 관리하거나, 특정 클래스(예: ajax-form)로 식별하는 방식으로 변경하여 기본 동작과 명확히 구분하세요.

Comment on lines 6 to 11
<li class="header__menu__item">
<span class="header__menu__nickname">닉네임: {{userNickname}}</span>
<a class="header__menu__nickname" href="/mypage">닉네임: {{userNickname}}</a>
</li>
<li class="header__menu__item">
<a class="btn btn_contained btn_size_s" href="/article">글쓰기</a>
</li>

Choose a reason for hiding this comment

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

스타일 일관성 문제: 닉네임을 <span>에서 <a> 태그로 변경했는데, CSS 스타일이 수정되지 않으면 링크 스타일(파란색, 밑줄 등)이 헤더 디자인을 깨뜨릴 수 있습니다. 기존 .header__menu__nickname 스타일이 <span> 기준이었다면, <a> 태그에 적용할 때 text-decoration: none, color 등을 명시적으로 재정의해야 합니다.

제안: CSS 파일에서 .header__menu__nickname { text-decoration: none; color: inherit; } 등을 추가하거나, 링크 스타일을 정의하세요.

Comment on lines +79 to +84

// multipart/form-data는 여기서 처리 안 함(파일 업로드 등)
const enctype = (form.enctype || "").toLowerCase();
if (enctype.includes("multipart/form-data")) {
window.showErrorPopup("파일 업로드 폼은 지원하지 않습니다.", "오류");
return;

Choose a reason for hiding this comment

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

멀티파트 폼 제한 사항: enctype.includes("multipart/form-data")인 폼을 지원하지 않는다고 명시되어 있으나, 현재 코드는 에러 메시지만 표시합니다. 실제로 파일 업로드가 필요한 경우 (예: 프로필 이미지 변경), 사용자가 AJAX 제출을 시도했을 때 에러가 발생합니다.

제안:

  1. 멀티파트 폼은 전통적인 form submit을 허용하거나
  2. FormData 기반 AJAX로 파일 업로드를 지원하도록 개선하세요

이 부분은 mypage의 프로필 이미지 업로드 기능과 연관될 수 있으므로 주의가 필요합니다.

- 서버의 반환값을 받아 redirect면 Location으로 리다이렉트 되도록 분기 코드 추가
@codingbaraGo codingbaraGo merged commit 43760d0 into main Jan 13, 2026
1 check passed
codingbaraGo added a commit that referenced this pull request Jan 13, 2026
@codingbaraGo codingbaraGo deleted the enhance/view/#56 branch January 13, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Enhance existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[View] - 뷰 기능 추가: 에러 팝업 출력, 공통 템플릿 전체 적용

1 participant

Comments