diff --git a/files/dfido2-lib.js b/files/dfido2-lib.js index e1cb5e2..f4a68be 100644 --- a/files/dfido2-lib.js +++ b/files/dfido2-lib.js @@ -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_REGISTERED = 'dfido2_lib_registered' const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url' diff --git a/files/fido2-ui-sdk.js b/files/fido2-ui-sdk.js index 42cf6d2..06cec11 100644 --- a/files/fido2-ui-sdk.js +++ b/files/fido2-ui-sdk.js @@ -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 `