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
Binary file modified .DS_Store
Binary file not shown.
Binary file modified src/.DS_Store
Binary file not shown.
Binary file modified src/main/.DS_Store
Binary file not shown.
Binary file added src/main/java/.DS_Store
Binary file not shown.
Binary file added src/main/java/org/.DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers(new DispatcherTypeRequestMatcher(DispatcherType.ASYNC)).permitAll()
.requestMatchers(
SecurityPath.PUBLIC_PATH).permitAll()
//GET요청인 문제 목록 조회, 문제 상세 조회는 가능하게, 나머지 HTTP메서드는 인증 필요하게 설정하기
.requestMatchers(HttpMethod.GET, "/api/problems", "/api/problems/{problemId}").permitAll()
.requestMatchers("/api/problems/**").authenticated()
.requestMatchers("/api/admin/**").hasRole("ADMIN") //어드민 권한 필요 (문제 생성, 관리 등)
.requestMatchers(HttpMethod.GET,
"/api/languages",
"/api/problems",
"/api/problems/{problemId}",
"/api/problems/*/discussions",
"/api/problems/{problemId}/discussions/{discussionId}/replies",
"/api/problems/{problemId}/discussions/{discussionId}/replies/**").permitAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class SecurityPath {
"/actuator/**",
"/chatting",
"/submit-test/**",
"/api/problems/**",
// "/api/problems/**", //문제의 HTTP 요청에 따라 다르게 처리될 수 있도록 SecurityConfig에서 설정함
"/ws/**",
"/swagger-ui/**",
"/swagger-resources/**",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public String getSigninView(){
return "test-login";
}

@GetMapping("/test/reset-password")
public String getResetPasswordView(){
return "test-reset-password";
}

@GetMapping("/test/submit")
public String getSubmitView() {
return "test-submit";
Expand Down
72 changes: 72 additions & 0 deletions src/main/resources/static/css/test-login.css
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,75 @@ body {
height: 100%;
object-fit: contain;
}

.modal {
display: none;
position: fixed;
z-index: 1000;
left: 0; top: 0;
width: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.6);
}

.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #101618;
color: #fff;
width: 360px;
padding: 30px 20px;
border-radius: 12px;
text-align: center;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
font-family: 'Gmarket Sans', sans-serif;
}


.modal-content h3 {
margin-bottom: 10px;
font-weight: 500;
font-size: 20px;
}

.modal-content p {
font-size: 14px;
margin-bottom: 20px;
color: #bbb;
}

.modal-content input {
width: 90%;
padding: 12px;
border-radius: 8px;
border: none;
margin-bottom: 20px;
background: #1e2a2f;
color: #fff;
font-size: 14px;
}

.modal-buttons button {
padding: 10px 20px;
border: none;
margin: 0 8px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease-in-out;
}

.modal-buttons button:first-child {
background-color: #555;
color: #fff;
}

.modal-buttons button:last-child {
background-color: #00c776;
color: #fff;
}

.modal-buttons button:hover {
opacity: 0.9;
}
Binary file added src/main/resources/static/images/lock-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/main/resources/static/js/test-mypage.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ function fetchUserData() {
const user = res.result;
// 2. 받아온 정보로 화면 반영
document.getElementById('nickname').textContent = user.nickname || '';
document.getElementById('profileImage').src = user.profileImageUrl || '/images/logo.png';
// document.getElementById('profileImage').src = user.profileImageUrl || '/images/logo.png';
document.getElementById('userId').textContent = user.username || '';
document.getElementById('email').textContent = user.email || '';
document.getElementById('tier').textContent = user.tier || '';
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/templates/test-mypage.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<section class="profile-section">
<div class="profile-img">
<!-- 프로필 이미지: DB에 없으면 기본 이미지로 대체 -->
<img src="/images/logo.png" alt="프로필 이미지" id="profileImage">
<img src="/images/logo.png" alt="프로필 이미지">
</div>
<div class="info-value" id="nickname">닉네임</div>
</section>
Expand Down
213 changes: 213 additions & 0 deletions src/main/resources/templates/test-reset-password.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>비밀번호 변경</title>
<style>
* {
box-sizing: border-box;
font-family: 'Gmarket Sans', sans-serif;
margin: 0;
padding: 0;
}

body {
background: linear-gradient(to bottom right, #214d35, #0f111a);
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
color: white;
}

.reset-container {
width: 500px;
padding: 40px;
}

.reset-container h1 {
font-size: 32px;
font-weight: bold;
margin-bottom: 10px;
}

.reset-container p {
font-size: 16px;
margin-bottom: 40px;
}

.input-group {
margin-bottom: 20px;
}

.input-label {
display: block;
margin-bottom: 8px;
font-weight: 500;
}

.input-wrapper {
position: relative;
}

.input-wrapper input {
width: 100%;
padding: 12px 40px 12px 40px;
border: none;
border-radius: 8px;
font-size: 14px;
}

.input-wrapper img {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
}

.input-wrapper .icon-left {
left: 10px;
}

.input-wrapper .icon-right {
right: 10px;
cursor: pointer;
}

.submit-button {
width: 100%;
padding: 14px;
font-size: 16px;
font-weight: bold;
background-color: white;
color: #000;
border: none;
border-radius: 8px;
margin-top: 30px;
cursor: pointer;
transition: background 0.3s;
}

.submit-button:hover {
background-color: #ddd;
}
.modal {
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
display: none;
}

.modal-content {
background: #101618;
color: #fff;
width: 300px;
padding: 30px 20px;
border-radius: 12px;
text-align: center;
font-family: 'Gmarket Sans', sans-serif;
}

</style>
</head>
<body>
<div class="reset-container">
<h1>비밀번호 변경</h1>
<p>새로운 비밀번호를 입력해주세요.</p>

<div class="input-group">
<label class="input-label" for="newPassword">새로운 비밀번호</label>
<div class="input-wrapper">
<img class="icon-left" src="/images/lock-icon.png" alt="자물쇠 아이콘">
<input type="password" id="newPassword" placeholder="새로운 비밀번호를 입력해주세요.">
</div>
</div>

<div class="input-group">
<label class="input-label" for="newPasswordConfirm">비밀번호 재확인</label>
<div class="input-wrapper">
<img class="icon-left" src="/images/lock-icon.png" alt="자물쇠 아이콘">
<input type="password" id="newPasswordConfirm" placeholder="새로운 비밀번호를 확인해주세요.">
</div>
</div>

<button class="submit-button" onclick="submitPasswordReset()">비밀번호 변경</button>
</div>

<script>
function toggleVisibility(inputId, icon) {
const input = document.getElementById(inputId);
const isPassword = input.type === "password";
input.type = isPassword ? "text" : "password";
icon.src = isPassword ? "/images/eye-on-icon.png" : "/images/eye-off-icon.png";
}

function getParamFromURL(param) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(param);
}
function showSuccessModal(message) {
const modalHtml = `
<div id="success-modal" class="modal" style="display: flex; align-items: center; justify-content: center;">
<div class="modal-content">
<h3>완료</h3>
<p>${message}</p>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHtml);
Comment on lines +156 to +164
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

XSS 취약점: 서버 메시지를 그대로 innerHTML 삽입

${message}가 escape 없이 모달에 삽입되어 있습니다. 악의적인 스크립트가 포함될 경우 클라이언트에서 실행될 수 있습니다.

- <p>${message}</p>
+ <p id="reset-msg"></p>

그리고 JS 쪽에서:

- showSuccessModal(result.message || "비밀번호가 성공적으로 변경되었습니다.");
+ showSuccessModal(result.message ?? "비밀번호가 성공적으로 변경되었습니다.");

function showSuccessModal(message) {
  const modalHtml = `
    <div id="success-modal" class="modal" style="display:flex;align-items:center;justify-content:center;">
      <div class="modal-content">
        <h3>완료</h3>
        <p id="reset-msg"></p>
      </div>
    </div>`;
  document.body.insertAdjacentHTML('beforeend', modalHtml);
  document.getElementById('reset-msg').textContent = message; // textContent 로 XSS 차단
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/resources/templates/test-reset-password.html around lines 156 to
164, the code inserts the server-provided message directly into the modal's
innerHTML without escaping, causing an XSS vulnerability. To fix this, sanitize
or escape the message content before inserting it into the HTML, or use
textContent assignment instead of innerHTML to safely display the message
without executing any embedded scripts.

}

async function submitPasswordReset() {
const token = getParamFromURL("tempResetToken");
const newPassword = document.getElementById("newPassword").value;
const newPasswordConfirm = document.getElementById("newPasswordConfirm").value;

if (!newPassword || !newPasswordConfirm) {
alert("모든 값을 입력해주세요.");
return;
}

if (newPassword !== newPasswordConfirm) {
alert("비밀번호가 일치하지 않습니다.");
return;
}

try {
const response = await fetch("/api/auth/reset-password", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
tempResetToken: token,
newPassword: newPassword,
newPasswordConfirm: newPasswordConfirm
})
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || "비밀번호 변경 실패");
}

const result = await response.json();
showSuccessModal(result.message || "비밀번호가 성공적으로 변경되었습니다.");

// 2초 뒤 로그인 페이지로 이동
setTimeout(() => {
window.location.href = "/test/signin";
}, 2000);
} catch (error) {
alert("오류 발생: " + error.message);
}
}
</script>
</body>
</html>