Improved UI SDk and modal demo

This commit is contained in:
dqj
2026-01-18 21:48:19 +09:00
parent 9bbbae8dd2
commit 055d59f979
7 changed files with 373 additions and 167 deletions

View File

@@ -46,7 +46,6 @@
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. --> <!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
<script src="files/config.js"></script> <script src="files/config.js"></script>
<script src="files/jquery.js"></script>
<script src="files/popper.js"></script> <script src="files/popper.js"></script>
<script src="files/bootstrap.js"></script> <script src="files/bootstrap.js"></script>
<script src="files/perfect-scrollbar.js"></script> <script src="files/perfect-scrollbar.js"></script>
@@ -169,27 +168,25 @@
window.location.href = "login.html"; window.location.href = "login.html";
} }
} }
$('#user_id').html(user_id); document.getElementById('user_id').textContent = user_id;
listDevices(); listDevices();
} }
async function setSessionStatus(){ async function setSessionStatus(){
const sessionOk = await validSession(); const sessionOk = await validSession();
const sessionStatusEl = document.getElementById('session_status');
if(sessionOk){ if(sessionOk){
$('#session_status').html(getI18NText(i18n_messages, 'msg_session_status_ok')) sessionStatusEl.textContent = getI18NText(i18n_messages, 'msg_session_status_ok');
}else{ }else{
$('#session_status').html(getI18NText(i18n_messages, 'msg_session_status_fail')) sessionStatusEl.textContent = getI18NText(i18n_messages, 'msg_session_status_fail');
} }
} }
async function addDevice(display_name){ async function addDevice(display_name){
//You can change the display name or ask the user to input it, if the display_name is null.
const result = await registerFido2(user_id, display_name?display_name:'dis_'+user_id); const result = await registerFido2(user_id, display_name?display_name:'dis_'+user_id);
if(result.status === 'ok'){ if(result.status === 'ok'){
//You can try to insert user_id into your user db now if this user_id does not exist in your db. alert(document.getElementById('msg_register_ok').textContent);
alert($('#msg_register_ok').html());
listDevices(); listDevices();
}else{ }else{
errProcessFido2(result) errProcessFido2(result)
@@ -213,17 +210,17 @@
async function listDevices(){ async function listDevices(){
const result = await listUserDevicesFido2() const result = await listUserDevicesFido2()
$("#devices_list").html(""); const devicesListEl = document.getElementById('devices_list');
devicesListEl.innerHTML = "";
if("ok" === result.status){ if("ok" === result.status){
let lst_html = ""
for(let dev of result.devices){ for(let dev of result.devices){
var dev_desc = dev.desc var dev_desc = dev.desc
if(!dev_desc || 0 === dev_desc.length){ if(!dev_desc || 0 === dev_desc.length){
let parser = new UAParser(dev.userAgent); const parser = new UAParser(dev.userAgent);
const osName = parser.getOS().name;
if(parser.getOS().name){ if(osName){
dev_desc = parser.getDevice().model + ',' + parser.getOS().name + ',' + parser.getBrowser().name; dev_desc = parser.getDevice().model + ',' + osName + ',' + parser.getBrowser().name;
}else{ }else{
dev_desc = dev.userAgent; dev_desc = dev.userAgent;
} }
@@ -231,14 +228,35 @@
var date = new Date(dev.registered_time); var date = new Date(dev.registered_time);
lst_html += '<tr><td><strong>'+ dev_desc +'</strong></td><td>'+ const row = document.createElement('tr');
date.toLocaleString()+'</td><td>'+
"<a style=\"padding-left: 0px;\" class=\"dropdown-item\" href=\"javascript:delDevice('"+dev.device_id+"');\">"+ const tdDevice = document.createElement('td');
'<i class="bx bx-trash me-1"></i><span id="title_del"> '+ getI18NText(i18n_messages, 'title_del') +'</span></a></div></td></tr>' const strong = document.createElement('strong');
strong.textContent = dev_desc;
tdDevice.appendChild(strong);
const tdTime = document.createElement('td');
tdTime.textContent = date.toLocaleString();
const tdAction = document.createElement('td');
const link = document.createElement('a');
link.className = 'dropdown-item';
link.href = "javascript:delDevice('" + dev.device_id + "');";
const icon = document.createElement('i');
icon.className = 'bx bx-trash me-1';
const span = document.createElement('span');
span.textContent = ' ' + getI18NText(i18n_messages, 'title_del');
link.appendChild(icon);
link.appendChild(span);
tdAction.appendChild(link);
row.appendChild(tdDevice);
row.appendChild(tdTime);
row.appendChild(tdAction);
devicesListEl.appendChild(row);
} }
$("#devices_list").html(lst_html);
}else{ }else{
const msg = getI18NErrorMessage(result.errorMessage); //fido2LibErrMsgLanguages.japanese const msg = getI18NErrorMessage(result.errorMessage);
alert(msg?msg:result.errorMessage); alert(msg?msg:result.errorMessage);
} }
} }

View File

@@ -1,12 +1,12 @@
/** /**
* amiPro utils * amiPro utils - jQuery-free version
*/ */
'use strict'; 'use strict';
function setI18NText(i18n_map){ function setI18NText(i18n_map){
for (const key of i18n_map.keys()) { for (const key of i18n_map.keys()) {
const elm = $("#"+key); const elm = document.getElementById(key);
if(elm){ if(elm){
const lang = window.navigator.language; const lang = window.navigator.language;
var elem = i18n_map.get(key) var elem = i18n_map.get(key)
@@ -17,7 +17,7 @@ function setI18NText(i18n_map){
} }
if(!msg)msg = key+"-"+lang if(!msg)msg = key+"-"+lang
$("#"+key).html(msg); elm.textContent = msg;
} }
} }
} }

View File

@@ -2,6 +2,8 @@ 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'
let configuredServerUrl = null;
/** ===APIs=== */ /** ===APIs=== */
if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)){ if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)){
@@ -9,9 +11,25 @@ if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)){
} }
function setFidoServerURL(url){ function setFidoServerURL(url){
if (!url || !url.startsWith('https://')) {
throw new Error('serverUrl must be a valid HTTPS URL');
}
configuredServerUrl = url;
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL, url); localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL, url);
} }
function getServerUrl() {
if (configuredServerUrl) {
return configuredServerUrl;
}
const stored = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL);
if (stored) {
configuredServerUrl = stored;
return stored;
}
return 'https://fido2.amipro.me';
}
function canTryAutoAuthentication(){ function canTryAutoAuthentication(){
//const session_text = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION) //const session_text = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
//alert('canTryAuth:'+session_text+"|"+(null != localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED))) //alert('canTryAuth:'+session_text+"|"+(null != localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED)))
@@ -67,7 +85,7 @@ async function listUserDevicesFido2(rpId = null) {
req.rp = { id: rpId }; req.rp = { id: rpId };
} }
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/dvc/lst", { const response = await fetch(getServerUrl() + "/usr/dvc/lst", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -104,7 +122,7 @@ async function delUserDeviceFido2(device_id, rpId = null) {
req.rp = { id: rpId }; req.rp = { id: rpId };
} }
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/dvc/rm", { const response = await fetch(getServerUrl() + "/usr/dvc/rm", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -154,7 +172,7 @@ async function validSession(rpId = null) {
req.rp = { id: rpId }; req.rp = { id: rpId };
} }
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/validsession", { const response = await fetch(getServerUrl() + "/usr/validsession", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -177,7 +195,7 @@ async function logoutFido2UserSession(){
const session_data = JSON.parse(session_text) const session_data = JSON.parse(session_text)
let req = {session: session_data['session'], username: session_data['uid']} let req = {session: session_data['session'], username: session_data['uid']}
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/delsession", { const response = await fetch(getServerUrl() + "/usr/delsession", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -192,7 +210,7 @@ async function logoutFido2UserSession(){
async function getRegistrationUser(reg_session_id){ async function getRegistrationUser(reg_session_id){
try { try {
let req = {session_id: reg_session_id} let req = {session_id: reg_session_id}
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/reg/username", { const response = await fetch(getServerUrl() + "/reg/username", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -410,7 +428,7 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
attestationOptions.rp = { id: rpId } attestationOptions.rp = { id: rpId }
} }
const svrUrl = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) const svrUrl = getServerUrl()
const response = await fetch(svrUrl + "/attestation/options", { const response = await fetch(svrUrl + "/attestation/options", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
@@ -445,7 +463,7 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
attResult.transports = res.response.getTransports(); attResult.transports = res.response.getTransports();
} }
const result = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/attestation/result", { const result = await fetch(getServerUrl() + "/attestation/result", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -524,7 +542,7 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
authnOptions.rp = { id: rpId }; authnOptions.rp = { id: rpId };
} }
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/assertion/options", { const response = await fetch(getServerUrl() + "/assertion/options", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {
@@ -566,7 +584,7 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
userHandle: _toBase64URL(btoa(_bufferToString(cred.response.userHandle))) //_toBase64URL(btoa(_bufferToString(cred.response.userHandle))) userHandle: _toBase64URL(btoa(_bufferToString(cred.response.userHandle))) //_toBase64URL(btoa(_bufferToString(cred.response.userHandle)))
} }
}; };
const res = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/assertion/result", { const res = await fetch(getServerUrl() + "/assertion/result", {
method: "POST", method: "POST",
cache: "no-cache", cache: "no-cache",
headers: { headers: {

View File

@@ -1,6 +1,19 @@
(function(window) { (function(window) {
'use strict'; 'use strict';
// Check required dependencies
if (typeof authenticateFido2 === 'undefined' ||
typeof registerFido2 === 'undefined' ||
typeof listUserDevicesFido2 === 'undefined' ||
typeof delUserDeviceFido2 === 'undefined' ||
typeof setFidoServerURL === 'undefined' ||
typeof logoutFido2UserSession === 'undefined') {
throw new Error(
'FIDO2 UI SDK requires dfido2-lib.js to be loaded first. ' +
'Please add: <script src="files/dfido2-lib.js"></script> before fido2-ui-sdk.js'
);
}
const FIDO2_UI_VERSION = '1.0.0'; const FIDO2_UI_VERSION = '1.0.0';
const DEFAULT_CONFIG = { const DEFAULT_CONFIG = {
@@ -459,8 +472,8 @@
showRemainingAttempts: true, showRemainingAttempts: true,
}, },
callbacks: { callbacks: {
onFido2Success: null, onLoginSuccess: null,
onFido2Error: null, onLoginError: null,
onPasswordLogin: null, onPasswordLogin: null,
onPasswordExhausted: null, onPasswordExhausted: null,
onLoginClosed: null, onLoginClosed: null,
@@ -513,18 +526,30 @@
container.innerHTML = ''; container.innerHTML = '';
const uniqueId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
const modalId = 'fido2LoginModal_' + uniqueId;
const titleId = 'fido2LoginModalTitle_' + uniqueId;
const errorId = 'fido2LoginError_' + uniqueId;
const hintId = 'fido2LoginHint_' + uniqueId;
const userIdId = 'fido2UserId_' + uniqueId;
const passwordId = 'fido2Password_' + uniqueId;
const passwordSectionId = 'fido2PasswordSection_' + uniqueId;
const mainBtnId = 'fido2MainBtn_' + uniqueId;
const toggleLinkId = 'fido2ToggleModeLink_' + uniqueId;
const modal = document.createElement('div'); const modal = document.createElement('div');
modal.className = 'modal fade fido2-sdk-login-modal'; modal.className = 'modal fade fido2-sdk-login-modal';
modal.id = 'fido2LoginModal'; modal.id = modalId;
modal.tabIndex = -1; modal.tabIndex = -1;
modal.setAttribute('aria-hidden', 'true'); modal.setAttribute('aria-hidden', 'true');
modal.innerHTML = this._getModalHTML(); modal.innerHTML = this._getModalHTML(uniqueId, titleId, errorId, hintId, userIdId, passwordId, passwordSectionId, mainBtnId, toggleLinkId);
container.appendChild(modal); container.appendChild(modal);
this.modalElement = modal; this.modalElement = modal;
this.containerElement = container; this.containerElement = container;
this._uniqueId = uniqueId;
this._debugLog('[Fido2Login] Modal element created'); this._debugLog('[Fido2Login] Modal element created with ID:', modalId);
this.themeManager.applyTheme(modal); this.themeManager.applyTheme(modal);
@@ -545,7 +570,7 @@
return modal; return modal;
}; };
Fido2Login.prototype._getModalHTML = function() { Fido2Login.prototype._getModalHTML = function(uniqueId, titleId, errorId, hintId, userIdId, passwordId, passwordSectionId, mainBtnId, toggleLinkId) {
const theme = this.config.theme; const theme = this.config.theme;
const features = this.config.features; const features = this.config.features;
@@ -554,45 +579,45 @@
<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 me-2" alt="Logo">` : ''} ${theme.logo ? `<img src="${theme.logo}" class="fido2-sdk-logo me-2" alt="Logo">` : ''}
<h5 class="modal-title fido2-sdk-text" id="fido2LoginModalTitle">${this.i18n.getText('title_login')}</h5> <h5 class="modal-title fido2-sdk-text" id="${titleId}">${this.i18n.getText('title_login')}</h5>
<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()} ${this._getBodyHTML(uniqueId, errorId, hintId, userIdId, passwordId, passwordSectionId, mainBtnId, toggleLinkId)}
</div> </div>
</div> </div>
</div> </div>
`; `;
}; };
Fido2Login.prototype._getBodyHTML = function() { Fido2Login.prototype._getBodyHTML = function(uniqueId, errorId, hintId, userIdId, passwordId, passwordSectionId, mainBtnId, toggleLinkId) {
const features = this.config.features; const features = this.config.features;
const theme = this.config.theme; const theme = this.config.theme;
let html = ''; let html = '';
html += ` html += `
<div id="fido2LoginError" class="alert alert-danger mb-3" style="display:none;"></div> <div id="${errorId}" class="alert alert-danger mb-3" style="display:none;"></div>
<div id="fido2LoginHint" class="alert alert-info mb-3" style="display:none;"></div> <div id="${hintId}" class="alert alert-info mb-3" style="display:none;"></div>
`; `;
html += ` html += `
<div class="mb-3"> <div class="mb-3">
<label for="fido2UserId" class="form-label fido2-sdk-text">${this.i18n.getText('placeholder_user_id')}</label> <label for="${userIdId}" class="form-label fido2-sdk-text">${this.i18n.getText('placeholder_user_id')}</label>
<input type="text" class="form-control" id="fido2UserId" placeholder="${this.i18n.getText('placeholder_user_id')}"> <input type="text" class="form-control" id="${userIdId}" placeholder="${this.i18n.getText('placeholder_user_id')}">
</div> </div>
`; `;
html += ` html += `
<div class="mb-3" id="fido2PasswordSection" style="display:none;"> <div class="mb-3" id="${passwordSectionId}" style="display:none;">
<label for="fido2Password" class="form-label fido2-sdk-text">${this.i18n.getText('placeholder_password')}</label> <label for="${passwordId}" class="form-label fido2-sdk-text">${this.i18n.getText('placeholder_password')}</label>
<input type="password" class="form-control" id="fido2Password" placeholder="${this.i18n.getText('placeholder_password')}"> <input type="password" class="form-control" id="${passwordId}" placeholder="${this.i18n.getText('placeholder_password')}">
</div> </div>
`; `;
html += ` html += `
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<button type="button" class="btn btn-primary fido2-sdk-btn fido2-sdk-btn-primary" id="fido2MainBtn"> <button type="button" class="btn btn-primary fido2-sdk-btn fido2-sdk-btn-primary" id="${mainBtnId}">
${this.i18n.getText('btn_fido2_login')} ${this.i18n.getText('btn_fido2_login')}
</button> </button>
</div> </div>
@@ -603,7 +628,7 @@
if (features.enablePasswordLogin) { if (features.enablePasswordLogin) {
html += ` html += `
<div class="text-center"> <div class="text-center">
<a href="javascript:void(0)" class="fido2-sdk-link" id="fido2ToggleModeLink">${this.i18n.getText('link_use_password')}</a> <a href="javascript:void(0)" class="fido2-sdk-link" id="${toggleLinkId}">${this.i18n.getText('link_use_password')}</a>
</div> </div>
`; `;
} }
@@ -612,7 +637,7 @@
const remaining = this.maxAttempts - this.attemptCount; const remaining = this.maxAttempts - this.attemptCount;
html += ` html += `
<div class="text-center mt-3"> <div class="text-center mt-3">
<small class="text-muted fido2-sdk-text" id="fido2RemainingAttempts"> <small class="text-muted fido2-sdk-text" id="fido2RemainingAttempts_${uniqueId}">
${this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining)} ${this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining)}
</small> </small>
</div> </div>
@@ -627,7 +652,7 @@
const container = this.modalElement || this.containerElement; const container = this.modalElement || this.containerElement;
if (!container) return; if (!container) return;
const mainBtn = container.querySelector('#fido2MainBtn'); const mainBtn = container.querySelector('[id^="fido2MainBtn_"]');
if (mainBtn) { if (mainBtn) {
mainBtn.addEventListener('click', () => { mainBtn.addEventListener('click', () => {
if (self.mode === LoginMode.FIDO2) { if (self.mode === LoginMode.FIDO2) {
@@ -638,15 +663,15 @@
}); });
} }
const toggleLink = container.querySelector('#fido2ToggleModeLink'); const toggleLink = container.querySelector('[id^="fido2ToggleModeLink_"]');
if (toggleLink) { if (toggleLink) {
toggleLink.addEventListener('click', () => { toggleLink.addEventListener('click', () => {
this._debugLog('[Fido2Login] Toggle link clicked, current mode:', this.mode); this._debugLog('[Fido2Login] Toggle link clicked, current mode:', self.mode);
self._toggleMode(); self._toggleMode();
}); });
} }
const userIdInput = container.querySelector('#fido2UserId'); const userIdInput = container.querySelector('[id^="fido2UserId_"]');
if (userIdInput) { if (userIdInput) {
userIdInput.addEventListener('keypress', (e) => { userIdInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
@@ -659,7 +684,7 @@
}); });
} }
const passwordInput = container.querySelector('#fido2Password'); const passwordInput = container.querySelector('[id^="fido2Password_"]');
if (passwordInput) { if (passwordInput) {
passwordInput.addEventListener('keypress', (e) => { passwordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
@@ -683,10 +708,15 @@
this._stopLoading(); this._stopLoading();
if (result.status === 'ok') { if (result.status === 'ok') {
this.state = LoginState.CLOSED; this.state = LoginState.CLOSED;
this._emit('fido2Success', result.username, result.session); this._emit('fido2Success', {
method: 'fido2',
username: result.username,
session: result.session
});
this._closeUI(); this._closeUI();
} else { } else {
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'AUTH_FAILED', code: 'AUTH_FAILED',
message: result.errorMessage || this.i18n.getText('msg_fido2_failed'), message: result.errorMessage || this.i18n.getText('msg_fido2_failed'),
originalError: result originalError: result
@@ -696,12 +726,14 @@
this._stopLoading(); this._stopLoading();
if (error.name === 'AbortError' || (error.message && error.message.toLowerCase().includes('canceled'))) { if (error.name === 'AbortError' || (error.message && error.message.toLowerCase().includes('canceled'))) {
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'CANCELED', code: 'CANCELED',
message: this.i18n.getText('msg_fido2_canceled'), message: this.i18n.getText('msg_fido2_canceled'),
originalError: error originalError: error
}); });
} else { } else {
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'AUTH_FAILED', code: 'AUTH_FAILED',
message: error.message || this.i18n.getText('msg_fido2_failed'), message: error.message || this.i18n.getText('msg_fido2_failed'),
originalError: error originalError: error
@@ -727,6 +759,11 @@
this._hideError(); this._hideError();
this._startLoading(); this._startLoading();
// Clear any existing FIDO2 session before password login
if (typeof logoutFido2UserSession === 'function') {
logoutFido2UserSession();
}
const passwordCallback = this.config.callbacks.onPasswordLogin; const passwordCallback = this.config.callbacks.onPasswordLogin;
if (typeof passwordCallback !== 'function') { if (typeof passwordCallback !== 'function') {
this._stopLoading(); this._stopLoading();
@@ -738,7 +775,11 @@
this._stopLoading(); this._stopLoading();
if (success) { if (success) {
this.state = LoginState.CLOSED; this.state = LoginState.CLOSED;
this._emit('fido2Success', userId, null); this._emit('fido2Success', {
method: 'password',
username: userId,
session: null
});
this._closeUI(); this._closeUI();
} else { } else {
this.attemptCount++; this.attemptCount++;
@@ -769,11 +810,11 @@
const container = this.modalElement || this.containerElement; const container = this.modalElement || this.containerElement;
if (!container) return; if (!container) return;
const passwordSection = container.querySelector('#fido2PasswordSection'); const passwordSection = container.querySelector('[id^="fido2PasswordSection_"]');
const mainBtn = container.querySelector('#fido2MainBtn'); const mainBtn = container.querySelector('[id^="fido2MainBtn_"]');
const toggleLink = container.querySelector('#fido2ToggleModeLink'); const toggleLink = container.querySelector('[id^="fido2ToggleModeLink_"]');
const titleEl = document.getElementById('fido2LoginModalTitle'); const titleEl = container.querySelector('[id^="fido2LoginModalTitle_"]');
const remainingEl = container.querySelector('#fido2RemainingAttempts'); const remainingEl = container.querySelector('[id^="fido2RemainingAttempts_"]');
if (this.mode === LoginMode.FIDO2) { if (this.mode === LoginMode.FIDO2) {
this.mode = LoginMode.PASSWORD; this.mode = LoginMode.PASSWORD;
@@ -789,7 +830,7 @@
} else if (this.config.features.showRemainingAttempts) { } else if (this.config.features.showRemainingAttempts) {
const remainingContainer = document.createElement('div'); const remainingContainer = document.createElement('div');
remainingContainer.className = 'text-center mt-3'; remainingContainer.className = 'text-center mt-3';
remainingContainer.innerHTML = `<small class="text-muted fido2-sdk-text" id="fido2RemainingAttempts">${this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining)}</small>`; remainingContainer.innerHTML = `<small class="text-muted fido2-sdk-text" id="fido2RemainingAttempts_${this._uniqueId}">${this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining)}</small>`;
toggleLink.parentNode.parentNode.insertBefore(remainingContainer, toggleLink.parentNode.nextSibling); toggleLink.parentNode.parentNode.insertBefore(remainingContainer, toggleLink.parentNode.nextSibling);
} }
} else { } else {
@@ -808,18 +849,21 @@
}; };
Fido2Login.prototype._getUserIdInput = function() { Fido2Login.prototype._getUserIdInput = function() {
const input = document.getElementById('fido2UserId'); const container = this.modalElement || this.containerElement;
const input = container ? container.querySelector('[id^="fido2UserId_"]') : null;
return input ? input.value.trim() : ''; return input ? input.value.trim() : '';
}; };
Fido2Login.prototype._getPasswordInput = function() { Fido2Login.prototype._getPasswordInput = function() {
const input = document.getElementById('fido2Password'); const container = this.modalElement || this.containerElement;
const input = container ? container.querySelector('[id^="fido2Password_"]') : null;
return input ? input.value : ''; return input ? input.value : '';
}; };
Fido2Login.prototype._showError = function(message) { Fido2Login.prototype._showError = function(message) {
const errorEl = document.getElementById('fido2LoginError'); const container = this.modalElement || this.containerElement;
const hintEl = document.getElementById('fido2LoginHint'); const errorEl = container ? container.querySelector('[id^="fido2LoginError_"]') : null;
const hintEl = container ? container.querySelector('[id^="fido2LoginHint_"]') : null;
if (hintEl) hintEl.style.display = 'none'; if (hintEl) hintEl.style.display = 'none';
if (errorEl) { if (errorEl) {
errorEl.textContent = message; errorEl.textContent = message;
@@ -828,12 +872,14 @@
}; };
Fido2Login.prototype._hideError = function() { Fido2Login.prototype._hideError = function() {
const errorEl = document.getElementById('fido2LoginError'); const container = this.modalElement || this.containerElement;
const errorEl = container ? container.querySelector('[id^="fido2LoginError_"]') : null;
if (errorEl) errorEl.style.display = 'none'; if (errorEl) errorEl.style.display = 'none';
}; };
Fido2Login.prototype._showHint = function(message) { Fido2Login.prototype._showHint = function(message) {
const hintEl = document.getElementById('fido2LoginHint'); const container = this.modalElement || this.containerElement;
const hintEl = container ? container.querySelector('[id^="fido2LoginHint_"]') : null;
if (hintEl) { if (hintEl) {
hintEl.textContent = message; hintEl.textContent = message;
hintEl.style.display = 'block'; hintEl.style.display = 'block';
@@ -841,12 +887,14 @@
}; };
Fido2Login.prototype._hideHint = function() { Fido2Login.prototype._hideHint = function() {
const hintEl = document.getElementById('fido2LoginHint'); const container = this.modalElement || this.containerElement;
const hintEl = container ? container.querySelector('[id^="fido2LoginHint_"]') : null;
if (hintEl) hintEl.style.display = 'none'; if (hintEl) hintEl.style.display = 'none';
}; };
Fido2Login.prototype._startLoading = function() { Fido2Login.prototype._startLoading = function() {
const mainBtn = document.getElementById('fido2MainBtn'); const container = this.modalElement || this.containerElement;
const mainBtn = container ? container.querySelector('[id^="fido2MainBtn_"]') : null;
if (mainBtn) { if (mainBtn) {
mainBtn.disabled = true; mainBtn.disabled = true;
mainBtn.dataset.originalText = mainBtn.textContent; mainBtn.dataset.originalText = mainBtn.textContent;
@@ -855,7 +903,8 @@
}; };
Fido2Login.prototype._stopLoading = function() { Fido2Login.prototype._stopLoading = function() {
const mainBtn = document.getElementById('fido2MainBtn'); const container = this.modalElement || this.containerElement;
const mainBtn = container ? container.querySelector('[id^="fido2MainBtn_"]') : null;
if (mainBtn) { if (mainBtn) {
mainBtn.disabled = false; mainBtn.disabled = false;
mainBtn.textContent = mainBtn.dataset.originalText || mainBtn.textContent = mainBtn.dataset.originalText ||
@@ -867,17 +916,24 @@
const container = this.modalElement || this.containerElement; const container = this.modalElement || this.containerElement;
if (!container) return; if (!container) return;
const remainingEl = container.querySelector('#fido2RemainingAttempts'); const remainingEl = container.querySelector('[id^="fido2RemainingAttempts_"]');
if (remainingEl) { if (remainingEl) {
const remaining = this.maxAttempts - this.attemptCount; const remaining = this.maxAttempts - this.attemptCount;
remainingEl.textContent = this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining); remainingEl.textContent = this.i18n.getText('msg_remaining_attempts').replace('{n}', remaining);
} }
}; };
Fido2Login.prototype._emit = function(event, ...args) { Fido2Login.prototype._emit = function(event, data) {
const callback = this.config.callbacks['on' + event.charAt(0).toUpperCase() + event.slice(1)]; const callbackName = 'on' + event.charAt(0).toUpperCase() + event.slice(1);
const callback = this.config.callbacks[callbackName];
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback.apply(this, args); if (event === 'fido2Success') {
callback(data.username, data.session, data);
} else if (event === 'fido2Error') {
callback(data, data.originalError);
} else {
callback(data);
}
} }
}; };
@@ -934,11 +990,16 @@
this._hideHint(); this._hideHint();
if (result.status === 'ok') { if (result.status === 'ok') {
this.state = LoginState.CLOSED; this.state = LoginState.CLOSED;
this._emit('fido2Success', result.username, result.session); this._emit('fido2Success', {
method: 'fido2',
username: result.username,
session: result.session
});
this._closeUI(); this._closeUI();
} else { } else {
this.state = LoginState.FIDO2; this.state = LoginState.FIDO2;
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'AUTH_FAILED', code: 'AUTH_FAILED',
message: result.errorMessage || this.i18n.getText('msg_fido2_failed'), message: result.errorMessage || this.i18n.getText('msg_fido2_failed'),
originalError: result originalError: result
@@ -951,6 +1012,7 @@
if (error.name === 'AbortError' || (error.message && error.message.toLowerCase().includes('canceled'))) { if (error.name === 'AbortError' || (error.message && error.message.toLowerCase().includes('canceled'))) {
this.state = LoginState.FIDO2; this.state = LoginState.FIDO2;
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'CANCELED', code: 'CANCELED',
message: this.i18n.getText('msg_fido2_canceled'), message: this.i18n.getText('msg_fido2_canceled'),
originalError: error originalError: error
@@ -958,6 +1020,7 @@
} else { } else {
this.state = LoginState.FIDO2; this.state = LoginState.FIDO2;
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'AUTH_FAILED', code: 'AUTH_FAILED',
message: error.message || this.i18n.getText('msg_fido2_failed'), message: error.message || this.i18n.getText('msg_fido2_failed'),
originalError: error originalError: error
@@ -974,6 +1037,7 @@
} else { } else {
this.state = LoginState.FIDO2; this.state = LoginState.FIDO2;
this._emit('fido2Error', { this._emit('fido2Error', {
method: 'fido2',
code: 'NO_REGISTRATION', code: 'NO_REGISTRATION',
message: this.i18n.getText('msg_no_registration'), message: this.i18n.getText('msg_no_registration'),
originalError: null originalError: null
@@ -1009,9 +1073,9 @@
const container = this.modalElement || this.containerElement; const container = this.modalElement || this.containerElement;
if (!container) return; if (!container) return;
const passwordSection = container.querySelector('#fido2PasswordSection'); const passwordSection = container.querySelector('[id^="fido2PasswordSection_"]');
const mainBtn = container.querySelector('#fido2MainBtn'); const mainBtn = container.querySelector('[id^="fido2MainBtn_"]');
const toggleLink = container.querySelector('#fido2ToggleModeLink'); const toggleLink = container.querySelector('[id^="fido2ToggleModeLink_"]');
if (passwordSection) passwordSection.style.display = 'none'; if (passwordSection) passwordSection.style.display = 'none';
if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_fido2_login'); if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_fido2_login');
@@ -1027,10 +1091,10 @@
const container = this.modalElement || this.containerElement; const container = this.modalElement || this.containerElement;
if (!container) return; if (!container) return;
const passwordSection = container.querySelector('#fido2PasswordSection'); const passwordSection = container.querySelector('[id^="fido2PasswordSection_"]');
const mainBtn = container.querySelector('#fido2MainBtn'); const mainBtn = container.querySelector('[id^="fido2MainBtn_"]');
const toggleLink = container.querySelector('#fido2ToggleModeLink'); const toggleLink = container.querySelector('[id^="fido2ToggleModeLink_"]');
const titleEl = document.getElementById('fido2LoginModalTitle'); const titleEl = container.querySelector('[id^="fido2LoginModalTitle_"]');
if (passwordSection) passwordSection.style.display = 'block'; if (passwordSection) passwordSection.style.display = 'block';
if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_password_login'); if (mainBtn) mainBtn.textContent = this.i18n.getText('btn_password_login');
@@ -1285,6 +1349,7 @@
const result = await registerFido2(effectiveUserId, displayName || 'Device-' + effectiveUserId, this.config.rpId); const result = await registerFido2(effectiveUserId, displayName || 'Device-' + effectiveUserId, this.config.rpId);
if (result.status === 'ok') { if (result.status === 'ok') {
// registerFido2 automatically creates/updates session in sessionStorage
await this.loadDevices(); await this.loadDevices();
this.eventManager.emit('deviceAdded', result); this.eventManager.emit('deviceAdded', result);
return result; return result;
@@ -1330,25 +1395,11 @@
DeviceManager.prototype.checkSession = async function() { DeviceManager.prototype.checkSession = async function() {
try { try {
const sessionUserId = this.getSessionUserId(); // Simply validate session with server, same as devices.html does
const sessionOk = await validSession(this.config.rpId);
if (!sessionUserId) { this.sessionStatus = !!sessionOk;
this.sessionStatus = false; this.eventManager.emit('sessionStatusChanged', this.sessionStatus);
this.eventManager.emit('sessionStatusChanged', false); return this.sessionStatus;
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) { } catch (error) {
this.sessionStatus = false; this.sessionStatus = false;
this.eventManager.emit('sessionStatusChanged', false); this.eventManager.emit('sessionStatusChanged', false);
@@ -1420,16 +1471,23 @@
container.innerHTML = ''; container.innerHTML = '';
const uniqueId = Date.now() + '_' + Math.random().toString(36).substr(2, 9);
const modalId = 'fido2SdkModal_' + uniqueId;
const sessionStatusId = 'fido2SessionStatus_' + uniqueId;
const addBtnId = 'fido2AddDeviceBtn_' + uniqueId;
const devicesListId = 'fido2DevicesList_' + uniqueId;
const modal = document.createElement('div'); const modal = document.createElement('div');
modal.className = 'modal fade fido2-sdk-modal'; modal.className = 'modal fade fido2-sdk-modal';
modal.id = 'fido2SdkModal'; modal.id = modalId;
modal.tabIndex = -1; modal.tabIndex = -1;
modal.setAttribute('aria-hidden', 'true'); modal.setAttribute('aria-hidden', 'true');
modal.innerHTML = this._getModalHTML(); modal.innerHTML = this._getModalHTML(uniqueId, sessionStatusId, addBtnId, devicesListId);
container.appendChild(modal); container.appendChild(modal);
this.modalElement = modal; this.modalElement = modal;
this.containerElement = container; this.containerElement = container;
this._uniqueId = uniqueId;
this.themeManager.applyTheme(modal); this.themeManager.applyTheme(modal);
@@ -1445,12 +1503,12 @@
this.cleanup(); this.cleanup();
}); });
this._bindEvents(); this._bindEvents(uniqueId, addBtnId, devicesListId);
return modal; return modal;
}; };
UIRenderer.prototype._getModalHTML = function() { UIRenderer.prototype._getModalHTML = function(uniqueId, sessionStatusId, addBtnId, devicesListId) {
const theme = this.config.theme; const theme = this.config.theme;
return ` return `
@@ -1459,11 +1517,11 @@
<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">${this.i18n.getText('my_devices')}</h5>
${this.config.features.showSessionStatus ? '<span class="badge fido2-sdk-status-badge" id="fido2SessionStatus"></span>' : ''} ${this.config.features.showSessionStatus ? `<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()} ${this._getBodyHTML(uniqueId, addBtnId, devicesListId)}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<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>
@@ -1473,7 +1531,7 @@
`; `;
}; };
UIRenderer.prototype._getBodyHTML = function() { UIRenderer.prototype._getBodyHTML = function(uniqueId, addBtnId, devicesListId) {
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 || '');
@@ -1488,7 +1546,7 @@
if (features.showAddButton) { if (features.showAddButton) {
html += ` html += `
<button type="button" class="btn btn-info mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="fido2AddDeviceBtn"> <button type="button" class="btn btn-info mb-3 fido2-sdk-btn fido2-sdk-btn-primary" id="${addBtnId}">
${this.i18n.getText('btn_add')} ${this.i18n.getText('btn_add')}
</button> </button>
`; `;
@@ -1504,7 +1562,7 @@
${features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''} ${features.showDeleteButton ? `<th>${this.i18n.getText('title_act')}</th>` : ''}
</tr> </tr>
</thead> </thead>
<tbody id="fido2DevicesList"> <tbody id="${devicesListId}">
<tr> <tr>
<td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td> <td colspan="3" class="text-center fido2-sdk-text">${this.i18n.getText('title_empty_list')}</td>
</tr> </tr>
@@ -1516,7 +1574,7 @@
return html; return html;
}; };
UIRenderer.prototype._bindEvents = function() { UIRenderer.prototype._bindEvents = function(uniqueId, addBtnId, devicesListId) {
const container = this.modalElement || this.containerElement; const container = this.modalElement || this.containerElement;
if (!container) return; if (!container) return;
@@ -1524,10 +1582,10 @@
btn.replaceWith(btn.cloneNode(true)); btn.replaceWith(btn.cloneNode(true));
}); });
const addBtn = container.querySelector('#fido2AddDeviceBtn'); const addBtn = container.querySelector('#' + addBtnId);
if (addBtn) { if (addBtn) {
addBtn.replaceWith(addBtn.cloneNode(true)); addBtn.replaceWith(addBtn.cloneNode(true));
const newAddBtn = container.querySelector('#fido2AddDeviceBtn'); const newAddBtn = container.querySelector('#' + addBtnId);
newAddBtn.addEventListener('click', () => { newAddBtn.addEventListener('click', () => {
this.eventManager.emit('addDevice'); this.eventManager.emit('addDevice');
}); });
@@ -1545,7 +1603,8 @@
}; };
UIRenderer.prototype.updateDevicesList = function(devices) { UIRenderer.prototype.updateDevicesList = function(devices) {
const tbody = document.getElementById('fido2DevicesList'); const container = this.modalElement || this.containerElement;
const tbody = container ? container.querySelector('[id^="fido2DevicesList_"]') : null;
if (!tbody) return; if (!tbody) return;
if (!devices || devices.length === 0) { if (!devices || devices.length === 0) {
@@ -1583,12 +1642,13 @@
}); });
tbody.innerHTML = html; tbody.innerHTML = html;
this._bindEvents(); this._bindEvents(this._uniqueId, 'fido2AddDeviceBtn_' + this._uniqueId, 'fido2DevicesList_' + this._uniqueId);
}; };
UIRenderer.prototype.updateSessionStatus = function(isValid) { UIRenderer.prototype.updateSessionStatus = function(isValid) {
const badge = document.getElementById('fido2SessionStatus'); const container = this.modalElement || this.containerElement;
const addBtn = document.getElementById('fido2AddDeviceBtn'); const badge = container ? container.querySelector('[id^="fido2SessionStatus_"]') : null;
const addBtn = container ? container.querySelector('[id^="fido2AddDeviceBtn_"]') : null;
if (badge) { if (badge) {
if (isValid) { if (isValid) {
@@ -1601,7 +1661,7 @@
} }
if (addBtn) { if (addBtn) {
addBtn.disabled = !isValid; addBtn.disabled = false;
} }
}; };
@@ -1773,10 +1833,6 @@
return this; return this;
} }
if (!window.jQuery) {
throw new Error('jQuery is required. Please include jQuery before fido2-ui-sdk.js');
}
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');
} }
@@ -1856,7 +1912,9 @@
try { try {
await self.deviceManager.addDevice(); await self.deviceManager.addDevice();
self.uiRenderer.updateDevicesList(self.deviceManager.devices); self.uiRenderer.updateDevicesList(self.deviceManager.devices);
// Re-check session after device list is updated
await self.deviceManager.checkSession(); await self.deviceManager.checkSession();
self.uiRenderer.updateSessionStatus(self.deviceManager.sessionStatus);
alert(self.i18n.getText('msg_register_ok')); alert(self.i18n.getText('msg_register_ok'));
} catch (error) { } catch (error) {
console.error('Add device error:', error); console.error('Add device error:', error);
@@ -1913,6 +1971,13 @@
} }
}; };
Fido2UIManager.prototype.logout = function() {
if (typeof logoutFido2UserSession === 'function') {
logoutFido2UserSession();
}
this.eventManager.emit('logout');
};
Fido2UIManager.prototype.destroy = function() { Fido2UIManager.prototype.destroy = function() {
if (this.deviceManager) { if (this.deviceManager) {
this.deviceManager.stopAutoRefresh(); this.deviceManager.stopAutoRefresh();
@@ -1926,10 +1991,6 @@
}; };
Fido2UIManager.prototype.renderLogin = function(config) { Fido2UIManager.prototype.renderLogin = function(config) {
if (!window.jQuery) {
throw new Error('jQuery is required. Please include jQuery before fido2-ui-sdk.js');
}
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');
} }
@@ -1954,6 +2015,11 @@
window.Fido2UIManager.refresh(); window.Fido2UIManager.refresh();
} }
}; };
window.Fido2UIManager.logout = function() {
if (window.Fido2UIManager && window.Fido2UIManager.logout) {
window.Fido2UIManager.logout();
}
};
window.Fido2UIManager.destroy = function() { window.Fido2UIManager.destroy = function() {
if (window.Fido2UIManager && window.Fido2UIManager.destroy) { if (window.Fido2UIManager && window.Fido2UIManager.destroy) {
window.Fido2UIManager.destroy(); window.Fido2UIManager.destroy();

View File

@@ -47,7 +47,6 @@
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. --> <!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
<script src="files/config.js"></script> <script src="files/config.js"></script>
<script src="files/jquery.js"></script>
<script src="files/popper.js"></script> <script src="files/popper.js"></script>
<script src="files/bootstrap.js"></script> <script src="files/bootstrap.js"></script>
<script src="files/perfect-scrollbar.js"></script> <script src="files/perfect-scrollbar.js"></script>
@@ -167,13 +166,12 @@
} }
async function authenticate(){ async function authenticate(){
var uid = $('#uid').val() var uid = document.getElementById('uid').value
if(uid && 0==uid.length)uid=null if(uid && 0==uid.length)uid=null
const result = await authenticateFido2(uid) const result = await authenticateFido2(uid)
if(result.status === 'ok'){ if(result.status === 'ok'){
//Set your logged in status and jump to user's top page.
location.href = 'devices.html?uid='+result.username location.href = 'devices.html?uid='+result.username
}else{ }else{
errProcessFido2(result) errProcessFido2(result)
@@ -181,9 +179,9 @@
} }
function checkInput(){ function checkInput(){
const uid = $('#uid').val() const uid = document.getElementById('uid').value
if(!uid || 0>=uid.length){ if(!uid || 0>=uid.length){
alert($('#msg_uid_input').html()) alert(document.getElementById('msg_uid_input').textContent)
return false; return false;
}else return true; }else return true;
} }

View File

@@ -179,7 +179,6 @@
} }
</style> </style>
<script src="files/jquery.js"></script>
<script src="files/popper.js"></script> <script src="files/popper.js"></script>
<script src="files/bootstrap.js"></script> <script src="files/bootstrap.js"></script>
<script src="files/dfido2-lib.js"></script> <script src="files/dfido2-lib.js"></script>
@@ -366,6 +365,36 @@
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ"); lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
i18n_messages.set("msg_feature_7", lang_map); i18n_messages.set("msg_feature_7", lang_map);
lang_map = new Map();
lang_map.set("en-US", "Login Default");
lang_map.set("zh-CN", "登录缺省");
lang_map.set("ja", "ログイン デフォルト");
i18n_messages.set("msg_code_login_default", lang_map);
lang_map = new Map();
lang_map.set("en-US", "Login Custom");
lang_map.set("zh-CN", "登录定制");
lang_map.set("ja", "ログイン カスタム");
i18n_messages.set("msg_code_login_custom", lang_map);
lang_map = new Map();
lang_map.set("en-US", "Device Manager Default");
lang_map.set("zh-CN", "设备管理缺省");
lang_map.set("ja", "デバイス管理 デフォルト");
i18n_messages.set("msg_code_device_default", lang_map);
lang_map = new Map();
lang_map.set("en-US", "Device Manager Custom");
lang_map.set("zh-CN", "设备管理定制");
lang_map.set("ja", "デバイス管理 カスタム");
i18n_messages.set("msg_code_device_custom", lang_map);
lang_map = new Map();
lang_map.set("en-US", "JS Import Examples:");
lang_map.set("zh-CN", "JS 引入示例:");
lang_map.set("ja", "JS インポート例:");
i18n_messages.set("msg_js_import_examples", lang_map);
lang_map = new Map(); lang_map = new Map();
lang_map.set("en-US", "Multi-language support (English/Japanese/Chinese)"); lang_map.set("en-US", "Multi-language support (English/Japanese/Chinese)");
lang_map.set("zh-CN", "多语言支持(英/日/中)"); lang_map.set("zh-CN", "多语言支持(英/日/中)");
@@ -510,41 +539,107 @@
<div class="demo-section"> <div class="demo-section">
<h2 id="msg_section_code_title">💻 Code Examples</h2> <h2 id="msg_section_code_title">💻 Code Examples</h2>
<p id="msg_code_simple">Simplest integration:</p>
<p id="msg_js_import_examples">JS Import Examples:</p>
<div class="code-block"> <div class="code-block">
<code>Fido2UIManager.renderLogin({ <code>&lt;script src="files/popper.js"&gt;&lt;/script&gt;<br>
container: '#login-container', &lt;script src="files/bootstrap.js"&gt;&lt;/script&gt;<br>
mode: 'modal', &lt;script src="files/dfido2-lib.js"&gt;&lt;/script&gt;<br>
serverUrl: SERVER_URL &lt;script src="files/fido2-ui-sdk.js"&gt;&lt;/script&gt;<br>
&lt;script src="files/amipro_utils.js"&gt;&lt;/script&gt;</code>
</div>
<p id="msg_code_login_default">Login Default:</p>
<div class="code-block">
<code>// Login Default<br>
Fido2UIManager.renderLogin({<br>
&nbsp;&nbsp;container: '#login-container',<br>
&nbsp;&nbsp;mode: 'modal',<br>
&nbsp;&nbsp;serverUrl: SERVER_URL,<br>
&nbsp;&nbsp;language: CURRENT_LANG<br>
});</code> });</code>
</div> </div>
<p id="msg_code_theme">With theme customization:</p> <p id="msg_code_login_custom">Login Custom:</p>
<div class="code-block"> <div class="code-block">
<code>Fido2UIManager.renderLogin({ <code>// Login with Custom Theme<br>
container: '#login-container', Fido2UIManager.renderLogin({<br>
mode: 'modal', &nbsp;&nbsp;container: '#login-container',<br>
serverUrl: SERVER_URL, &nbsp;&nbsp;mode: 'modal',<br>
language: 'ja', &nbsp;&nbsp;serverUrl: SERVER_URL,<br>
theme: { &nbsp;&nbsp;language: 'ja',<br>
primaryColor: '#ce59d9', &nbsp;&nbsp;theme: {<br>
backgroundColor: '#faf5ff' &nbsp;&nbsp;&nbsp;&nbsp;logo: 'files/favicon.ico',<br>
}, &nbsp;&nbsp;&nbsp;&nbsp;primaryColor: '#ce59d9',<br>
features: { &nbsp;&nbsp;&nbsp;&nbsp;backgroundColor: '#faf5ff',<br>
enablePasswordLogin: true, &nbsp;&nbsp;&nbsp;&nbsp;textColor: '#6b21a8',<br>
autoShowPassword: true &nbsp;&nbsp;&nbsp;&nbsp;borderRadius: '12px'<br>
} &nbsp;&nbsp;},<br>
&nbsp;&nbsp;features: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;autoAuth: false,<br>
&nbsp;&nbsp;&nbsp;&nbsp;enablePasswordLogin: true,<br>
&nbsp;&nbsp;&nbsp;&nbsp;autoShowPassword: true,<br>
&nbsp;&nbsp;&nbsp;&nbsp;maxPasswordAttempts: 3<br>
&nbsp;&nbsp;},<br>
&nbsp;&nbsp;callbacks: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;onFido2Success: function(username, session) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Handle login success<br>
&nbsp;&nbsp;&nbsp;&nbsp;},<br>
&nbsp;&nbsp;&nbsp;&nbsp;onFido2Error: function(error) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Handle error<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;}<br>
});</code> });</code>
</div> </div>
<p id="msg_code_device">Device Manager:</p> <p id="msg_code_device_default">Device Manager Default:</p>
<div class="code-block"> <div class="code-block">
<code>Fido2UIManager.renderDeviceManager({ <code>// Device Manager Default<br>
userId: currentUserId, Fido2UIManager.renderDeviceManager({<br>
container: '#device-container', &nbsp;&nbsp;userId: currentUserId,<br>
mode: 'modal', &nbsp;&nbsp;container: '#device-container',<br>
serverUrl: SERVER_URL, &nbsp;&nbsp;mode: 'modal',<br>
language: 'ja' &nbsp;&nbsp;serverUrl: SERVER_URL,<br>
&nbsp;&nbsp;language: CURRENT_LANG<br>
});</code>
</div>
<p id="msg_code_device_custom">Device Manager Custom:</p>
<div class="code-block">
<code>// Device Manager with Custom Style<br>
Fido2UIManager.renderDeviceManager({<br>
&nbsp;&nbsp;userId: currentUserId,<br>
&nbsp;&nbsp;container: '#device-container',<br>
&nbsp;&nbsp;mode: 'modal',<br>
&nbsp;&nbsp;serverUrl: SERVER_URL,<br>
&nbsp;&nbsp;language: 'ja',<br>
&nbsp;&nbsp;theme: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;logo: 'files/favicon.ico',<br>
&nbsp;&nbsp;&nbsp;&nbsp;primaryColor: '#ce59d9',<br>
&nbsp;&nbsp;&nbsp;&nbsp;backgroundColor: '#faf5ff',<br>
&nbsp;&nbsp;&nbsp;&nbsp;textColor: '#6b21a8',<br>
&nbsp;&nbsp;&nbsp;&nbsp;borderRadius: '12px'<br>
&nbsp;&nbsp;},<br>
&nbsp;&nbsp;features: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;showAddButton: true,<br>
&nbsp;&nbsp;&nbsp;&nbsp;showDeleteButton: true,<br>
&nbsp;&nbsp;&nbsp;&nbsp;showUserInfo: true,<br>
&nbsp;&nbsp;&nbsp;&nbsp;showSessionStatus: true<br>
&nbsp;&nbsp;},<br>
&nbsp;&nbsp;callbacks: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;onInit: function(manager) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Manager initialized<br>
&nbsp;&nbsp;&nbsp;&nbsp;},<br>
&nbsp;&nbsp;&nbsp;&nbsp;onDeviceAdded: function(device) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Device added<br>
&nbsp;&nbsp;&nbsp;&nbsp;},<br>
&nbsp;&nbsp;&nbsp;&nbsp;onDeviceDeleted: function(deviceId) {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Device deleted<br>
&nbsp;&nbsp;&nbsp;&nbsp;},<br>
&nbsp;&nbsp;&nbsp;&nbsp;onClose: function() {<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// Manager closed<br>
&nbsp;&nbsp;&nbsp;&nbsp;}<br>
&nbsp;&nbsp;}<br>
});</code> });</code>
</div> </div>
</div> </div>
@@ -585,7 +680,7 @@
return 'en-US'; return 'en-US';
} }
const CURRENT_LANG = getBrowserLanguage();//'https://fido2.amipro.me'; const CURRENT_LANG = getBrowserLanguage();
function enableDeviceManagementButtons() { function enableDeviceManagementButtons() {
document.getElementById('btn-manage-devices').disabled = false; document.getElementById('btn-manage-devices').disabled = false;
@@ -602,6 +697,7 @@
} }
function logout() { function logout() {
Fido2UIManager.logout();
currentUserId = null; currentUserId = null;
isLoggedIn = false; isLoggedIn = false;
log('success', getI18NText(i18n_messages, 'msg_logout_success')); log('success', getI18NText(i18n_messages, 'msg_logout_success'));
@@ -825,5 +921,16 @@
https://amipro.me/fido2_top.html https://amipro.me/fido2_top.html
</a> </a>
</p> </p>
<!--
CSP (Content Security Policy) 示例 - 生产环境部署建议添加:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' https://your-server.com;">
-->
</body> </body>
</html> </html>

View File

@@ -151,7 +151,6 @@
} }
</style> </style>
<script src="files/jquery.js"></script>
<script src="files/popper.js"></script> <script src="files/popper.js"></script>
<script src="files/bootstrap.js"></script> <script src="files/bootstrap.js"></script>
<script src="files/dfido2-lib.js"></script> <script src="files/dfido2-lib.js"></script>