비밀번호 암호화 로직 개선:
eapim-portal CI / build (push) Has been cancelled
eapim-portal Test / test (push) Has been cancelled

- name 재지정 및 hidden 필드로 봉투 전송 처리 추가
- 폼 제출 후 원상복구 스케줄링 로직 구현
This commit is contained in:
Rinjae
2026-09-02 09:50:56 +09:00
parent aafa7ac351
commit b2787882bf
@@ -158,8 +158,86 @@
});
}
/** 원본 입력에서 name 을 잠시 떼어둔 동안 그 이름을 보관하는 표시용 속성 */
var NAME_HOLDER = 'data-password-crypto-name';
/**
* 폼의 지정 필드 값을 봉투로 치환한다. 실패해도 reject 하지 않고 평문을 남긴다.
* 입력 요소에서 실제 <input> 하나를 고른다.
* 같은 name 이 둘 이상이면 form.elements[name] 이 RadioNodeList 를 돌려주므로,
* 우리가 붙인 hidden 이 아닌 실제 입력창을 골라야 한다.
*/
function resolveInput(el) {
if (!el) {
return null;
}
if (el.tagName) {
return el;
}
for (var i = 0; i < el.length; i++) {
var candidate = el[i];
if (candidate && candidate.tagName && candidate.type !== 'hidden') {
return candidate;
}
}
return null;
}
/**
* 원본 입력의 value 는 그대로 두고, name 만 같은 이름의 hidden 으로 옮겨 봉투를 싣는다.
* @returns {Function} 원상복구 함수
*/
function swapToEnvelopeField(form, input, name, envelope) {
var hidden = (form.ownerDocument || global.document).createElement('input');
hidden.type = 'hidden';
hidden.name = name;
hidden.value = envelope;
hidden.setAttribute(NAME_HOLDER, name);
input.setAttribute(NAME_HOLDER, name);
input.removeAttribute('name');
form.appendChild(hidden);
return function restore() {
if (hidden.parentNode) {
hidden.parentNode.removeChild(hidden);
}
if (input.getAttribute(NAME_HOLDER) === name) {
input.setAttribute('name', name);
input.removeAttribute(NAME_HOLDER);
}
};
}
/**
* 제출 직후 원상복구한다. 폼 데이터 직렬화는 form.submit()/requestSubmit() 호출 시점에
* 동기로 끝나므로, 다음 매크로태스크에서 되돌려도 봉투는 이미 전송된 뒤다.
* 뒤로가기(bfcache) 복원 시에도 name 이 떨어진 채 남지 않도록 pageshow 에서 한 번 더 처리한다.
*/
function scheduleRestore(restores) {
if (restores.length === 0) {
return;
}
var done = false;
var run = function () {
if (done) {
return;
}
done = true;
restores.forEach(function (restore) { restore(); });
global.removeEventListener('pageshow', run);
};
global.addEventListener('pageshow', run);
global.setTimeout(run, 0);
}
/**
* 폼의 지정 필드를 봉투로 전송한다. 실패해도 reject 하지 않고 평문을 남긴다.
*
* 입력창(<input type="password">)의 value 는 절대 건드리지 않는다. 브라우저 비밀번호 관리자는
* 제출 시점의 입력값을 그대로 저장하므로 봉투로 덮어쓰면 봉투 문자열이 비밀번호로 저장되고,
* 다음 자동완성 로그인이 깨진다. 대신 원본 입력의 name 을 잠시 떼고 같은 name 의 hidden 에
* 봉투를 실어 보낸 뒤 제출 직후 되돌린다.
*
* @param {HTMLFormElement} form
* @param {string[]} fieldNames
* @returns {Promise<void>}
@@ -171,7 +249,7 @@
var targets = {};
var elements = {};
fieldNames.forEach(function (name) {
var el = form.elements ? form.elements[name] : null;
var el = resolveInput(form.elements ? form.elements[name] : null);
if (el && typeof el.value === 'string' && el.value.length > 0) {
elements[name] = el;
targets[name] = el.value;
@@ -181,11 +259,14 @@
return global.Promise.resolve();
}
return encryptValues(targets).then(function (encrypted) {
var restores = [];
Object.keys(elements).forEach(function (name) {
if (encrypted[name]) {
elements[name].value = encrypted[name];
// 폴백으로 평문이 그대로 돌아온 경우엔 아무것도 바꾸지 않는다.
if (encrypted[name] && encrypted[name] !== targets[name]) {
restores.push(swapToEnvelopeField(form, elements[name], name, encrypted[name]));
}
});
scheduleRestore(restores);
});
}