diff --git a/src/main/resources/static/js/json-lint.js b/src/main/resources/static/js/json-lint.js index ebc3cc3..0f37911 100644 --- a/src/main/resources/static/js/json-lint.js +++ b/src/main/resources/static/js/json-lint.js @@ -415,18 +415,4 @@ var jsonlint = (function(){ if (typeof require !== 'undefined' && typeof exports !== 'undefined') { exports.parser = jsonlint; exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); } - exports.main = function commonjsMain(args) { - if (!args[1]) - throw new Error('Usage: '+args[0]+' FILE'); - if (typeof process !== 'undefined') { - var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8"); - } else { - var cwd = require("file").path(require("file").cwd()); - var source = cwd.join(args[1]).read({charset: "utf-8"}); - } - return exports.parser.parse(source); - } - if (typeof module !== 'undefined' && require.main === module) { - exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); - } } \ No newline at end of file diff --git a/src/main/resources/static/js/popup/custom-popups.js b/src/main/resources/static/js/popup/custom-popups.js index 23a4320..fdb5d1a 100644 --- a/src/main/resources/static/js/popup/custom-popups.js +++ b/src/main/resources/static/js/popup/custom-popups.js @@ -1,12 +1,48 @@ const customPopups = { + /** + * HTML 새니타이저 - 허용된 태그만 통과시키고 나머지는 제거 + * XSS 방지를 위해 script, event handler 등 위험 요소를 제거합니다. + * @param {string} input - 새니타이즈할 문자열 + * @returns {string} 새니타이즈된 HTML 문자열 + */ + _sanitizeHtml: function (input) { + if (!input) return ''; + var allowedTags = ['br', 'strong', 'b', 'em', 'i', 'span', 'p']; + var div = document.createElement('div'); + div.innerHTML = input; + + // script, style, iframe, object, embed, form 등 위험 요소 제거 + var dangerousTags = div.querySelectorAll('script, style, iframe, object, embed, form, link, meta, base'); + for (var i = dangerousTags.length - 1; i >= 0; i--) { + dangerousTags[i].parentNode.removeChild(dangerousTags[i]); + } + + // 허용되지 않은 태그는 텍스트 노드로 대체 + var allElements = div.querySelectorAll('*'); + for (var j = allElements.length - 1; j >= 0; j--) { + var el = allElements[j]; + if (allowedTags.indexOf(el.tagName.toLowerCase()) === -1) { + var textNode = document.createTextNode(el.textContent); + el.parentNode.replaceChild(textNode, el); + } else { + // 허용된 태그라도 모든 속성 제거 (on* 이벤트 핸들러 등 방지) + while (el.attributes.length > 0) { + el.removeAttribute(el.attributes[0].name); + } + } + } + + return div.innerHTML; + }, + /** * 알림 팝업 표시 * @param {string} message - 알림 메시지 * @param {Function} callback - 확인 버튼 클릭 시 호출되는 콜백 (선택사항) */ showAlert: function (message, callback) { - // 메시지 설정 - $('#customAlertMessage').html(message); + // 메시지 설정 (XSS 방지를 위해 새니타이즈) + $('#customAlertMessage').html(customPopups._sanitizeHtml(message)); // 팝업 표시 (modal 구조 사용) $('#customAlert').show(); @@ -100,9 +136,9 @@ const customPopups = { const onConfirm = options.onConfirm; const onCancel = options.onCancel; - // 팝업 내용 설정 + // 팝업 내용 설정 (XSS 방지를 위해 새니타이즈) $('#passwordPopupTitle').text(title); - $('#passwordPopupMessage').html(message); + $('#passwordPopupMessage').html(customPopups._sanitizeHtml(message)); // 입력 필드 및 에러 초기화 $('#passwordPopupInput').val('').removeClass('error'); @@ -215,8 +251,8 @@ const customPopups = { * @param {Function} callback - 버튼 클릭 시 호출되는 콜백 (파라미터: boolean - true는 확인, false는 취소) */ showConfirm: function (message, callback) { - // 메시지 설정 - $('#customConfirmMessage').html(message); + // 메시지 설정 (XSS 방지를 위해 새니타이즈) + $('#customConfirmMessage').html(customPopups._sanitizeHtml(message)); // 팝업 표시 (modal 구조 사용) $('#customConfirm').show(); @@ -302,7 +338,7 @@ const customPopups = { $(document).off('keydown.customConfirmEsc'); }, showEmailValidationPopup: function (message, onConvertCallback, onChangeEmailCallback) { - $('#emailValidationPopupMessage').html(message); + $('#emailValidationPopupMessage').html(customPopups._sanitizeHtml(message)); $('#emailValidationPopup').css('display', 'flex'); $('#emailConvertButton').one('click', function () { @@ -320,7 +356,7 @@ const customPopups = { }); }, showEmailResendPopup: function (message, loginId) { - $('#emailResendMessage').html(message); + $('#emailResendMessage').html(customPopups._sanitizeHtml(message)); $('#userLoginId').val(loginId); $('#emailResend').show(); }, diff --git a/src/main/resources/swagger/default-token-api-spec.json b/src/main/resources/swagger/default-token-api-spec.json index 133a1c7..6430a9d 100644 --- a/src/main/resources/swagger/default-token-api-spec.json +++ b/src/main/resources/swagger/default-token-api-spec.json @@ -1,15 +1,40 @@ { "openapi": "3.0.0", "info": { - "title": "케이뱅크 OAuth 2.0 토큰 발급", + "title": "광주은행 OAuth 2.0 토큰 발급", "description": "OAuth 2.0 토큰 발급을 위한 API 문서입니다.", "version": "1.0.0" }, + "security": [ + { + "clientCredentials": [] + } + ], + "components": { + "securitySchemes": { + "clientCredentials": { + "type": "oauth2", + "flows": { + "clientCredentials": { + "tokenUrl": "/api/v1/oauth/token", + "scopes": { + "api": "API 접근 권한" + } + } + } + } + } + }, "paths": { "/api/v1/oauth/token": { "post": { - "summary": "케이뱅크 액세스 토큰 발급", + "summary": "광주은행 액세스 토큰 발급", "description": "client_credentials grant type을 사용하여 액세스 토큰을 발급받습니다.", + "security": [ + { + "clientCredentials": ["api"] + } + ], "requestBody": { "required": true, "content": { @@ -26,12 +51,12 @@ "client_id": { "type": "string", "maxLength": 256, - "description": "케이뱅크에서 발급한 client id" + "description": "광주은행에서 발급한 client id" }, "client_secret": { "type": "string", "maxLength": 512, - "description": "케이뱅크에서 발급한 client secret" + "description": "광주은행에서 발급한 client secret" }, "scope": { "type": "string", diff --git a/src/main/resources/templates/views/apps/register/components/newUserInfoForm.html b/src/main/resources/templates/views/apps/register/components/newUserInfoForm.html index 1eca39d..ffc8127 100644 --- a/src/main/resources/templates/views/apps/register/components/newUserInfoForm.html +++ b/src/main/resources/templates/views/apps/register/components/newUserInfoForm.html @@ -183,6 +183,8 @@ // 테스트 환경 안내 메시지 표시 function displayTestAuthNotice(authNumber) { + // XSS 방지를 위해 authNumber를 이스케이프 처리 + const safeAuthNumber = String(authNumber).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); const noticeHtml = `
@@ -193,7 +195,7 @@

테스트 환경 안내

현재 테스트 환경입니다. 실제로 SMS가 발송되지 않으며, 인증번호는 - ${authNumber} + ${safeAuthNumber} 입니다.