Updated JS in main branch
This commit is contained in:
@@ -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) {
|
||||
'use strict';
|
||||
|
||||
@@ -34,11 +43,13 @@
|
||||
showDeleteButton: true,
|
||||
showUserInfo: true,
|
||||
showSessionStatus: true,
|
||||
showRecoveryButton: true,
|
||||
},
|
||||
callbacks: {},
|
||||
rpId: null,
|
||||
autoRefresh: true,
|
||||
refreshInterval: 5000,
|
||||
recoverySessionId: null,
|
||||
};
|
||||
|
||||
const DEFAULT_I18N = {
|
||||
@@ -449,6 +460,78 @@
|
||||
'ko': '비밀번호를 입력하세요',
|
||||
'ru': 'Пожалуйста, введите пароль',
|
||||
'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.refreshTimer = null;
|
||||
this.sessionStatus = false;
|
||||
this.recoveryMode = false;
|
||||
this.recoveryUser = null;
|
||||
}
|
||||
|
||||
DeviceManager.prototype.getSessionUserId = function() {
|
||||
@@ -1395,7 +1480,6 @@
|
||||
|
||||
DeviceManager.prototype.checkSession = async function() {
|
||||
try {
|
||||
// Simply validate session with server, same as devices.html does
|
||||
const sessionOk = await validSession(this.config.rpId);
|
||||
this.sessionStatus = !!sessionOk;
|
||||
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) {
|
||||
if (device.desc && device.desc.length > 0) {
|
||||
return device.desc;
|
||||
@@ -1510,20 +1700,24 @@
|
||||
|
||||
UIRenderer.prototype._getModalHTML = function(uniqueId, sessionStatusId, addBtnId, devicesListId) {
|
||||
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 `
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content fido2-sdk-card">
|
||||
<div class="modal-header fido2-sdk-header">
|
||||
${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>
|
||||
${this.config.features.showSessionStatus ? `<span class="badge fido2-sdk-status-badge" id="${sessionStatusId}"></span>` : ''}
|
||||
<h5 class="modal-title fido2-sdk-text">${modalTitle}</h5>
|
||||
${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>
|
||||
</div>
|
||||
<div class="modal-body fido2-sdk-container">
|
||||
${this._getBodyHTML(uniqueId, addBtnId, devicesListId)}
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1535,41 +1729,57 @@
|
||||
const features = this.config.features;
|
||||
const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
|
||||
const userId = dm ? dm.getEffectiveUserId() : (this.config.userId || '');
|
||||
const isRecoveryMode = dm ? dm.isInRecoveryMode() : false;
|
||||
const recoveryUser = dm ? dm.getRecoveryUser() : null;
|
||||
|
||||
let html = '';
|
||||
|
||||
if (features.showUserInfo && userId) {
|
||||
html += `<div class="fido2-sdk-user-info mb-3 fido2-sdk-text">
|
||||
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
if (features.showAddButton) {
|
||||
if (isRecoveryMode && recoveryUser) {
|
||||
html += `
|
||||
<button type="button" class="btn btn-info mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="${addBtnId}">
|
||||
${this.i18n.getText('btn_add')}
|
||||
<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) {
|
||||
html += `<div class="fido2-sdk-user-info mb-3 fido2-sdk-text">
|
||||
<strong>${this.i18n.getText('title_welcome')}:</strong> ${userId}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped fido2-sdk-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${this.i18n.getText('title_device')}</th>
|
||||
<th>${this.i18n.getText('title_time')}</th>
|
||||
${features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="${devicesListId}">
|
||||
<tr>
|
||||
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
if (features.showAddButton) {
|
||||
html += `
|
||||
<button type="button" class="btn btn-info mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="${addBtnId}">
|
||||
${this.i18n.getText('btn_add')}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped fido2-sdk-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>${this.i18n.getText('title_device')}</th>
|
||||
<th>${this.i18n.getText('title_time')}</th>
|
||||
${features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="${devicesListId}">
|
||||
<tr>
|
||||
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
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() {
|
||||
if (this.modalElement) {
|
||||
const bootstrapModal = window.bootstrap.Modal.getInstance(this.modalElement);
|
||||
@@ -1685,137 +1965,6 @@
|
||||
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() {
|
||||
this.config = null;
|
||||
@@ -1888,11 +2037,7 @@
|
||||
|
||||
const mode = manager.config.mode;
|
||||
|
||||
if (mode === 'standalone') {
|
||||
manager.uiRenderer.renderStandalone();
|
||||
} else {
|
||||
manager.uiRenderer.renderModal();
|
||||
}
|
||||
manager.uiRenderer.renderModal();
|
||||
|
||||
manager._bindInternalEvents();
|
||||
manager._loadInitialData();
|
||||
@@ -1944,6 +2089,43 @@
|
||||
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) => {
|
||||
console.error('FIDO2 SDK Error:', error);
|
||||
});
|
||||
@@ -1951,10 +2133,21 @@
|
||||
|
||||
Fido2UIManager.prototype._loadInitialData = async function() {
|
||||
try {
|
||||
await this.deviceManager.loadDevices();
|
||||
await this.deviceManager.checkSession();
|
||||
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.checkSession();
|
||||
}
|
||||
} catch (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;
|
||||
};
|
||||
|
||||
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) {
|
||||
if (!window.bootstrap) {
|
||||
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.renderDeviceManager = Fido2UIManager.prototype.renderDeviceManager;
|
||||
window.Fido2UIManager.renderLogin = Fido2UIManager.prototype.renderLogin;
|
||||
window.Fido2UIManager.renderDeviceManagerWithRecovery = Fido2UIManager.prototype.renderDeviceManagerWithRecovery;
|
||||
window.Fido2UIManager.renderFromUrlParams = Fido2UIManager.prototype.renderFromUrlParams;
|
||||
window.Fido2UIManager.close = function() {
|
||||
if (window.Fido2UIManager && window.Fido2UIManager.close) {
|
||||
window.Fido2UIManager.close();
|
||||
@@ -2025,6 +2253,24 @@
|
||||
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');
|
||||
})(window);
|
||||
|
||||
Reference in New Issue
Block a user