diff --git a/files/fido2-ui-sdk.js b/files/fido2-ui-sdk.js
index 4eca816..3affe6e 100644
--- a/files/fido2-ui-sdk.js
+++ b/files/fido2-ui-sdk.js
@@ -32,90 +32,1074 @@
'my_devices': {
'en-US': 'My devices',
'zh-CN': '我的设备',
- 'ja': 'マイデバイス'
+ 'ja': 'マイデバイス',
+ 'es': 'Mis dispositivos',
+ 'de': 'Meine Geräte',
+ 'fr': 'Mes appareils',
+ 'pt': 'Meus dispositivos',
+ 'ko': '내 장치',
+ 'ru': 'Мои устройства',
+ 'it': 'I miei dispositivi'
},
'btn_add': {
'en-US': 'Add device',
'zh-CN': '添加设备',
- 'ja': 'デバイスを追加'
+ 'ja': 'デバイスを追加',
+ 'es': 'Añadir dispositivo',
+ 'de': 'Gerät hinzufügen',
+ 'fr': 'Ajouter un appareil',
+ 'pt': 'Adicionar dispositivo',
+ 'ko': '장치 추가',
+ 'ru': 'Добавить устройство',
+ 'it': 'Aggiungi dispositivo'
},
'title_device': {
'en-US': 'Device',
'zh-CN': '设备',
- 'ja': 'デバイス'
+ 'ja': 'デバイス',
+ 'es': 'Dispositivo',
+ 'de': 'Gerät',
+ 'fr': 'Appareil',
+ 'pt': 'Dispositivo',
+ 'ko': '장치',
+ 'ru': 'Устройство',
+ 'it': 'Dispositivo'
},
'title_time': {
'en-US': 'Registered time',
'zh-CN': '添加时间',
- 'ja': '登録時間'
+ 'ja': '登録時間',
+ 'es': 'Hora de registro',
+ 'de': 'Registrierungszeit',
+ 'fr': 'Heure d\'inscription',
+ 'pt': 'Hora do registro',
+ 'ko': '등록 시간',
+ 'ru': 'Время регистрации',
+ 'it': 'Ora di registrazione'
},
'title_act': {
'en-US': 'Actions',
'zh-CN': '操作',
- 'ja': '操作'
+ 'ja': '操作',
+ 'es': 'Acciones',
+ 'de': 'Aktionen',
+ 'fr': 'Actions',
+ 'pt': 'Ações',
+ 'ko': '작업',
+ 'ru': 'Действия',
+ 'it': 'Azioni'
},
'title_del': {
'en-US': 'Delete',
'zh-CN': '删除',
- 'ja': '削除'
+ 'ja': '削除',
+ 'es': 'Eliminar',
+ 'de': 'Löschen',
+ 'fr': 'Supprimer',
+ 'pt': 'Excluir',
+ 'ko': '삭제',
+ 'ru': 'Удалить',
+ 'it': 'Elimina'
},
'title_logout': {
'en-US': 'Log out',
'zh-CN': '登出',
- 'ja': 'ログアウト'
+ 'ja': 'ログアウト',
+ 'es': 'Cerrar sesión',
+ 'de': 'Abmelden',
+ 'fr': 'Déconnexion',
+ 'pt': 'Sair',
+ 'ko': '로그아웃',
+ 'ru': 'Выйти',
+ 'it': 'Disconnetti'
},
'title_empty_list': {
'en-US': 'No devices, please add.',
'zh-CN': '无设备,请添加。',
- 'ja': 'デバイスがなし、追加してください。'
+ 'ja': 'デバイスがなし、追加してください。',
+ 'es': 'Sin dispositivos, por favor añada.',
+ 'de': 'Keine Geräte, bitte hinzufügen.',
+ 'fr': 'Pas d\'appareils, s\'il vous plaît ajouter.',
+ 'pt': 'Sem dispositivos, por favor adicione.',
+ 'ko': '장치가 없습니다. 추가해 주세요.',
+ 'ru': 'Нет устройств, пожалуйста добавьте.',
+ 'it': 'Nessun dispositivo, si prega di aggiungere.'
},
'msg_register_ok': {
'en-US': 'Device registered successfully',
'zh-CN': '添加设备成功',
- 'ja': 'デバイス登録完了'
+ 'ja': 'デバイス登録完了',
+ 'es': 'Dispositivo registrado exitosamente',
+ 'de': 'Gerät erfolgreich registriert',
+ 'fr': 'Appareil enregistré avec succès',
+ 'pt': 'Dispositivo registrado com sucesso',
+ 'ko': '장치 등록 완료',
+ 'ru': 'Устройство успешно зарегистрировано',
+ 'it': 'Dispositivo registrato con successo'
},
'msg_deldev_ok': {
'en-US': 'Device deleted successfully',
'zh-CN': '设备删除成功',
- 'ja': 'デバイスを削除しました'
+ 'ja': 'デバイスを削除しました',
+ 'es': 'Dispositivo eliminado exitosamente',
+ 'de': 'Gerät erfolgreich gelöscht',
+ 'fr': 'Appareil supprimé avec succès',
+ 'pt': 'Dispositivo excluído com sucesso',
+ 'ko': '장치 삭제 완료',
+ 'ru': 'Устройство успешно удалено',
+ 'it': 'Dispositivo eliminato con successo'
},
'msg_confirm_deldev': {
'en-US': 'Do you want to delete this device?',
'zh-CN': '确认删除此设备吗?',
- 'ja': 'デバイスを削除しますか?'
+ 'ja': 'デバイスを削除しますか?',
+ 'es': '¿Desea eliminar este dispositivo?',
+ 'de': 'Möchten Sie dieses Gerät löschen?',
+ 'fr': 'Voulez-vous supprimer cet appareil?',
+ 'pt': 'Deseja excluir este dispositivo?',
+ 'ko': '이 장치를 삭제하시겠습니까?',
+ 'ru': 'Хотите удалить это устройство?',
+ 'it': 'Vuoi eliminare questo dispositivo?'
},
'msg_session_status_ok': {
'en-US': 'FIDO2 session is valid',
'zh-CN': 'FIDO2会话正常',
- 'ja': 'FIDO2セッションは正常です'
+ 'ja': 'FIDO2セッションは正常です',
+ 'es': 'La sesión FIDO2 es válida',
+ 'de': 'FIDO2-Sitzung ist gültig',
+ 'fr': 'La session FIDO2 est valide',
+ 'pt': 'A sessão FIDO2 é válida',
+ 'ko': 'FIDO2 세션이 정상입니다',
+ 'ru': 'Сессия FIDO2 действительна',
+ 'it': 'La sessione FIDO2 è valida'
},
'msg_session_status_fail': {
'en-US': 'FIDO2 session is invalid',
'zh-CN': 'FIDO2会话无效',
- 'ja': 'FIDO2セッションは無効です'
+ 'ja': 'FIDO2セッションは無効です',
+ 'es': 'La sesión FIDO2 no es válida',
+ 'de': 'FIDO2-Sitzung ist ungültig',
+ 'fr': 'La session FIDO2 n\'est pas valide',
+ 'pt': 'A sessão FIDO2 é inválida',
+ 'ko': 'FIDO2 세션이 무효합니다',
+ 'ru': 'Сессия FIDO2 недействительна',
+ 'it': 'La sessione FIDO2 non è valida'
},
'btn_close': {
'en-US': 'Close',
'zh-CN': '关闭',
- 'ja': '閉じる'
+ 'ja': '閉じる',
+ 'es': 'Cerrar',
+ 'de': 'Schließen',
+ 'fr': 'Fermer',
+ 'pt': 'Fechar',
+ 'ko': '닫기',
+ 'ru': 'Закрыть',
+ 'it': 'Chiudi'
},
'title_welcome': {
'en-US': 'Welcome',
'zh-CN': '欢迎',
- 'ja': 'ようこそ'
+ 'ja': 'ようこそ',
+ 'es': 'Bienvenido',
+ 'de': 'Willkommen',
+ 'fr': 'Bienvenue',
+ 'pt': 'Bem-vindo',
+ 'ko': '환영합니다',
+ 'ru': 'Добро пожаловать',
+ 'it': 'Benvenuto'
},
'btn_login': {
'en-US': 'Login',
'zh-CN': '重新登录',
- 'ja': 'ログイン'
+ 'ja': 'ログイン',
+ 'es': 'Iniciar sesión',
+ 'de': 'Anmelden',
+ 'fr': 'Connexion',
+ 'pt': 'Login',
+ 'ko': '로그인',
+ 'ru': 'Войти',
+ 'it': 'Accedi'
},
'msg_session_invalid': {
'en-US': 'Session expired, please login again',
'zh-CN': '会话已过期,请重新登录',
- 'ja': 'セッション切れ、再ログインしてください'
+ 'ja': 'セッション切れ、再ログインしてください',
+ 'es': 'Sesión vencida, por favor inicie sesión nuevamente',
+ 'de': 'Sitzung abgelaufen, bitte erneut anmelden',
+ 'fr': 'Session expirée, veuillez vous connecter à nouveau',
+ 'pt': 'Sessão expirada, por favor faça login novamente',
+ 'ko': '세션이 만료되었습니다. 다시 로그인해 주세요.',
+ 'ru': 'Сессия истекла, пожалуйста войдите снова',
+ 'it': 'Sessione scaduta, si prega di accedere nuovamente'
+ },
+ 'title_login': {
+ 'en-US': 'Login',
+ 'zh-CN': '登录',
+ 'ja': 'ログイン',
+ 'es': 'Iniciar sesión',
+ 'de': 'Anmelden',
+ 'fr': 'Connexion',
+ 'pt': 'Login',
+ 'ko': '로그인',
+ 'ru': 'Войти',
+ 'it': 'Accedi'
+ },
+ 'title_password_login': {
+ 'en-US': 'Password Login',
+ 'zh-CN': '密码登录',
+ 'ja': 'パスワードログイン',
+ 'es': 'Iniciar sesión con contraseña',
+ 'de': 'Anmeldung mit Passwort',
+ 'fr': 'Connexion par mot de passe',
+ 'pt': 'Login com senha',
+ 'ko': '비밀번호 로그인',
+ 'ru': 'Вход по паролю',
+ 'it': 'Accesso con password'
+ },
+ 'btn_fido2_login': {
+ 'en-US': 'Use Passkey',
+ 'zh-CN': '使用Passkey登录',
+ 'ja': 'Passkeyを使用',
+ 'es': 'Usar Passkey',
+ 'de': 'Passkey verwenden',
+ 'fr': 'Utiliser Passkey',
+ 'pt': 'Usar Passkey',
+ 'ko': 'Passkey 사용',
+ 'ru': 'Использовать Passkey',
+ 'it': 'Usa Passkey'
+ },
+ 'btn_password_login': {
+ 'en-US': 'Password Login',
+ 'zh-CN': '密码登录',
+ 'ja': 'パスワードログイン',
+ 'es': 'Iniciar sesión con contraseña',
+ 'de': 'Anmeldung mit Passwort',
+ 'fr': 'Connexion par mot de passe',
+ 'pt': 'Login com senha',
+ 'ko': '비밀번호 로그인',
+ 'ru': 'Вход по паролю',
+ 'it': 'Accesso con password'
+ },
+ 'link_use_password': {
+ 'en-US': 'Use password instead',
+ 'zh-CN': '使用密码登录',
+ 'ja': 'パスワードを使用',
+ 'es': 'Usar contraseña',
+ 'de': 'Passwort verwenden',
+ 'fr': 'Utiliser mot de passe',
+ 'pt': 'Usar senha',
+ 'ko': '비밀번호 사용',
+ 'ru': 'Использовать пароль',
+ 'it': 'Usa password'
+ },
+ 'link_use_fido2': {
+ 'en-US': 'Use Passkey instead',
+ 'zh-CN': '使用Passkey登录',
+ 'ja': 'Passkeyを使用',
+ 'es': 'Usar Passkey',
+ 'de': 'Passkey verwenden',
+ 'fr': 'Utiliser Passkey',
+ 'pt': 'Usar Passkey',
+ 'ko': 'Passkey 사용',
+ 'ru': 'Использовать Passkey',
+ 'it': 'Usa Passkey'
+ },
+ 'placeholder_user_id': {
+ 'en-US': 'Enter User ID',
+ 'zh-CN': '请输入用户ID',
+ 'ja': 'ユーザーIDを入力',
+ 'es': 'Ingrese ID de usuario',
+ 'de': 'Benutzer-ID eingeben',
+ 'fr': 'Entrez l\'ID utilisateur',
+ 'pt': 'Digite o ID do usuário',
+ 'ko': '사용자 ID 입력',
+ 'ru': 'Введите ID пользователя',
+ 'it': 'Inserisci ID utente'
+ },
+ 'placeholder_password': {
+ 'en-US': 'Enter Password',
+ 'zh-CN': '请输入密码',
+ 'ja': 'パスワードを入力',
+ 'es': 'Ingrese contraseña',
+ 'de': 'Passwort eingeben',
+ 'fr': 'Entrez le mot de passe',
+ 'pt': 'Digite a senha',
+ 'ko': '비밀번호 입력',
+ 'ru': 'Введите пароль',
+ 'it': 'Inserisci password'
+ },
+ 'msg_remaining_attempts': {
+ 'en-US': 'Remaining attempts: {n}',
+ 'zh-CN': '剩余尝试次数: {n}',
+ 'ja': '残り{n}回',
+ 'es': 'Intentos restantes: {n}',
+ 'de': 'Verbleibende Versuche: {n}',
+ 'fr': 'Tentatives restantes: {n}',
+ 'pt': 'Tentativas restantes: {n}',
+ 'ko': '남은 시도: {n}',
+ 'ru': 'Осталось попыток: {n}',
+ 'it': 'Tentativi rimanenti: {n}'
+ },
+ 'msg_password_exhausted': {
+ 'en-US': 'Too many failed attempts, login closed',
+ 'zh-CN': '尝试次数过多,登录已关闭',
+ 'ja': '試行回数超過、ログイン閉鎖',
+ 'es': 'Demasiados intentos fallidos, inicio de sesión cerrado',
+ 'de': 'Zu viele fehlgeschlagene Versuche, Anmeldung geschlossen',
+ 'fr': 'Trop de tentatives échouées, connexion fermée',
+ 'pt': 'Muitas tentativas falharam, login fechado',
+ 'ko': '시도 횟수 초과, 로그인 종료',
+ 'ru': 'Слишком много неудачных попыток, вход закрыт',
+ 'it': 'Troppi tentativi falliti, accesso chiuso'
+ },
+ 'title_password_exhausted': {
+ 'en-US': 'Login Failed',
+ 'zh-CN': '登录失败',
+ 'ja': 'ログイン失敗',
+ 'es': 'Error de inicio de sesión',
+ 'de': 'Anmeldung fehlgeschlagen',
+ 'fr': 'Échec de la connexion',
+ 'pt': 'Falha no login',
+ 'ko': '로그인 실패',
+ 'ru': 'Ошибка входа',
+ 'it': 'Accesso fallito'
+ },
+ 'msg_no_registration': {
+ 'en-US': 'No device registered',
+ 'zh-CN': '暂未注册设备',
+ 'ja': 'デバイス未登録',
+ 'es': 'No hay dispositivo registrado',
+ 'de': 'Kein Gerät registriert',
+ 'fr': 'Aucun appareil enregistré',
+ 'pt': 'Nenhum dispositivo registrado',
+ 'ko': '등록된 장치 없음',
+ 'ru': 'Устройство не зарегистрировано',
+ 'it': 'Nessun dispositivo registrato'
+ },
+ 'msg_fido2_failed': {
+ 'en-US': 'Passkey login failed',
+ 'zh-CN': 'Passkey登录失败',
+ 'ja': 'Passkeyログイン失敗',
+ 'es': 'Error de inicio de sesión con Passkey',
+ 'de': 'Passkey-Anmeldung fehlgeschlagen',
+ 'fr': 'Échec de la connexion Passkey',
+ 'pt': 'Falha no login com Passkey',
+ 'ko': 'Passkey 로그인 실패',
+ 'ru': 'Ошибка входа Passkey',
+ 'it': 'Accesso Passkey fallito'
+ },
+ 'msg_fido2_canceled': {
+ 'en-US': 'Login was canceled',
+ 'zh-CN': '登录已取消',
+ 'ja': 'ログインキャンセル',
+ 'es': 'Inicio de sesión cancelado',
+ 'de': 'Anmeldung abgebrochen',
+ 'fr': 'Connexion annulée',
+ 'pt': 'Login cancelado',
+ 'ko': '로그인 취소됨',
+ 'ru': 'Вход отменён',
+ 'it': 'Accesso annullato'
+ },
+ 'msg_autofail_hint': {
+ 'en-US': 'Auto login failed, please enter User ID',
+ 'zh-CN': '自动登录失败,请输入用户ID',
+ 'ja': '自動ログイン失敗、ユーザーIDを入力してください',
+ 'es': 'Error de inicio de sesión automático, ingrese ID de usuario',
+ 'de': 'Automatische Anmeldung fehlgeschlagen, bitte Benutzer-ID eingeben',
+ 'fr': 'Échec de la connexion automatique, entrez l\'ID utilisateur',
+ 'pt': 'Login automático falhou, digite o ID do usuário',
+ 'ko': '자동 로그인 실패, 사용자 ID 입력하세요',
+ 'ru': 'Автоматический вход не удался, введите ID пользователя',
+ 'it': 'Accesso automatico fallito, inserisci ID utente'
+ },
+ 'msg_invalid_user_id': {
+ 'en-US': 'Please enter User ID',
+ 'zh-CN': '请输入用户ID',
+ 'ja': 'ユーザーIDを入力してください',
+ 'es': 'Por favor ingrese ID de usuario',
+ 'de': 'Bitte Benutzer-ID eingeben',
+ 'fr': 'Veuillez entrer l\'ID utilisateur',
+ 'pt': 'Por favor digite o ID do usuário',
+ 'ko': '사용자 ID를 입력하세요',
+ 'ru': 'Пожалуйста, введите ID пользователя',
+ 'it': 'Per favore inserisci ID utente'
+ },
+ 'msg_invalid_password': {
+ 'en-US': 'Please enter Password',
+ 'zh-CN': '请输入密码',
+ 'ja': 'パスワードを入力してください',
+ 'es': 'Por favor ingrese contraseña',
+ 'de': 'Bitte Passwort eingeben',
+ 'fr': 'Veuillez entrer le mot de passe',
+ 'pt': 'Por favor digite a senha',
+ 'ko': '비밀번호를 입력하세요',
+ 'ru': 'Пожалуйста, введите пароль',
+ 'it': 'Per favore inserisci password'
}
};
+ const LOGIN_DEFAULT_CONFIG = {
+ serverUrl: 'https://fido2.amipro.me',
+ container: null,
+ theme: {
+ logo: null,
+ primaryColor: '#696cff',
+ backgroundColor: '#ffffff',
+ textColor: '#333333',
+ borderRadius: '8px',
+ },
+ language: 'zh-CN',
+ customI18n: {},
+ features: {
+ autoAuth: true,
+ enablePasswordLogin: true,
+ autoShowPassword: false,
+ maxPasswordAttempts: 3,
+ showRemainingAttempts: true,
+ },
+ callbacks: {
+ onFido2Success: null,
+ onFido2Error: null,
+ onPasswordLogin: null,
+ onPasswordExhausted: null,
+ onLoginClosed: null,
+ },
+ rpId: null,
+ };
+
+ const LoginMode = {
+ FIDO2: 'fido2',
+ PASSWORD: 'password'
+ };
+
+ const LoginState = {
+ LOADING: 'loading',
+ FIDO2: 'fido2',
+ PASSWORD: 'password',
+ CLOSED: 'closed'
+ };
+
+ function Fido2Login(config) {
+ this.config = Object.assign({}, LOGIN_DEFAULT_CONFIG, config);
+ this.i18n = new I18nManager(this.config);
+ this.themeManager = new ThemeManager(this.config);
+ this.eventManager = new EventManager();
+ this.state = LoginState.LOADING;
+ this.mode = LoginMode.FIDO2;
+ this.attemptCount = 0;
+ this.maxAttempts = this.config.features.maxPasswordAttempts || 3;
+ this.modalElement = null;
+ this.containerElement = null;
+ this.bootstrapModal = null;
+ this.initialized = false;
+
+ if (this.config.serverUrl) {
+ setFidoServerURL(this.config.serverUrl);
+ }
+ }
+
+ Fido2Login.prototype._createModal = function() {
+ const container = typeof this.config.container === 'string'
+ ? document.querySelector(this.config.container)
+ : this.config.container;
+
+ this._debugLog('[Fido2Login] _createModal container:', this.config.container);
+ this._debugLog('[Fido2Login] Container element:', container);
+
+ if (!container) {
+ throw new Error('Container not found: ' + this.config.container);
+ }
+
+ container.innerHTML = '';
+
+ const modal = document.createElement('div');
+ modal.className = 'modal fade fido2-sdk-login-modal';
+ modal.id = 'fido2LoginModal';
+ modal.tabIndex = -1;
+ modal.setAttribute('aria-hidden', 'true');
+
+ modal.innerHTML = this._getModalHTML();
+ container.appendChild(modal);
+ this.modalElement = modal;
+ this.containerElement = container;
+
+ this._debugLog('[Fido2Login] Modal element created');
+
+ this.themeManager.applyTheme(modal);
+
+ this.bootstrapModal = new window.bootstrap.Modal(modal, {
+ backdrop: true,
+ keyboard: true
+ });
+
+ this._debugLog('[Fido2Login] BootstrapModal instance created');
+
+ modal.addEventListener('hidden.bs.modal', () => {
+ this.state = LoginState.CLOSED;
+ this._emit('loginClosed');
+ this.cleanup();
+ });
+
+ this._bindEvents();
+ return modal;
+ };
+
+ Fido2Login.prototype._getModalHTML = function() {
+ const theme = this.config.theme;
+ const features = this.config.features;
+
+ return `
+
+
+
+
+ ${this._getBodyHTML()}
+
+
+
+ `;
+ };
+
+ Fido2Login.prototype._getBodyHTML = function() {
+ const features = this.config.features;
+ const theme = this.config.theme;
+
+ let html = '';
+
+ html += `
+
+
+ `;
+
+ html += `
+
+
+
+
+ `;
+
+ html += `
+
+
+
+
+ `;
+
+ html += `
+
+
+
+ `;
+
+ html += `
`;
+
+ if (features.enablePasswordLogin) {
+ html += `
+
+ `;
+ }
+
+ if (features.showRemainingAttempts && this.mode === LoginMode.PASSWORD) {
+ const remaining = this.maxAttempts - this.attemptCount;
+ html += `
+
+
+ ${this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining)}
+
+
+ `;
+ }
+
+ return html;
+ };
+
+ Fido2Login.prototype._bindEvents = function() {
+ const self = this;
+ const container = this.modalElement || this.containerElement;
+ if (!container) return;
+
+ const mainBtn = container.querySelector('#fido2MainBtn');
+ if (mainBtn) {
+ mainBtn.addEventListener('click', () => {
+ if (self.mode === LoginMode.FIDO2) {
+ self._handleFido2Login();
+ } else {
+ self._handlePasswordLogin();
+ }
+ });
+ }
+
+ const toggleLink = container.querySelector('#fido2ToggleModeLink');
+ if (toggleLink) {
+ toggleLink.addEventListener('click', () => {
+ this._debugLog('[Fido2Login] Toggle link clicked, current mode:', this.mode);
+ self._toggleMode();
+ });
+ }
+
+ const userIdInput = container.querySelector('#fido2UserId');
+ if (userIdInput) {
+ userIdInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ if (self.mode === LoginMode.FIDO2) {
+ self._handleFido2Login();
+ } else {
+ self._handlePasswordLogin();
+ }
+ }
+ });
+ }
+
+ const passwordInput = container.querySelector('#fido2Password');
+ if (passwordInput) {
+ passwordInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ self._handlePasswordLogin();
+ }
+ });
+ }
+ };
+
+ Fido2Login.prototype._handleFido2Login = function() {
+ const userId = this._getUserIdInput();
+ if (!userId) {
+ this._showError(this.i18n.getText('msg_invalid_user_id'));
+ return;
+ }
+
+ this._hideError();
+ this._startLoading();
+
+ authenticateFido2(userId, this.config.rpId).then(result => {
+ this._stopLoading();
+ if (result.status === 'ok') {
+ this.state = LoginState.CLOSED;
+ this._emit('fido2Success', result.username, result.session);
+ this._closeUI();
+ } else {
+ this._emit('fido2Error', {
+ code: 'AUTH_FAILED',
+ message: result.errorMessage || this.i18n.getText('msg_fido2_failed'),
+ originalError: result
+ });
+ }
+ }).catch(error => {
+ this._stopLoading();
+ if (error.name === 'AbortError' || (error.message && error.message.toLowerCase().includes('canceled'))) {
+ this._emit('fido2Error', {
+ code: 'CANCELED',
+ message: this.i18n.getText('msg_fido2_canceled'),
+ originalError: error
+ });
+ } else {
+ this._emit('fido2Error', {
+ code: 'AUTH_FAILED',
+ message: error.message || this.i18n.getText('msg_fido2_failed'),
+ originalError: error
+ });
+ }
+ });
+ };
+
+ Fido2Login.prototype._handlePasswordLogin = function() {
+ const userId = this._getUserIdInput();
+ const password = this._getPasswordInput();
+
+ if (!userId) {
+ this._showError(this.i18n.getText('msg_invalid_user_id'));
+ return;
+ }
+
+ if (!password) {
+ this._showError(this.i18n.getText('msg_invalid_password'));
+ return;
+ }
+
+ this._hideError();
+ this._startLoading();
+
+ const passwordCallback = this.config.callbacks.onPasswordLogin;
+ if (typeof passwordCallback !== 'function') {
+ this._stopLoading();
+ console.error('onPasswordLogin callback is not defined');
+ return;
+ }
+
+ Promise.resolve(passwordCallback(userId, password)).then(success => {
+ this._stopLoading();
+ if (success) {
+ this.state = LoginState.CLOSED;
+ this._emit('fido2Success', userId, null);
+ this._closeUI();
+ } else {
+ this.attemptCount++;
+ if (this.attemptCount >= this.maxAttempts) {
+ this._closeUI();
+ this._emit('passwordExhausted', userId, this.attemptCount, this.maxAttempts);
+ } else {
+ this._updateRemainingAttempts();
+ const remaining = this.maxAttempts - this.attemptCount;
+ this._showError(this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining));
+ }
+ }
+ }).catch(error => {
+ this._stopLoading();
+ this.attemptCount++;
+ if (this.attemptCount >= this.maxAttempts) {
+ this._closeUI();
+ this._emit('passwordExhausted', userId, this.attemptCount, this.maxAttempts);
+ } else {
+ this._updateRemainingAttempts();
+ const remaining = this.maxAttempts - this.attemptCount;
+ this._showError(this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining));
+ }
+ });
+ };
+
+ Fido2Login.prototype._toggleMode = function() {
+ const container = this.modalElement || this.containerElement;
+ if (!container) return;
+
+ const passwordSection = container.querySelector('#fido2PasswordSection');
+ const mainBtn = container.querySelector('#fido2MainBtn');
+ const toggleLink = container.querySelector('#fido2ToggleModeLink');
+ const titleEl = document.getElementById('fido2LoginModalTitle');
+ const remainingEl = container.querySelector('#fido2RemainingAttempts');
+
+ if (this.mode === LoginMode.FIDO2) {
+ this.mode = LoginMode.PASSWORD;
+ if (passwordSection) passwordSection.style.display = 'block';
+ if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_password_login');
+ if (toggleLink) toggleLink.textContent = this.i18n.getText('link_use_fido2');
+ if (titleEl) titleEl.textContent = this.i18n.getText('title_password_login');
+
+ this.attemptCount = 0;
+ const remaining = this.maxAttempts - this.attemptCount;
+ if (remainingEl) {
+ remainingEl.textContent = this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining);
+ } else if (this.config.features.showRemainingAttempts) {
+ const remainingContainer = document.createElement('div');
+ remainingContainer.className = 'text-center mt-3';
+ remainingContainer.innerHTML = `${this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining)}`;
+ toggleLink.parentNode.parentNode.insertBefore(remainingContainer, toggleLink.parentNode.nextSibling);
+ }
+ } else {
+ this.mode = LoginMode.FIDO2;
+ if (passwordSection) passwordSection.style.display = 'none';
+ if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_fido2_login');
+ if (toggleLink) toggleLink.textContent = this.i18n.getText('link_use_password');
+ if (titleEl) titleEl.textContent = this.i18n.getText('title_login');
+
+ if (remainingEl) {
+ remainingEl.parentNode.remove();
+ }
+ }
+
+ this._hideError();
+ };
+
+ Fido2Login.prototype._getUserIdInput = function() {
+ const input = document.getElementById('fido2UserId');
+ return input ? input.value.trim() : '';
+ };
+
+ Fido2Login.prototype._getPasswordInput = function() {
+ const input = document.getElementById('fido2Password');
+ return input ? input.value : '';
+ };
+
+ Fido2Login.prototype._showError = function(message) {
+ const errorEl = document.getElementById('fido2LoginError');
+ const hintEl = document.getElementById('fido2LoginHint');
+ if (hintEl) hintEl.style.display = 'none';
+ if (errorEl) {
+ errorEl.textContent = message;
+ errorEl.style.display = 'block';
+ }
+ };
+
+ Fido2Login.prototype._hideError = function() {
+ const errorEl = document.getElementById('fido2LoginError');
+ if (errorEl) errorEl.style.display = 'none';
+ };
+
+ Fido2Login.prototype._showHint = function(message) {
+ const hintEl = document.getElementById('fido2LoginHint');
+ if (hintEl) {
+ hintEl.textContent = message;
+ hintEl.style.display = 'block';
+ }
+ };
+
+ Fido2Login.prototype._hideHint = function() {
+ const hintEl = document.getElementById('fido2LoginHint');
+ if (hintEl) hintEl.style.display = 'none';
+ };
+
+ Fido2Login.prototype._startLoading = function() {
+ const mainBtn = document.getElementById('fido2MainBtn');
+ if (mainBtn) {
+ mainBtn.disabled = true;
+ mainBtn.dataset.originalText = mainBtn.textContent;
+ mainBtn.textContent = '...';
+ }
+ };
+
+ Fido2Login.prototype._stopLoading = function() {
+ const mainBtn = document.getElementById('fido2MainBtn');
+ if (mainBtn) {
+ mainBtn.disabled = false;
+ mainBtn.textContent = mainBtn.dataset.originalText ||
+ (this.mode === LoginMode.FIDO2 ? this.i18n.getText('btn_fido2_login') : this.i18n.getText('btn_password_login'));
+ }
+ };
+
+ Fido2Login.prototype._updateRemainingAttempts = function() {
+ const container = this.modalElement || this.containerElement;
+ if (!container) return;
+
+ const remainingEl = container.querySelector('#fido2RemainingAttempts');
+ if (remainingEl) {
+ const remaining = this.maxAttempts - this.attemptCount;
+ remainingEl.textContent = this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining);
+ }
+ };
+
+ Fido2Login.prototype._emit = function(event, ...args) {
+ const callback = this.config.callbacks['on' + event.charAt(0).toUpperCase() + event.slice(1)];
+ if (typeof callback === 'function') {
+ callback.apply(this, args);
+ }
+ };
+
+ Fido2Login.prototype._closeUI = function() {
+ if (this.bootstrapModal) {
+ this.bootstrapModal.hide();
+ } else if (this.modalElement) {
+ const bootstrapModal = window.bootstrap.Modal.getInstance(this.modalElement);
+ if (bootstrapModal) {
+ bootstrapModal.hide();
+ }
+ }
+ };
+
+ Fido2Login.prototype.show = function() {
+ this._debugLog('[Fido2Login] show() called, initialized:', this.initialized);
+
+ if (this.initialized) {
+ this._debugLog('[Fido2Login] Already initialized, showing modal');
+ if (this.bootstrapModal) {
+ this.bootstrapModal.show();
+ }
+ return;
+ }
+
+ this._debugLog('[Fido2Login] Creating modal...');
+ this._createModal();
+ this._initAutoAuth();
+ this.initialized = true;
+
+ this._debugLog('[Fido2Login] Modal created, showing...');
+ if (this.bootstrapModal) {
+ this._debugLog('[Fido2Login] bootstrapModal.show() called');
+ this.bootstrapModal.show();
+ }
+ };
+
+ Fido2Login.prototype._initAutoAuth = function() {
+ const self = this;
+ const features = this.config.features;
+
+ this._debugLog('[Fido2Login] _initAutoAuth called');
+ this._debugLog('[Fido2Login] features.autoAuth:', features.autoAuth);
+ this._debugLog('[Fido2Login] canTryAutoAuthentication():', canTryAutoAuthentication());
+ this._debugLog('[Fido2Login] dfido2_lib_registered:', localStorage.getItem('dfido2_lib_registered'));
+
+ if (features.autoAuth && canTryAutoAuthentication()) {
+ this.state = LoginState.LOADING;
+ this._showHint(this.i18n.getText('msg_autofail_hint'));
+ this._debugLog('[Fido2Login] Starting auto Fido2 authentication...');
+
+ authenticateFido2(null, this.config.rpId).then(result => {
+ this._debugLog('[Fido2Login] authenticateFido2 result:', result);
+ this._hideHint();
+ if (result.status === 'ok') {
+ this.state = LoginState.CLOSED;
+ this._emit('fido2Success', result.username, result.session);
+ this._closeUI();
+ } else {
+ this.state = LoginState.FIDO2;
+ this._emit('fido2Error', {
+ code: 'AUTH_FAILED',
+ message: result.errorMessage || this.i18n.getText('msg_fido2_failed'),
+ originalError: result
+ });
+ this._updateUIForFido2Mode();
+ }
+ }).catch(error => {
+ this._debugLog('[Fido2Login] authenticateFido2 error:', error);
+ this._hideHint();
+ if (error.name === 'AbortError' || (error.message && error.message.toLowerCase().includes('canceled'))) {
+ this.state = LoginState.FIDO2;
+ this._emit('fido2Error', {
+ code: 'CANCELED',
+ message: this.i18n.getText('msg_fido2_canceled'),
+ originalError: error
+ });
+ } else {
+ this.state = LoginState.FIDO2;
+ this._emit('fido2Error', {
+ code: 'AUTH_FAILED',
+ message: error.message || this.i18n.getText('msg_fido2_failed'),
+ originalError: error
+ });
+ }
+ this._updateUIForFido2Mode();
+ });
+ } else if (!canTryAutoAuthentication()) {
+ this._debugLog('[Fido2Login] No registration, going to password mode');
+ if (features.enablePasswordLogin) {
+ this.state = LoginState.PASSWORD;
+ this.mode = LoginMode.PASSWORD;
+ this._updateUIForPasswordMode();
+ } else {
+ this.state = LoginState.FIDO2;
+ this._emit('fido2Error', {
+ code: 'NO_REGISTRATION',
+ message: this.i18n.getText('msg_no_registration'),
+ originalError: null
+ });
+ }
+ } else {
+ this._debugLog('[Fido2Login] autoAuth disabled or other condition, going to fido2 mode');
+ this.state = LoginState.FIDO2;
+ this._updateUIForFido2Mode();
+ }
+ };
+
+ Fido2Login.prototype._debugLog = function() {
+ const args = Array.prototype.slice.call(arguments);
+ const message = args.map(arg => {
+ if (typeof arg === 'object') {
+ try {
+ return JSON.stringify(arg);
+ } catch (e) {
+ return String(arg);
+ }
+ }
+ return String(arg);
+ }).join(' ');
+
+ if (typeof window.fido2LoginDebug === 'function') {
+ window.fido2LoginDebug(message);
+ }
+ console.log.apply(console, args);
+ };
+
+ Fido2Login.prototype._updateUIForFido2Mode = function() {
+ const container = this.modalElement || this.containerElement;
+ if (!container) return;
+
+ const passwordSection = container.querySelector('#fido2PasswordSection');
+ const mainBtn = container.querySelector('#fido2MainBtn');
+ const toggleLink = container.querySelector('#fido2ToggleModeLink');
+
+ if (passwordSection) passwordSection.style.display = 'none';
+ if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_fido2_login');
+ if (toggleLink && this.config.features.enablePasswordLogin) {
+ toggleLink.textContent = this.i18n.getText('link_use_password');
+ toggleLink.style.display = 'inline';
+ } else if (toggleLink) {
+ toggleLink.style.display = 'none';
+ }
+ };
+
+ Fido2Login.prototype._updateUIForPasswordMode = function() {
+ const container = this.modalElement || this.containerElement;
+ if (!container) return;
+
+ const passwordSection = container.querySelector('#fido2PasswordSection');
+ const mainBtn = container.querySelector('#fido2MainBtn');
+ const toggleLink = container.querySelector('#fido2ToggleModeLink');
+ const titleEl = document.getElementById('fido2LoginModalTitle');
+
+ if (passwordSection) passwordSection.style.display = 'block';
+ if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_password_login');
+ if (toggleLink) toggleLink.textContent = this.i18n.getText('link_use_fido2');
+ if (titleEl) titleEl.textContent = this.i18n.getText('title_password_login');
+
+ this._updateRemainingAttempts();
+ };
+
+ Fido2Login.prototype.hide = function() {
+ this._closeUI();
+ };
+
+ Fido2Login.prototype.destroy = function() {
+ this.state = LoginState.CLOSED;
+ if (this.bootstrapModal) {
+ this.bootstrapModal.dispose();
+ }
+ this.cleanup();
+ };
+
+ Fido2Login.prototype.cleanup = function() {
+ if (this.modalElement) {
+ this.themeManager.cleanup(this.modalElement);
+ if (this.modalElement.parentNode) {
+ this.modalElement.parentNode.removeChild(this.modalElement);
+ }
+ this.modalElement = null;
+ }
+ this.containerElement = null;
+ this.bootstrapModal = null;
+ };
+
+ Fido2Login.prototype.setMode = function(mode) {
+ if (mode === LoginMode.PASSWORD && this.mode !== LoginMode.PASSWORD) {
+ this.mode = LoginMode.PASSWORD;
+ this.attemptCount = 0;
+ this._updateUIForPasswordMode();
+ } else if (mode === LoginMode.FIDO2 && this.mode !== LoginMode.FIDO2) {
+ this.mode = LoginMode.FIDO2;
+ this._updateUIForFido2Mode();
+ }
+ };
+
+ Fido2Login.prototype.getUserId = function() {
+ return this._getUserIdInput();
+ };
+
+ Fido2Login.prototype.getState = function() {
+ return this.state;
+ };
+
+ Fido2Login.prototype.getMode = function() {
+ return this.mode;
+ };
+
+ Fido2Login.prototype.getAttemptCount = function() {
+ return this.attemptCount;
+ };
+
+ Fido2Login.prototype.getRemainingAttempts = function() {
+ return this.maxAttempts - this.attemptCount;
+ };
+
+ Fido2Login.prototype.resetPasswordAttempts = function() {
+ this.attemptCount = 0;
+ this._updateRemainingAttempts();
+ };
+
function I18nManager(config) {
this.config = config;
this.messages = new Map();
@@ -240,6 +1224,35 @@
this.sessionStatus = false;
}
+ DeviceManager.prototype.getSessionUserId = function() {
+ try {
+ const sessionText = sessionStorage.getItem('fido2_user_session');
+ if (!sessionText) return null;
+ const sessionData = JSON.parse(sessionText);
+ return sessionData.uid || null;
+ } catch (error) {
+ return null;
+ }
+ };
+
+ DeviceManager.prototype.getEffectiveUserId = function() {
+ const sessionUserId = this.getSessionUserId();
+ if (sessionUserId) {
+ return sessionUserId;
+ }
+ return this.config.userId || null;
+ };
+
+ DeviceManager.prototype.validateUserId = function() {
+ const sessionUserId = this.getSessionUserId();
+ const inputUserId = this.config.userId;
+
+ if (sessionUserId && inputUserId && sessionUserId !== inputUserId) {
+ return { valid: false, error: 'User ID mismatch: session user is ' + sessionUserId };
+ }
+ return { valid: true };
+ };
+
DeviceManager.prototype.loadDevices = async function() {
try {
const result = await listUserDevicesFido2(this.config.rpId);
@@ -259,13 +1272,17 @@
DeviceManager.prototype.addDevice = async function(displayName) {
try {
- const sessionData = this.getSessionData();
- if (!sessionData) {
- throw new Error('No session data available');
+ const effectiveUserId = this.getEffectiveUserId();
+ if (!effectiveUserId) {
+ throw new Error('No user ID available. Please login first.');
}
- const userId = sessionData.uid;
- const result = await registerFido2(userId, displayName || 'Device-' + userId, this.config.rpId);
+ const validation = this.validateUserId();
+ if (!validation.valid) {
+ throw new Error(validation.error);
+ }
+
+ const result = await registerFido2(effectiveUserId, displayName || 'Device-' + effectiveUserId, this.config.rpId);
if (result.status === 'ok') {
await this.loadDevices();
@@ -282,6 +1299,16 @@
DeviceManager.prototype.deleteDevice = async function(deviceId) {
try {
+ const effectiveUserId = this.getEffectiveUserId();
+ if (!effectiveUserId) {
+ throw new Error('No user ID available. Please login first.');
+ }
+
+ const validation = this.validateUserId();
+ if (!validation.valid) {
+ throw new Error(validation.error);
+ }
+
const result = await delUserDeviceFido2(deviceId, this.config.rpId);
if (result.status === 'ok') {
@@ -297,27 +1324,31 @@
}
};
- DeviceManager.prototype.getSessionData = function() {
- try {
- const sessionText = sessionStorage.getItem('fido2_user_session');
- if (!sessionText) return null;
-
- return JSON.parse(sessionText);
- } catch (error) {
- return null;
- }
- };
-
DeviceManager.prototype.getUserId = function() {
- const sessionData = this.getSessionData();
- return sessionData ? sessionData.uid : null;
+ return this.getEffectiveUserId();
};
DeviceManager.prototype.checkSession = async function() {
try {
- this.sessionStatus = await validSession(this.config.rpId);
- this.eventManager.emit('sessionStatusChanged', this.sessionStatus);
- return this.sessionStatus;
+ const sessionUserId = this.getSessionUserId();
+
+ if (!sessionUserId) {
+ this.sessionStatus = false;
+ this.eventManager.emit('sessionStatusChanged', false);
+ return false;
+ }
+
+ const validation = this.validateUserId();
+ if (!validation.valid) {
+ this.sessionStatus = false;
+ this.eventManager.emit('sessionStatusChanged', false);
+ this.eventManager.emit('userMismatch', validation.error);
+ return false;
+ }
+
+ this.sessionStatus = true;
+ this.eventManager.emit('sessionStatusChanged', true);
+ return true;
} catch (error) {
this.sessionStatus = false;
this.eventManager.emit('sessionStatusChanged', false);
@@ -444,7 +1475,8 @@
UIRenderer.prototype._getBodyHTML = function() {
const features = this.config.features;
- const userId = window.Fido2UIManager.deviceManager ? window.Fido2UIManager.deviceManager.getUserId() : '';
+ const dm = window.Fido2UIManager ? window.Fido2UIManager.deviceManager : null;
+ const userId = dm ? dm.getEffectiveUserId() : (this.config.userId || '');
let html = '';
@@ -454,13 +1486,6 @@
`;
}
- html += `
- ${this.i18n.getText('msg_session_invalid')}
-
-
`;
-
if (features.showAddButton) {
html += `