2FA (추가 인증) 기능 추가:
- Step-up 인증 Interceptor 및 보호 경로 관리 로직 도입 - 2FA 팝업 모듈 및 스타일(SASS, JS) 추가 - 로그인 실패 사유 Enum 및 2FA 관련 처리 로직 공통화
This commit is contained in:
@@ -197,39 +197,30 @@
|
||||
);
|
||||
}
|
||||
|
||||
// step-up 2FA 응답(401 + stepUpRequired) 판별 및 처리 헬퍼
|
||||
function isStepUpRequired(jqXHR) {
|
||||
return jqXHR && jqXHR.status === 401 && jqXHR.responseJSON && jqXHR.responseJSON.stepUpRequired;
|
||||
}
|
||||
function requireStepUp(purpose, retry) {
|
||||
if (typeof TwoFactorAuth === 'undefined') {
|
||||
customPopups.showAlert('추가 인증이 필요합니다. 다시 시도해주세요.');
|
||||
return;
|
||||
}
|
||||
TwoFactorAuth.open({
|
||||
mode: 'stepup',
|
||||
purpose: purpose,
|
||||
onSuccess: function() { retry(); },
|
||||
onCancel: function() { /* 취소: 원 작업 중단 */ }
|
||||
});
|
||||
}
|
||||
|
||||
// Show password prompt for viewing client secret (최초 1회 노출 + 서버측 물리 삭제)
|
||||
function showPasswordPrompt() {
|
||||
customPopups.showPasswordInput({
|
||||
title: 'Client Secret 조회',
|
||||
message: '보안을 위해 비밀번호를 입력해주세요.<br>조회 즉시 값은 영구 삭제됩니다.',
|
||||
onConfirm: function(password) {
|
||||
$.ajax({
|
||||
url: /*[[@{/myapikey/credential/reveal-secret}]]*/ '/myapikey/credential/reveal-secret',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ clientId: CREDENTIAL_CLIENT_ID, password: password }),
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response.success) {
|
||||
customPopups.hidePasswordInput();
|
||||
|
||||
// 서버가 반환한 secret을 화면에 1회 주입
|
||||
$('#revealedSecretValue').text(response.secret);
|
||||
$('#revealedSecretCopyBtn').attr('data-secret', response.secret).data('secret', response.secret);
|
||||
|
||||
$('#hiddenSecretBox').hide();
|
||||
$('#revealedSecretBox').fadeIn(300);
|
||||
} else if (response.alreadyRevealed) {
|
||||
customPopups.hidePasswordInput();
|
||||
showLostKeyGuide();
|
||||
} else {
|
||||
customPopups.showPasswordError(response.message || '비밀번호가 일치하지 않습니다.');
|
||||
}
|
||||
}).fail(function() {
|
||||
customPopups.showPasswordError('오류가 발생했습니다. 다시 시도해주세요.');
|
||||
});
|
||||
doRevealSecret(password);
|
||||
},
|
||||
onCancel: function() {
|
||||
// User cancelled - do nothing
|
||||
@@ -237,6 +228,42 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Client Secret 조회 요청 (step-up 필요 시 2FA 후 동일 요청 재시도)
|
||||
function doRevealSecret(password) {
|
||||
$.ajax({
|
||||
url: /*[[@{/myapikey/credential/reveal-secret}]]*/ '/myapikey/credential/reveal-secret',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ clientId: CREDENTIAL_CLIENT_ID, password: password }),
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response.success) {
|
||||
customPopups.hidePasswordInput();
|
||||
|
||||
// 서버가 반환한 secret을 화면에 1회 주입
|
||||
$('#revealedSecretValue').text(response.secret);
|
||||
$('#revealedSecretCopyBtn').attr('data-secret', response.secret).data('secret', response.secret);
|
||||
|
||||
$('#hiddenSecretBox').hide();
|
||||
$('#revealedSecretBox').fadeIn(300);
|
||||
} else if (response.alreadyRevealed) {
|
||||
customPopups.hidePasswordInput();
|
||||
showLostKeyGuide();
|
||||
} else {
|
||||
customPopups.showPasswordError(response.message || '비밀번호가 일치하지 않습니다.');
|
||||
}
|
||||
}).fail(function(jqXHR) {
|
||||
if (isStepUpRequired(jqXHR)) {
|
||||
customPopups.hidePasswordInput();
|
||||
requireStepUp('/myapikey/credential/reveal-secret', function() { doRevealSecret(password); });
|
||||
return;
|
||||
}
|
||||
customPopups.showPasswordError('오류가 발생했습니다. 다시 시도해주세요.');
|
||||
});
|
||||
}
|
||||
|
||||
// Copy to clipboard function - called from button with data-secret attribute
|
||||
function copyToClipboardFromButton(button) {
|
||||
var text = $(button).data('secret');
|
||||
@@ -277,34 +304,38 @@
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
doDeleteApiKey(clientId);
|
||||
});
|
||||
}
|
||||
|
||||
$('.loading-overlay').show();
|
||||
// 인증키 삭제 요청 (step-up 필요 시 2FA 후 동일 요청 재시도)
|
||||
function doDeleteApiKey(clientId) {
|
||||
$('.loading-overlay').show();
|
||||
|
||||
const requestData = {
|
||||
clientId: clientId
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/myapikey/api_key_delete',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(requestData),
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response && response.success === false) {
|
||||
customPopups.showAlert(response.msg || 'API 삭제에 실패했습니다.');
|
||||
return;
|
||||
}
|
||||
customPopups.showAlert(response.msg || 'API Key가 삭제되었습니다.', function() {
|
||||
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
||||
});
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
customPopups.showAlert('API 삭제 요청 중 오류가 발생했습니다: ' + errorThrown);
|
||||
}).always(function() {
|
||||
$('.loading-overlay').hide();
|
||||
$.ajax({
|
||||
url: '/myapikey/api_key_delete',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({ clientId: clientId }),
|
||||
headers: {
|
||||
'X-XSRF-TOKEN': /*[[${_csrf.token}]]*/ 'token'
|
||||
}
|
||||
}).done(function(response) {
|
||||
if (response && response.success === false) {
|
||||
customPopups.showAlert(response.msg || 'API 삭제에 실패했습니다.');
|
||||
return;
|
||||
}
|
||||
customPopups.showAlert(response.msg || 'API Key가 삭제되었습니다.', function() {
|
||||
window.location.href = /*[[@{/myapikey}]]*/ '/myapikey';
|
||||
});
|
||||
}).fail(function(jqXHR, textStatus, errorThrown) {
|
||||
if (isStepUpRequired(jqXHR)) {
|
||||
requireStepUp('/myapikey/api_key_delete', function() { doDeleteApiKey(clientId); });
|
||||
return;
|
||||
}
|
||||
customPopups.showAlert('API 삭제 요청 중 오류가 발생했습니다: ' + errorThrown);
|
||||
}).always(function() {
|
||||
$('.loading-overlay').hide();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user