Updated JS in main branch
This commit is contained in:
@@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @file dfido2-lib.js
|
||||||
|
* @description FIDO2 library of amipro FIDO2 Server
|
||||||
|
* @version 2025-12-12
|
||||||
|
* @author Amipro Co., Ltd. (https://www.amipro.me/)
|
||||||
|
* @license Copyright (c) Amipro Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
const DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION = 'fido2_user_session'
|
const DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION = 'fido2_user_session'
|
||||||
const DFIDO2_LIB_LOCALSTG_NAME_REGISTERED = 'dfido2_lib_registered'
|
const DFIDO2_LIB_LOCALSTG_NAME_REGISTERED = 'dfido2_lib_registered'
|
||||||
const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url'
|
const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url'
|
||||||
|
|||||||
@@ -1,3 +1,12 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @file fido2-ui-sdk.js
|
||||||
|
* @description FIDO2 UI SDK of amipro FIDO2 Server
|
||||||
|
* @version 2025-12-12
|
||||||
|
* @author Amipro Co., Ltd. (https://www.amipro.me/)
|
||||||
|
* @license Copyright (c) Amipro Co., Ltd. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
(function(window) {
|
(function(window) {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
@@ -34,11 +43,13 @@
|
|||||||
showDeleteButton: true,
|
showDeleteButton: true,
|
||||||
showUserInfo: true,
|
showUserInfo: true,
|
||||||
showSessionStatus: true,
|
showSessionStatus: true,
|
||||||
|
showRecoveryButton: true,
|
||||||
},
|
},
|
||||||
callbacks: {},
|
callbacks: {},
|
||||||
rpId: null,
|
rpId: null,
|
||||||
autoRefresh: true,
|
autoRefresh: true,
|
||||||
refreshInterval: 5000,
|
refreshInterval: 5000,
|
||||||
|
recoverySessionId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const DEFAULT_I18N = {
|
const DEFAULT_I18N = {
|
||||||
@@ -449,6 +460,78 @@
|
|||||||
'ko': '비밀번호를 입력하세요',
|
'ko': '비밀번호를 입력하세요',
|
||||||
'ru': 'Пожалуйста, введите пароль',
|
'ru': 'Пожалуйста, введите пароль',
|
||||||
'it': 'Per favore inserisci password'
|
'it': 'Per favore inserisci password'
|
||||||
|
},
|
||||||
|
'title_recovery': {
|
||||||
|
'en-US': 'Device Recovery',
|
||||||
|
'zh-CN': '设备恢复',
|
||||||
|
'ja': 'デバイス回復',
|
||||||
|
'es': 'Recuperación de dispositivo',
|
||||||
|
'de': 'Gerätewiederherstellung',
|
||||||
|
'fr': 'Récupération de dispositif',
|
||||||
|
'pt': 'Recuperação de dispositivo',
|
||||||
|
'ko': '장치 복구',
|
||||||
|
'ru': 'Восстановление устройства',
|
||||||
|
'it': 'Ripristino dispositivo'
|
||||||
|
},
|
||||||
|
'msg_recovery_success': {
|
||||||
|
'en-US': 'Device recovered successfully',
|
||||||
|
'zh-CN': '设备恢复成功',
|
||||||
|
'ja': 'デバイス回復完了',
|
||||||
|
'es': 'Dispositivo recuperado exitosamente',
|
||||||
|
'de': 'Gerät erfolgreich wiederhergestellt',
|
||||||
|
'fr': 'Appareil récupéré avec succès',
|
||||||
|
'pt': 'Dispositivo recuperado com sucesso',
|
||||||
|
'ko': '장치 복구 완료',
|
||||||
|
'ru': 'Устройство успешно восстановлено',
|
||||||
|
'it': 'Dispositivo ripristinato con successo'
|
||||||
|
},
|
||||||
|
'msg_recovery_failed': {
|
||||||
|
'en-US': 'Recovery failed',
|
||||||
|
'zh-CN': '恢复失败',
|
||||||
|
'ja': '回復失敗',
|
||||||
|
'es': 'Recuperación fallida',
|
||||||
|
'de': 'Wiederherstellung fehlgeschlagen',
|
||||||
|
'fr': 'Échec de la récupération',
|
||||||
|
'pt': 'Falha na recuperação',
|
||||||
|
'ko': '복구 실패',
|
||||||
|
'ru': 'Ошибка восстановления',
|
||||||
|
'it': 'Ripristino fallito'
|
||||||
|
},
|
||||||
|
'msg_recovery_user_found': {
|
||||||
|
'en-US': 'Recovery session verified for user: {user}',
|
||||||
|
'zh-CN': '已验证恢复会话,用户: {user}',
|
||||||
|
'ja': 'ユーザー {user} の回復セッションが確認されました',
|
||||||
|
'es': 'Sesión de recuperación verificada para el usuario: {user}',
|
||||||
|
'de': 'Wiederherstellungssitzung für Benutzer verifiziert: {user}',
|
||||||
|
'fr': 'Session de récupération vérifiée pour l\'utilisateur: {user}',
|
||||||
|
'pt': 'Sessão de recuperação verificada para o usuário: {user}',
|
||||||
|
'ko': '사용자 {user}의 복구 세션 확인됨',
|
||||||
|
'ru': 'Сессия восстановления подтверждена для пользователя: {user}',
|
||||||
|
'it': 'Sessione di ripristino verificata per l\'utente: {user}'
|
||||||
|
},
|
||||||
|
'btn_start_recovery': {
|
||||||
|
'en-US': 'Start Recovery',
|
||||||
|
'zh-CN': '开始恢复',
|
||||||
|
'ja': '回復を開始',
|
||||||
|
'es': 'Iniciar recuperación',
|
||||||
|
'de': 'Wiederherstellung starten',
|
||||||
|
'fr': 'Démarrer la récupération',
|
||||||
|
'pt': 'Iniciar recuperação',
|
||||||
|
'ko': '복구 시작',
|
||||||
|
'ru': 'Начать восстановление',
|
||||||
|
'it': 'Avvia ripristino'
|
||||||
|
},
|
||||||
|
'btn_cancel': {
|
||||||
|
'en-US': 'Cancel',
|
||||||
|
'zh-CN': '取消',
|
||||||
|
'ja': 'キャンセル',
|
||||||
|
'es': 'Cancelar',
|
||||||
|
'de': 'Abbrechen',
|
||||||
|
'fr': 'Annuler',
|
||||||
|
'pt': 'Cancelar',
|
||||||
|
'ko': '취소',
|
||||||
|
'ru': 'Отмена',
|
||||||
|
'it': 'Annulla'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1286,6 +1369,8 @@
|
|||||||
this.devices = [];
|
this.devices = [];
|
||||||
this.refreshTimer = null;
|
this.refreshTimer = null;
|
||||||
this.sessionStatus = false;
|
this.sessionStatus = false;
|
||||||
|
this.recoveryMode = false;
|
||||||
|
this.recoveryUser = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceManager.prototype.getSessionUserId = function() {
|
DeviceManager.prototype.getSessionUserId = function() {
|
||||||
@@ -1395,7 +1480,6 @@
|
|||||||
|
|
||||||
DeviceManager.prototype.checkSession = async function() {
|
DeviceManager.prototype.checkSession = async function() {
|
||||||
try {
|
try {
|
||||||
// Simply validate session with server, same as devices.html does
|
|
||||||
const sessionOk = await validSession(this.config.rpId);
|
const sessionOk = await validSession(this.config.rpId);
|
||||||
this.sessionStatus = !!sessionOk;
|
this.sessionStatus = !!sessionOk;
|
||||||
this.eventManager.emit('sessionStatusChanged', this.sessionStatus);
|
this.eventManager.emit('sessionStatusChanged', this.sessionStatus);
|
||||||
@@ -1407,6 +1491,112 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.getRegistrationUser = async function(regSessionId) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(getServerUrl() + '/reg/username', {
|
||||||
|
method: 'POST',
|
||||||
|
cache: 'no-cache',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ session_id: regSessionId })
|
||||||
|
});
|
||||||
|
const resp = await response.json();
|
||||||
|
return resp;
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
return { status: 'failed', errorMessage: err.message };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.startRecovery = async function(recoverySessionId) {
|
||||||
|
try {
|
||||||
|
if (!recoverySessionId) {
|
||||||
|
throw new Error('Recovery session ID is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await this.getRegistrationUser(recoverySessionId);
|
||||||
|
|
||||||
|
if (result && result.status === 'ok' && result.username) {
|
||||||
|
this.recoveryMode = true;
|
||||||
|
this.recoveryUser = {
|
||||||
|
username: result.username,
|
||||||
|
displayName: result.displayname || result.username,
|
||||||
|
sessionId: recoverySessionId
|
||||||
|
};
|
||||||
|
|
||||||
|
this.config.userId = result.username;
|
||||||
|
this.eventManager.emit('recoveryStarted', this.recoveryUser);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
user: this.recoveryUser
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(result.errorMessage || 'Invalid recovery session');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.eventManager.emit('error', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.finishRecovery = async function(displayName) {
|
||||||
|
try {
|
||||||
|
if (!this.recoveryMode || !this.recoveryUser) {
|
||||||
|
throw new Error('Not in recovery mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
const effectiveDisplayName = displayName || this.recoveryUser.displayName || 'RecoveredDevice-' + this.recoveryUser.username;
|
||||||
|
|
||||||
|
const result = await registerFido2(this.recoveryUser.username, effectiveDisplayName, this.config.rpId);
|
||||||
|
|
||||||
|
if (result.status === 'ok') {
|
||||||
|
const wasRecoveryMode = this.recoveryMode;
|
||||||
|
const recoveredUser = this.recoveryUser;
|
||||||
|
|
||||||
|
this.recoveryMode = false;
|
||||||
|
this.recoveryUser = null;
|
||||||
|
|
||||||
|
await this.loadDevices();
|
||||||
|
this.eventManager.emit('deviceAdded', result);
|
||||||
|
this.eventManager.emit('recoveryCompleted', {
|
||||||
|
user: recoveredUser,
|
||||||
|
device: result
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
user: recoveredUser,
|
||||||
|
device: result
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(result.errorMessage || 'Failed to add device during recovery');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.eventManager.emit('error', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.cancelRecovery = function() {
|
||||||
|
if (this.recoveryMode) {
|
||||||
|
const canceledUser = this.recoveryUser;
|
||||||
|
this.recoveryMode = false;
|
||||||
|
this.recoveryUser = null;
|
||||||
|
this.eventManager.emit('recoveryCanceled', canceledUser);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.isInRecoveryMode = function() {
|
||||||
|
return this.recoveryMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
DeviceManager.prototype.getRecoveryUser = function() {
|
||||||
|
return this.recoveryUser;
|
||||||
|
};
|
||||||
|
|
||||||
DeviceManager.prototype.parseDeviceDescription = function(device) {
|
DeviceManager.prototype.parseDeviceDescription = function(device) {
|
||||||
if (device.desc && device.desc.length > 0) {
|
if (device.desc && device.desc.length > 0) {
|
||||||
return device.desc;
|
return device.desc;
|
||||||
@@ -1510,20 +1700,24 @@
|
|||||||
|
|
||||||
UIRenderer.prototype._getModalHTML = function(uniqueId, sessionStatusId, addBtnId, devicesListId) {
|
UIRenderer.prototype._getModalHTML = function(uniqueId, sessionStatusId, addBtnId, devicesListId) {
|
||||||
const theme = this.config.theme;
|
const theme = this.config.theme;
|
||||||
|
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
||||||
|
const isRecoveryMode = dm ? dm.isInRecoveryMode() : false;
|
||||||
|
const modalTitle = isRecoveryMode ? this.i18n.getText('title_recovery') : this.i18n.getText('my_devices');
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||||
<div class="modal-content fido2-sdk-card">
|
<div class="modal-content fido2-sdk-card">
|
||||||
<div class="modal-header fido2-sdk-header">
|
<div class="modal-header fido2-sdk-header">
|
||||||
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo" alt="Logo">` : ''}
|
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo" alt="Logo">` : ''}
|
||||||
<h5 class="modal-title fido2-sdk-text">${this.i18n.getText('my_devices')}</h5>
|
<h5 class="modal-title fido2-sdk-text">${modalTitle}</h5>
|
||||||
${this.config.features.showSessionStatus ? `<span class="badge fido2-sdk-status-badge" id="${sessionStatusId}"></span>` : ''}
|
${this.config.features.showSessionStatus && !isRecoveryMode ? `<span class="badge fido2-sdk-status-badge" id="${sessionStatusId}"></span>` : ''}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body fido2-sdk-container">
|
<div class="modal-body fido2-sdk-container">
|
||||||
${this._getBodyHTML(uniqueId, addBtnId, devicesListId)}
|
${this._getBodyHTML(uniqueId, addBtnId, devicesListId)}
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
${isRecoveryMode ? `<button type="button" class="btn btn-secondary fido2-sdk-btn" id="fido2CancelRecoveryBtn_${uniqueId}">${this.i18n.getText('btn_cancel')}</button>` : ''}
|
||||||
<button type="button" class="btn btn-secondary fido2-sdk-btn" data-bs-dismiss="modal">${this.i18n.getText('btn_close')}</button>
|
<button type="button" class="btn btn-secondary fido2-sdk-btn" data-bs-dismiss="modal">${this.i18n.getText('btn_close')}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1535,9 +1729,24 @@
|
|||||||
const features = this.config.features;
|
const features = this.config.features;
|
||||||
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
||||||
const userId = dm ? dm.getEffectiveUserId() : (this.config.userId || '');
|
const userId = dm ? dm.getEffectiveUserId() : (this.config.userId || '');
|
||||||
|
const isRecoveryMode = dm ? dm.isInRecoveryMode() : false;
|
||||||
|
const recoveryUser = dm ? dm.getRecoveryUser() : null;
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
|
if (isRecoveryMode && recoveryUser) {
|
||||||
|
html += `
|
||||||
|
<div class="alert alert-success mb-3 fido2-sdk-text">
|
||||||
|
<strong>${this.i18n.getText('msg_recovery_user_found').replace('{user}', recoveryUser.displayName || recoveryUser.username)}</strong>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<button type="button" class="btn btn-success mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2StartRecoveryBtn_${uniqueId}">
|
||||||
|
${this.i18n.getText('btn_start_recovery')}
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
if (features.showUserInfo && userId) {
|
if (features.showUserInfo && userId) {
|
||||||
html += `<div class="fido2-sdk-user-info mb-3 fido2-sdk-text">
|
html += `<div class="fido2-sdk-user-info mb-3 fido2-sdk-text">
|
||||||
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
||||||
@@ -1570,6 +1779,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
};
|
};
|
||||||
@@ -1591,6 +1801,28 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startRecoveryBtn = container.querySelector('[id^="fido2StartRecoveryBtn_"]');
|
||||||
|
if (startRecoveryBtn) {
|
||||||
|
startRecoveryBtn.replaceWith(startRecoveryBtn.cloneNode(true));
|
||||||
|
const newStartRecoveryBtn = container.querySelector('[id^="fido2StartRecoveryBtn_"]');
|
||||||
|
if (newStartRecoveryBtn) {
|
||||||
|
newStartRecoveryBtn.addEventListener('click', () => {
|
||||||
|
this.eventManager.emit('startRecovery');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelRecoveryBtn = container.querySelector('[id^="fido2CancelRecoveryBtn_"]');
|
||||||
|
if (cancelRecoveryBtn) {
|
||||||
|
cancelRecoveryBtn.replaceWith(cancelRecoveryBtn.cloneNode(true));
|
||||||
|
const newCancelRecoveryBtn = container.querySelector('[id^="fido2CancelRecoveryBtn_"]');
|
||||||
|
if (newCancelRecoveryBtn) {
|
||||||
|
newCancelRecoveryBtn.addEventListener('click', () => {
|
||||||
|
this.eventManager.emit('cancelRecovery');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container.querySelectorAll('[data-fido2-action="delete"]').forEach(btn => {
|
container.querySelectorAll('[data-fido2-action="delete"]').forEach(btn => {
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -1665,6 +1897,54 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UIRenderer.prototype.updateRecoveryUI = function() {
|
||||||
|
const container = this.modalElement || this.containerElement;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
||||||
|
if (!dm) return;
|
||||||
|
|
||||||
|
const isRecoveryMode = dm.isInRecoveryMode();
|
||||||
|
const uniqueId = this._uniqueId;
|
||||||
|
|
||||||
|
const titleEl = container.querySelector('.modal-title');
|
||||||
|
if (titleEl) {
|
||||||
|
titleEl.textContent = isRecoveryMode ? this.i18n.getText('title_recovery') : this.i18n.getText('my_devices');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionBadge = container.querySelector('[id^="fido2SessionStatus_"]');
|
||||||
|
if (sessionBadge) {
|
||||||
|
sessionBadge.style.display = isRecoveryMode ? 'none' : 'inline';
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalBody = container.querySelector('.modal-body');
|
||||||
|
if (modalBody) {
|
||||||
|
modalBody.innerHTML = this._getBodyHTML(uniqueId, 'fido2AddDeviceBtn_' + uniqueId, 'fido2DevicesList_' + uniqueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalFooter = container.querySelector('.modal-footer');
|
||||||
|
if (modalFooter) {
|
||||||
|
if (isRecoveryMode) {
|
||||||
|
let cancelBtn = modalFooter.querySelector('[id^="fido2CancelRecoveryBtn_"]');
|
||||||
|
if (!cancelBtn) {
|
||||||
|
cancelBtn = document.createElement('button');
|
||||||
|
cancelBtn.type = 'button';
|
||||||
|
cancelBtn.className = 'btn btn-secondary fido2-sdk-btn';
|
||||||
|
cancelBtn.id = 'fido2CancelRecoveryBtn_' + uniqueId;
|
||||||
|
cancelBtn.textContent = this.i18n.getText('btn_cancel');
|
||||||
|
modalFooter.insertBefore(cancelBtn, modalFooter.firstChild);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const cancelBtn = modalFooter.querySelector('[id^="fido2CancelRecoveryBtn_"]');
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._bindEvents(uniqueId, 'fido2AddDeviceBtn_' + uniqueId, 'fido2DevicesList_' + uniqueId);
|
||||||
|
};
|
||||||
|
|
||||||
UIRenderer.prototype.close = function() {
|
UIRenderer.prototype.close = function() {
|
||||||
if (this.modalElement) {
|
if (this.modalElement) {
|
||||||
const bootstrapModal = window.bootstrap.Modal.getInstance(this.modalElement);
|
const bootstrapModal = window.bootstrap.Modal.getInstance(this.modalElement);
|
||||||
@@ -1685,137 +1965,6 @@
|
|||||||
this.containerElement = null;
|
this.containerElement = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
UIRenderer.prototype.renderStandalone = function() {
|
|
||||||
if (this.config.container) {
|
|
||||||
const container = typeof this.config.container === 'string'
|
|
||||||
? document.querySelector(this.config.container)
|
|
||||||
: this.config.container;
|
|
||||||
|
|
||||||
if (container) {
|
|
||||||
container.innerHTML = this._getStandaloneBodyHTML();
|
|
||||||
this.containerElement = container;
|
|
||||||
this.themeManager.applyTheme(container);
|
|
||||||
this._bindEvents();
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.body.innerHTML = this._getStandaloneBodyHTML();
|
|
||||||
document.body.className = 'fido2-sdk-standalone';
|
|
||||||
this.containerElement = document.body;
|
|
||||||
this.themeManager.applyTheme(document.body);
|
|
||||||
this._bindEvents();
|
|
||||||
return document.body;
|
|
||||||
};
|
|
||||||
|
|
||||||
UIRenderer.prototype._getStandaloneBodyHTML = function() {
|
|
||||||
const theme = this.config.theme;
|
|
||||||
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
|
||||||
const userId = dm ? dm.getEffectiveUserId() : (this.config.userId || '');
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="container fido2-sdk-container">
|
|
||||||
<div class="card fido2-sdk-card">
|
|
||||||
<div class="card-header fido2-sdk-header d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo me-2" alt="Logo">` : ''}
|
|
||||||
<h4 class="mb-0 fido2-sdk-text">${this.i18n.getText('my_devices')}</h4>
|
|
||||||
</div>
|
|
||||||
${this.config.features.showSessionStatus ? '<span class="badge fido2-sdk-status-badge" id="fido2SessionStatus"></span>' : ''}
|
|
||||||
</div>
|
|
||||||
<div class="card-body fido2-sdk-body">
|
|
||||||
${this.config.features.showUserInfo && userId ? `
|
|
||||||
<div class="alert alert-info fido2-sdk-user-info fido2-sdk-text">
|
|
||||||
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
${this.config.features.showAddButton ? `
|
|
||||||
<button type="button" class="btn btn-info mt-2 mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2AddDeviceBtn">
|
|
||||||
${this.i18n.getText('btn_add')}
|
|
||||||
</button>
|
|
||||||
` : ''}
|
|
||||||
<div class="table-responsive mt-2">
|
|
||||||
<table class="table table-striped fido2-sdk-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>${this.i18n.getText('title_device')}</th>
|
|
||||||
<th>${this.i18n.getText('title_time')}</th>
|
|
||||||
${this.config.features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="fido2DevicesList">
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
UIRenderer.prototype._getStandaloneHTML = function() {
|
|
||||||
const theme = this.config.theme;
|
|
||||||
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
|
||||||
const userId = dm ? dm.getEffectiveUserId() : (this.config.userId || '');
|
|
||||||
|
|
||||||
return `
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>${this.i18n.getText('my_devices')}</title>
|
|
||||||
<link rel="stylesheet" href="files/bootstrap.css">
|
|
||||||
<link rel="stylesheet" href="files/boxicons.css">
|
|
||||||
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
|
||||||
</head>
|
|
||||||
<body class="fido2-sdk-standalone">
|
|
||||||
<div class="container fido2-sdk-container">
|
|
||||||
<div class="card fido2-sdk-card">
|
|
||||||
<div class="card-header fido2-sdk-header d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo me-2" alt="Logo">` : ''}
|
|
||||||
<h4 class="mb-0 fido2-sdk-text">${this.i18n.getText('my_devices')}</h4>
|
|
||||||
</div>
|
|
||||||
${this.config.features.showSessionStatus ? '<span class="badge fido2-sdk-status-badge" id="fido2SessionStatus"></span>' : ''}
|
|
||||||
</div>
|
|
||||||
<div class="card-body fido2-sdk-body">
|
|
||||||
${this.config.features.showUserInfo && userId ? `
|
|
||||||
<div class="alert alert-info fido2-sdk-user-info fido2-sdk-text">
|
|
||||||
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
|
||||||
</div>
|
|
||||||
` : ''}
|
|
||||||
${this.config.features.showAddButton ? `
|
|
||||||
<button type="button" class="btn btn-info mt-2 mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2AddDeviceBtn">
|
|
||||||
${this.i18n.getText('btn_add')}
|
|
||||||
</button>
|
|
||||||
` : ''}
|
|
||||||
<div class="table-responsive mt-2">
|
|
||||||
<table class="table table-striped fido2-sdk-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>${this.i18n.getText('title_device')}</th>
|
|
||||||
<th>${this.i18n.getText('title_time')}</th>
|
|
||||||
${this.config.features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody id="fido2DevicesList">
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
function Fido2UIManager() {
|
function Fido2UIManager() {
|
||||||
this.config = null;
|
this.config = null;
|
||||||
@@ -1888,11 +2037,7 @@
|
|||||||
|
|
||||||
const mode = manager.config.mode;
|
const mode = manager.config.mode;
|
||||||
|
|
||||||
if (mode === 'standalone') {
|
|
||||||
manager.uiRenderer.renderStandalone();
|
|
||||||
} else {
|
|
||||||
manager.uiRenderer.renderModal();
|
manager.uiRenderer.renderModal();
|
||||||
}
|
|
||||||
|
|
||||||
manager._bindInternalEvents();
|
manager._bindInternalEvents();
|
||||||
manager._loadInitialData();
|
manager._loadInitialData();
|
||||||
@@ -1944,6 +2089,43 @@
|
|||||||
self.uiRenderer.updateSessionStatus(isValid);
|
self.uiRenderer.updateSessionStatus(isValid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('startRecovery', async () => {
|
||||||
|
const recoveryBtn = document.querySelector('[id^="fido2StartRecoveryBtn_"]');
|
||||||
|
if (recoveryBtn) recoveryBtn.disabled = true;
|
||||||
|
try {
|
||||||
|
const result = await self.deviceManager.finishRecovery();
|
||||||
|
if (result.success) {
|
||||||
|
alert(self.i18n.getText('msg_recovery_success'));
|
||||||
|
self.uiRenderer.updateDevicesList(self.deviceManager.devices);
|
||||||
|
await self.deviceManager.checkSession();
|
||||||
|
self.uiRenderer.updateSessionStatus(self.deviceManager.sessionStatus);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Recovery error:', error);
|
||||||
|
alert(error.message || self.i18n.getText('msg_recovery_failed'));
|
||||||
|
} finally {
|
||||||
|
if (recoveryBtn) recoveryBtn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('cancelRecovery', async () => {
|
||||||
|
self.deviceManager.cancelRecovery();
|
||||||
|
self.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('recoveryStarted', (user) => {
|
||||||
|
console.log('Recovery started for user:', user);
|
||||||
|
self.uiRenderer.updateRecoveryUI();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('recoveryCompleted', (result) => {
|
||||||
|
console.log('Recovery completed:', result);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.eventManager.on('recoveryCanceled', (user) => {
|
||||||
|
console.log('Recovery canceled for user:', user);
|
||||||
|
});
|
||||||
|
|
||||||
this.eventManager.on('error', (error) => {
|
this.eventManager.on('error', (error) => {
|
||||||
console.error('FIDO2 SDK Error:', error);
|
console.error('FIDO2 SDK Error:', error);
|
||||||
});
|
});
|
||||||
@@ -1951,10 +2133,21 @@
|
|||||||
|
|
||||||
Fido2UIManager.prototype._loadInitialData = async function() {
|
Fido2UIManager.prototype._loadInitialData = async function() {
|
||||||
try {
|
try {
|
||||||
|
if (this.config.recoverySessionId) {
|
||||||
|
await this.deviceManager.startRecovery(this.config.recoverySessionId);
|
||||||
|
this.uiRenderer.cleanup();
|
||||||
|
this.uiRenderer.renderModal();
|
||||||
|
this._bindInternalEvents();
|
||||||
|
} else {
|
||||||
await this.deviceManager.loadDevices();
|
await this.deviceManager.loadDevices();
|
||||||
await this.deviceManager.checkSession();
|
await this.deviceManager.checkSession();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Initial data load error:', error);
|
console.error('Initial data load error:', error);
|
||||||
|
if (this.config.recoverySessionId) {
|
||||||
|
alert(error.message || this.i18n.getText('msg_recovery_failed'));
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1990,6 +2183,39 @@
|
|||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.renderDeviceManagerWithRecovery = function(config) {
|
||||||
|
const recoveryConfig = Object.assign({}, config, {
|
||||||
|
recoverySessionId: config.recoverySessionId || config.recoverySessionId
|
||||||
|
});
|
||||||
|
return this.renderDeviceManager(recoveryConfig);
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.renderFromUrlParams = function(config) {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const params = url.searchParams;
|
||||||
|
const regSessionId = params.get('rid');
|
||||||
|
|
||||||
|
if (regSessionId) {
|
||||||
|
return this.renderDeviceManagerWithRecovery(Object.assign({}, config, {
|
||||||
|
recoverySessionId: regSessionId
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return this.renderDeviceManager(config);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.startRecovery = function(recoverySessionId, displayName) {
|
||||||
|
return this.deviceManager.finishRecovery(displayName);
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.isRecoveryMode = function() {
|
||||||
|
return this.deviceManager ? this.deviceManager.isInRecoveryMode() : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
Fido2UIManager.prototype.getRecoveryUser = function() {
|
||||||
|
return this.deviceManager ? this.deviceManager.getRecoveryUser() : null;
|
||||||
|
};
|
||||||
|
|
||||||
Fido2UIManager.prototype.renderLogin = function(config) {
|
Fido2UIManager.prototype.renderLogin = function(config) {
|
||||||
if (!window.bootstrap) {
|
if (!window.bootstrap) {
|
||||||
throw new Error('Bootstrap is required. Please include Bootstrap JS before fido2-ui-sdk.js');
|
throw new Error('Bootstrap is required. Please include Bootstrap JS before fido2-ui-sdk.js');
|
||||||
@@ -2005,6 +2231,8 @@
|
|||||||
window.Fido2UIManager = new Fido2UIManager();
|
window.Fido2UIManager = new Fido2UIManager();
|
||||||
window.Fido2UIManager.renderDeviceManager = Fido2UIManager.prototype.renderDeviceManager;
|
window.Fido2UIManager.renderDeviceManager = Fido2UIManager.prototype.renderDeviceManager;
|
||||||
window.Fido2UIManager.renderLogin = Fido2UIManager.prototype.renderLogin;
|
window.Fido2UIManager.renderLogin = Fido2UIManager.prototype.renderLogin;
|
||||||
|
window.Fido2UIManager.renderDeviceManagerWithRecovery = Fido2UIManager.prototype.renderDeviceManagerWithRecovery;
|
||||||
|
window.Fido2UIManager.renderFromUrlParams = Fido2UIManager.prototype.renderFromUrlParams;
|
||||||
window.Fido2UIManager.close = function() {
|
window.Fido2UIManager.close = function() {
|
||||||
if (window.Fido2UIManager && window.Fido2UIManager.close) {
|
if (window.Fido2UIManager && window.Fido2UIManager.close) {
|
||||||
window.Fido2UIManager.close();
|
window.Fido2UIManager.close();
|
||||||
@@ -2025,6 +2253,24 @@
|
|||||||
window.Fido2UIManager.destroy();
|
window.Fido2UIManager.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
window.Fido2UIManager.isRecoveryMode = function() {
|
||||||
|
if (window.Fido2UIManager && window.Fido2UIManager.isRecoveryMode) {
|
||||||
|
return window.Fido2UIManager.isRecoveryMode();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
window.Fido2UIManager.getRecoveryUser = function() {
|
||||||
|
if (window.Fido2UIManager && window.Fido2UIManager.getRecoveryUser) {
|
||||||
|
return window.Fido2UIManager.getRecoveryUser();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
window.Fido2UIManager.startRecovery = function(recoverySessionId, displayName) {
|
||||||
|
if (window.Fido2UIManager && window.Fido2UIManager.startRecovery) {
|
||||||
|
return window.Fido2UIManager.startRecovery(recoverySessionId, displayName);
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error('Recovery not initialized'));
|
||||||
|
};
|
||||||
|
|
||||||
console.log('FIDO2 UI SDK v' + FIDO2_UI_VERSION + ' loaded');
|
console.log('FIDO2 UI SDK v' + FIDO2_UI_VERSION + ' loaded');
|
||||||
})(window);
|
})(window);
|
||||||
|
|||||||
Reference in New Issue
Block a user