비밀번호 암호화 로직 개선:
- name 재지정 및 hidden 필드로 봉투 전송 처리 추가 - 폼 제출 후 원상복구 스케줄링 로직 구현
This commit is contained in:
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user