Compare commits
7 Commits
44a35fb45a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7248372cd8 | ||
|
|
1d2f4dfc9b | ||
|
|
03ce446b8a | ||
| 89ea8a48fc | |||
|
|
055d59f979 | ||
|
|
9bbbae8dd2 | ||
|
|
db2b608606 |
58
devices.html
58
devices.html
@@ -46,7 +46,6 @@
|
||||
<!--? 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/jquery.js"></script>
|
||||
<script src="files/popper.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/perfect-scrollbar.js"></script>
|
||||
@@ -169,27 +168,25 @@
|
||||
window.location.href = "login.html";
|
||||
}
|
||||
}
|
||||
$('#user_id').html(user_id);
|
||||
document.getElementById('user_id').textContent = user_id;
|
||||
listDevices();
|
||||
}
|
||||
|
||||
async function setSessionStatus(){
|
||||
const sessionOk = await validSession();
|
||||
const sessionStatusEl = document.getElementById('session_status');
|
||||
if(sessionOk){
|
||||
$('#session_status').html(getI18NText(i18n_messages, 'msg_session_status_ok'))
|
||||
sessionStatusEl.textContent = getI18NText(i18n_messages, 'msg_session_status_ok');
|
||||
}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){
|
||||
//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);
|
||||
|
||||
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($('#msg_register_ok').html());
|
||||
alert(document.getElementById('msg_register_ok').textContent);
|
||||
listDevices();
|
||||
}else{
|
||||
errProcessFido2(result)
|
||||
@@ -213,17 +210,17 @@
|
||||
|
||||
async function listDevices(){
|
||||
const result = await listUserDevicesFido2()
|
||||
$("#devices_list").html("");
|
||||
const devicesListEl = document.getElementById('devices_list');
|
||||
devicesListEl.innerHTML = "";
|
||||
if("ok" === result.status){
|
||||
let lst_html = ""
|
||||
|
||||
for(let dev of result.devices){
|
||||
var dev_desc = dev.desc
|
||||
if(!dev_desc || 0 === dev_desc.length){
|
||||
let parser = new UAParser(dev.userAgent);
|
||||
|
||||
if(parser.getOS().name){
|
||||
dev_desc = parser.getDevice().model + ',' + parser.getOS().name + ',' + parser.getBrowser().name;
|
||||
const parser = new UAParser(dev.userAgent);
|
||||
const osName = parser.getOS().name;
|
||||
if(osName){
|
||||
dev_desc = parser.getDevice().model + ',' + osName + ',' + parser.getBrowser().name;
|
||||
}else{
|
||||
dev_desc = dev.userAgent;
|
||||
}
|
||||
@@ -231,14 +228,35 @@
|
||||
|
||||
var date = new Date(dev.registered_time);
|
||||
|
||||
lst_html += '<tr><td><strong>'+ dev_desc +'</strong></td><td>'+
|
||||
date.toLocaleString()+'</td><td>'+
|
||||
"<a style=\"padding-left: 0px;\" class=\"dropdown-item\" href=\"javascript:delDevice('"+dev.device_id+"');\">"+
|
||||
'<i class="bx bx-trash me-1"></i><span id="title_del"> '+ getI18NText(i18n_messages, 'title_del') +'</span></a></div></td></tr>'
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const tdDevice = document.createElement('td');
|
||||
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{
|
||||
const msg = getI18NErrorMessage(result.errorMessage); //fido2LibErrMsgLanguages.japanese
|
||||
const msg = getI18NErrorMessage(result.errorMessage);
|
||||
alert(msg?msg:result.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
322
docs/README.md
Normal file
322
docs/README.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# FIDO2 UI SDK
|
||||
|
||||
一个简单易用的 FIDO2 设备管理 UI 库,支持快速集成 FIDO2 认证设备管理功能。
|
||||
|
||||
## 特性
|
||||
|
||||
- 🚀 **快速集成** - 一行代码即可集成设备管理功能
|
||||
- 🎨 **主题定制** - 支持自定义 Logo、颜色、背景等
|
||||
- 🌐 **国际化** - 内置中文、英文、日文支持
|
||||
- 📱 **响应式** - 支持桌面端和移动端
|
||||
- 🎭 **双模式** - 支持弹出窗口和独立页面两种模式
|
||||
- 🎯 **事件回调** - 完整的事件系统,灵活处理各种操作
|
||||
- ♿ **向后兼容** - 完全兼容现有的对接方式
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 引入依赖
|
||||
|
||||
```html
|
||||
<!-- 必需的依赖 -->
|
||||
<script src="files/jquery.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/dfido2-lib.js"></script>
|
||||
|
||||
<!-- 引入 SDK -->
|
||||
<script src="files/fido2-ui-sdk.js"></script>
|
||||
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||
```
|
||||
|
||||
### 2. 添加容器(仅模态框模式需要)
|
||||
|
||||
```html
|
||||
<div id="device-container"></div>
|
||||
```
|
||||
|
||||
### 3. 调用 SDK
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: 'https://fido2.amipro.me'
|
||||
});
|
||||
```
|
||||
|
||||
## 使用模式
|
||||
|
||||
### 模式 1: 弹出窗口 (推荐)
|
||||
|
||||
弹出窗口模式在当前页面上显示模态框,用户操作完成后自动关闭。
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: 'https://fido2.amipro.me'
|
||||
});
|
||||
```
|
||||
|
||||
**适用场景:**
|
||||
- 用户在设置页面需要管理设备
|
||||
- 不希望离开当前页面
|
||||
- 快速查看和管理设备
|
||||
|
||||
### 模式 2: 独立页面
|
||||
|
||||
独立页面模式会渲染完整的设备管理页面,适合作为独立的页面功能。
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
mode: 'standalone',
|
||||
serverUrl: 'https://fido2.amipro.me'
|
||||
});
|
||||
```
|
||||
|
||||
**适用场景:**
|
||||
- 需要独立的设备管理页面
|
||||
- 通过链接直接访问设备管理
|
||||
- 需要更多屏幕空间显示设备信息
|
||||
|
||||
## 配置选项
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
// === 核心配置 ===
|
||||
serverUrl: 'https://fido2.amipro.me', // FIDO2 服务器地址
|
||||
mode: 'modal', // 'modal' | 'standalone'
|
||||
|
||||
// === 容器配置 (仅 modal 模式需要) ===
|
||||
container: '#device-container', // 容器选择器或 DOM 元素
|
||||
|
||||
// === 主题定制 ===
|
||||
theme: {
|
||||
logo: 'path/to/logo.png', // Logo URL
|
||||
primaryColor: '#ce59d9', // 主色调
|
||||
backgroundColor: '#ffffff', // 背景色
|
||||
textColor: '#333333', // 文字颜色
|
||||
borderRadius: '8px', // 圆角
|
||||
},
|
||||
|
||||
// === 国际化 ===
|
||||
language: 'zh-CN', // 'en-US' | 'zh-CN' | 'ja'
|
||||
customI18n: { // 自定义翻译
|
||||
'my_devices': { 'zh-CN': '我的设备' },
|
||||
// ...
|
||||
},
|
||||
|
||||
// === 功能开关 ===
|
||||
features: {
|
||||
showAddButton: true,
|
||||
showDeleteButton: true,
|
||||
showUserInfo: true,
|
||||
showSessionStatus: true,
|
||||
},
|
||||
|
||||
// === 事件回调 ===
|
||||
callbacks: {
|
||||
onInit: () => {}, // 初始化完成
|
||||
onDeviceAdded: (device) => {}, // 设备添加成功
|
||||
onDeviceDeleted: (deviceId) => {}, // 设备删除成功
|
||||
onDeviceListLoaded: (devices) => {}, // 设备列表加载完成
|
||||
onError: (error) => {}, // 错误发生
|
||||
onClose: () => {}, // 窗口关闭 (仅 modal 模式)
|
||||
},
|
||||
|
||||
// === 其他 ===
|
||||
rpId: null, // Relying Party ID
|
||||
autoRefresh: true, // 自动刷新设备列表
|
||||
refreshInterval: 5000, // 刷新间隔(ms)
|
||||
});
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
### 示例 1: 基础集成
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="files/jquery.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/dfido2-lib.js"></script>
|
||||
<script src="files/fido2-ui-sdk.js"></script>
|
||||
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||
</head>
|
||||
<body>
|
||||
<button onclick="openDeviceManager()">管理设备</button>
|
||||
<div id="device-container"></div>
|
||||
|
||||
<script>
|
||||
function openDeviceManager() {
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: 'https://fido2.amipro.me'
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 示例 2: 带主题定制
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: 'https://fido2.amipro.me',
|
||||
theme: {
|
||||
logo: '/assets/my-logo.png',
|
||||
primaryColor: '#0066cc',
|
||||
backgroundColor: '#f8f9fa',
|
||||
textColor: '#333333',
|
||||
borderRadius: '12px'
|
||||
},
|
||||
language: 'zh-CN'
|
||||
});
|
||||
```
|
||||
|
||||
### 示例 3: 带事件回调
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: 'https://fido2.amipro.me',
|
||||
callbacks: {
|
||||
onInit: function(manager) {
|
||||
console.log('设备管理器初始化完成');
|
||||
},
|
||||
onDeviceAdded: function(device) {
|
||||
console.log('设备添加成功:', device);
|
||||
// 刷新用户界面或其他逻辑
|
||||
},
|
||||
onDeviceDeleted: function(deviceId) {
|
||||
console.log('设备删除成功:', deviceId);
|
||||
},
|
||||
onDeviceListLoaded: function(devices) {
|
||||
console.log('设备列表加载完成:', devices.length, '个设备');
|
||||
},
|
||||
onError: function(error) {
|
||||
console.error('发生错误:', error);
|
||||
},
|
||||
onClose: function() {
|
||||
console.log('窗口关闭');
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 示例 4: 自定义翻译
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: 'https://fido2.amipro.me',
|
||||
language: 'zh-CN',
|
||||
customI18n: {
|
||||
'my_devices': {
|
||||
'zh-CN': '我的 FIDO2 设备'
|
||||
},
|
||||
'btn_add': {
|
||||
'zh-CN': '添加新设备'
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## API 方法
|
||||
|
||||
### renderDeviceManager(config)
|
||||
|
||||
渲染设备管理器。
|
||||
|
||||
**参数:**
|
||||
- `config` (Object) - 配置对象
|
||||
|
||||
**返回:**
|
||||
- (Object) - 设备管理器实例
|
||||
|
||||
### close()
|
||||
|
||||
关闭模态框(仅 modal 模式)。
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.close();
|
||||
```
|
||||
|
||||
### refresh()
|
||||
|
||||
刷新设备列表和会话状态。
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.refresh();
|
||||
```
|
||||
|
||||
### destroy()
|
||||
|
||||
销毁设备管理器实例。
|
||||
|
||||
```javascript
|
||||
Fido2UIManager.destroy();
|
||||
```
|
||||
|
||||
## 事件系统
|
||||
|
||||
SDK 提供以下事件:
|
||||
|
||||
| 事件名称 | 触发时机 | 回调参数 |
|
||||
|---------|---------|---------|
|
||||
| `init` | 初始化完成 | (manager) |
|
||||
| `deviceAdded` | 设备添加成功 | (device) |
|
||||
| `deviceDeleted` | 设备删除成功 | (deviceId) |
|
||||
| `deviceListLoaded` | 设备列表加载完成 | (devices) |
|
||||
| `sessionStatusChanged` | 会话状态改变 | (isValid) |
|
||||
| `error` | 发生错误 | (error) |
|
||||
| `close` | 窗口关闭 | 无 |
|
||||
|
||||
## 向后兼容性
|
||||
|
||||
SDK 完全兼容现有的对接方式:
|
||||
|
||||
- ✅ `devices.html` - 继续使用现有逻辑,无需修改
|
||||
- ✅ `login.html` - 继续使用现有逻辑,无需修改
|
||||
- ✅ `dfido2-lib.js` - 核心库保持不变
|
||||
- ✅ 所有 CSS 文件 - 保持原样
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
- Chrome 67+
|
||||
- Firefox 60+
|
||||
- Safari 13+
|
||||
- Edge 18+
|
||||
|
||||
## 依赖
|
||||
|
||||
- jQuery 3.x
|
||||
- Bootstrap 5.x
|
||||
- dfido2-lib.js(核心 FIDO2 库)
|
||||
|
||||
## 演示
|
||||
|
||||
- [模态框模式演示](modal-demo.html)
|
||||
- [独立页面模式演示](standalone-demo.html)
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保 `dfido2-lib.js` 在 `fido2-ui-sdk.js` 之前加载
|
||||
2. Modal 模式需要提供有效的容器选择器
|
||||
3. 独立页面模式会替换当前页面的内容
|
||||
4. 确保浏览器支持 WebAuthn API
|
||||
|
||||
## 许可证
|
||||
|
||||
[根据您的项目许可证填写]
|
||||
|
||||
## 联系方式
|
||||
|
||||
如有问题或建议,请联系 [您的联系方式]
|
||||
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* amiPro utils
|
||||
* amiPro utils - jQuery-free version
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function setI18NText(i18n_map){
|
||||
for (const key of i18n_map.keys()) {
|
||||
const elm = $("#"+key);
|
||||
const elm = document.getElementById(key);
|
||||
if(elm){
|
||||
const lang = window.navigator.language;
|
||||
var elem = i18n_map.get(key)
|
||||
var elem = i18n_map.get(key)
|
||||
var msg = null
|
||||
if(elem){
|
||||
msg = elem.get(lang)
|
||||
@@ -17,14 +17,14 @@ function setI18NText(i18n_map){
|
||||
}
|
||||
if(!msg)msg = key+"-"+lang
|
||||
|
||||
$("#"+key).html(msg);
|
||||
elm.textContent = msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getI18NText(i18n_map, key){
|
||||
const lang = window.navigator.language;
|
||||
var elem = i18n_map.get(key)
|
||||
var elem = i18n_map.get(key)
|
||||
var msg = null
|
||||
if(elem){
|
||||
msg = elem.get(lang)
|
||||
|
||||
@@ -1,7 +1,18 @@
|
||||
/**
|
||||
*
|
||||
* @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'
|
||||
|
||||
let configuredServerUrl = null;
|
||||
|
||||
/** ===APIs=== */
|
||||
|
||||
if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)){
|
||||
@@ -9,9 +20,25 @@ if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_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);
|
||||
}
|
||||
|
||||
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(){
|
||||
//const session_text = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
//alert('canTryAuth:'+session_text+"|"+(null != localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED)))
|
||||
@@ -67,7 +94,7 @@ async function listUserDevicesFido2(rpId = null) {
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -78,6 +105,9 @@ async function listUserDevicesFido2(rpId = null) {
|
||||
const resp = await response.json();
|
||||
if ('ok' === resp.status && resp.session === session_data.session) {
|
||||
return {status:'ok', devices:resp.devices}
|
||||
} else if (resp.errorMessage && resp.errorMessage.includes('No user session')) {
|
||||
sessionStorage.removeItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION);
|
||||
return {status:'ok', devices:[]}
|
||||
} else {
|
||||
return {status:'failed', errorMessage: resp.errorMessage}
|
||||
}
|
||||
@@ -101,7 +131,7 @@ async function delUserDeviceFido2(device_id, rpId = null) {
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -151,7 +181,7 @@ async function validSession(rpId = null) {
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -174,7 +204,7 @@ async function logoutFido2UserSession(){
|
||||
|
||||
const session_data = JSON.parse(session_text)
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -189,7 +219,7 @@ async function logoutFido2UserSession(){
|
||||
async function getRegistrationUser(reg_session_id){
|
||||
try {
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -219,17 +249,65 @@ function errMessageFido2(result){
|
||||
break;
|
||||
case fido2LibErrCodes.timeout:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr103:');
|
||||
break;
|
||||
break;
|
||||
case fido2LibErrCodes.invalid_state:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr105:');
|
||||
break;
|
||||
case fido2LibErrCodes.not_allowed:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr107:');
|
||||
break;
|
||||
case fido2LibErrCodes.abort:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr108:');
|
||||
break;
|
||||
case fido2LibErrCodes.not_supported:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr109:');
|
||||
break;
|
||||
case fido2LibErrCodes.security:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr110:');
|
||||
break;
|
||||
case fido2LibErrCodes.network:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr111:');
|
||||
break;
|
||||
case fido2LibErrCodes.constraint:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr112:');
|
||||
break;
|
||||
case fido2LibErrCodes.not_readable:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr113:');
|
||||
break;
|
||||
case fido2LibErrCodes.encoding:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr114:');
|
||||
break;
|
||||
case fido2LibErrCodes.data_error:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr115:');
|
||||
break;
|
||||
default:
|
||||
rtn=result.errorMessage?result.errorMessage:getI18NErrorMessage('Fido2LibErr104:');
|
||||
}
|
||||
}else if(result.name && "InvalidStateError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr105:');
|
||||
}else if(result.name && "NotAllowedError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr107:');
|
||||
}else if(result.name && "AbortError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr108:');
|
||||
}else if(result.name && "NotSupportedError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr109:');
|
||||
}else if(result.name && "SecurityError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr110:');
|
||||
}else if(result.name && "NetworkError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr111:');
|
||||
}else if(result.name && "ConstraintError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr112:');
|
||||
}else if(result.name && "NotReadableError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr113:');
|
||||
}else if(result.name && "EncodingError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr114:');
|
||||
}else if(result.name && "DataError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr115:');
|
||||
}else if(result.errorMessage){
|
||||
const msg = getI18NErrorMessage(result.errorMessage);
|
||||
rtn=msg?msg:result.errorMessage;
|
||||
}else{
|
||||
rtn=getI18NErrorMessage(i18n_messages, 'Fido2LibErr104:');
|
||||
rtn=getI18NErrorMessage('Fido2LibErr104:');
|
||||
}
|
||||
|
||||
return rtn;
|
||||
@@ -238,7 +316,17 @@ function errMessageFido2(result){
|
||||
const fido2LibErrCodes = {
|
||||
user_canceled : -101,
|
||||
timeout : -102,
|
||||
unknown : -999
|
||||
unknown : -999,
|
||||
invalid_state : -103,
|
||||
not_allowed : -104,
|
||||
abort : -105,
|
||||
not_supported : -106,
|
||||
security : -107,
|
||||
network : -108,
|
||||
constraint : -109,
|
||||
not_readable : -110,
|
||||
encoding : -111,
|
||||
data_error : -112
|
||||
}
|
||||
|
||||
const errMsgs = new Map();
|
||||
@@ -268,11 +356,23 @@ errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr118:', 'Signature is not
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr119:', 'No user session!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr120:', 'User has reached the device limit!');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr101:', 'Your browser does not support FIDO2.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr102:', 'The user canceled.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr103:', 'The process timeout.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr104:', 'System error.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr105:', 'The same authenticator cannot be registered again.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr101:', 'Your browser does not support FIDO2/WebAuthn.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr102:', 'The operation was canceled by the user.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr103:', 'The operation timed out.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr104:', 'A system error occurred.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr105:', 'The authenticator contains credentials that are already registered with this website.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr106:', 'Another request is already in progress.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr107:', 'The operation was not allowed.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr108:', 'The operation was aborted.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr109:', 'This operation is not supported on your device.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr110:', 'A security error occurred.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr111:', 'A network error occurred.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr112:', 'The operation failed due to a constraint violation.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr113:', 'Could not read the credential.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr114:', 'The data format is invalid.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr115:', 'A data error occurred.');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr106:', 'Another request is already in progress.');
|
||||
|
||||
errMsgs.set(fido2LibErrMsgLanguages.japanese, new Map());
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr101:', '登録されていないエンタープライズ認証デバイス aaguid!');
|
||||
@@ -294,11 +394,23 @@ errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr118:', '署名は base6
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr119:', 'ユーザーセッションがありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr120:', 'ユーザーはデバイスの制限数に達しました!');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr101:', 'お使いのブラウザは FIDO2 をサポートしていません。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr102:', 'ユーザーがキャンセルしました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr103:', 'プロセスがタイムアウトしました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr104:', 'システムエラー。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr105:', '同じ認証デバイスを再登録することはできません。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr101:', 'お使いのブラウザは FIDO2/WebAuthn をサポートしていません。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr102:', 'ユーザーによって操作がキャンセルされました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr103:', '操作がタイムアウトしました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr104:', 'システムエラーが発生しました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr105:', 'この認証デバイスには、すでにこの网站に登録されている憑拠が含まれています。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr106:', '別のリクエストがすでに進行中です。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr107:', '操作が許可されませんでした。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr108:', '操作が中止されました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr109:', 'この機器ではこの操作はサポートされていません。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr110:', 'セキュリティエラーが発生しました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr111:', 'ネットワークエラーが発生しました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr112:', '制約違反のため操作に失敗しました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr113:', '憑拠を読み取れませんでした。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr114:', 'データ形式が無効です。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr115:', 'データエラーが発生しました。');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr106:', '別のリクエストがすでに進行中です。');
|
||||
|
||||
errMsgs.set(fido2LibErrMsgLanguages.chinese_cn, new Map());
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr101:', '未注册的企业认证器 aaguid!');
|
||||
@@ -320,11 +432,23 @@ errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr118:', '签名不是
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr119:', '未建立用户会话!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr120:', '用户已达到设备限制数!');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr101:', '您的浏览器不支持FIDO2.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr102:', '用户取消了操作。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr106:', '另一个请求正在进行中。');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr101:', '您的浏览器不支持FIDO2/WebAuthn。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr102:', '操作已被用户取消。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr103:', '操作超时。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr104:', '系统错误。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr105:', '无法再次注册相同的认证器。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr104:', '发生系统错误。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr105:', '您的认证器包含已在此网站注册的凭据。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr106:', '另一个请求正在进行中。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr107:', '操作不被允许。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr108:', '操作已中止。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr109:', '您的设备不支持此操作。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr110:', '发生安全错误。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr111:', '发生网络错误。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr112:', '由于约束冲突,操作失败。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr113:', '无法读取凭据。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr114:', '数据格式无效。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr115:', '发生数据错误。');
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -374,8 +498,8 @@ function makePublicKey(attOptsResp) {
|
||||
rp: attOptsResp.rp,
|
||||
user: {
|
||||
id: _stringToArrayBuffer(attOptsResp.user.id), //_base64ToArrayBuffer(_fromBase64URL(attOptsResp.user.id)),
|
||||
name: attOptsResp.user.name,
|
||||
displayName: attOptsResp.user.displayName,
|
||||
name: attOptsResp.user.name || attOptsResp.user.id || 'user',
|
||||
displayName: attOptsResp.user.displayName || attOptsResp.user.name || attOptsResp.user.id || 'User',
|
||||
},
|
||||
pubKeyCredParams: attOptsResp.pubKeyCredParams,
|
||||
timeout: attOptsResp.timeout,
|
||||
@@ -387,6 +511,10 @@ function makePublicKey(attOptsResp) {
|
||||
|
||||
async function doAttestation(username, displayName, rpId, userVerification = 'preferred') {
|
||||
var process_time_limit = Number.MAX_SAFE_INTEGER
|
||||
if (window._fido2_pending_request) {
|
||||
return {status:'failed', errorMessage: 'Fido2LibErr106:A request is already pending'};
|
||||
}
|
||||
window._fido2_pending_request = true;
|
||||
try {
|
||||
const attestationOptions = {
|
||||
username: username,
|
||||
@@ -403,7 +531,7 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
||||
attestationOptions.rp = { id: rpId }
|
||||
}
|
||||
|
||||
const svrUrl = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)
|
||||
const svrUrl = getServerUrl()
|
||||
const response = await fetch(svrUrl + "/attestation/options", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
@@ -438,7 +566,7 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -464,6 +592,7 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
||||
} catch (err) {
|
||||
var errRtn = {status:'failed', errorMessage: err.message};
|
||||
if(err.name) errRtn.name = err.name
|
||||
|
||||
if(err.name && 'NotAllowedError' === err.name){
|
||||
const nowtm = (new Date()).getTime()
|
||||
if(nowtm > process_time_limit){
|
||||
@@ -471,14 +600,40 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
||||
}else{
|
||||
errRtn.errCode = fido2LibErrCodes.user_canceled
|
||||
}
|
||||
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||
|
||||
} else if(err.name && 'InvalidStateError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.invalid_state
|
||||
} else if(err.name && 'AbortError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.abort
|
||||
} else if(err.name && 'NotSupportedError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.not_supported
|
||||
} else if(err.name && 'SecurityError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.security
|
||||
} else if(err.name && 'NetworkError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.network
|
||||
} else if(err.name && 'ConstraintError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.constraint
|
||||
} else if(err.name && 'NotReadableError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.not_readable
|
||||
} else if(err.name && 'EncodingError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.encoding
|
||||
} else if(err.name && 'DataError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.data_error
|
||||
} else {
|
||||
errRtn.errCode = fido2LibErrCodes.unknown
|
||||
}
|
||||
|
||||
return errRtn;
|
||||
} finally {
|
||||
window._fido2_pending_request = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function doAssertion(username = null, rpId = null, userVerification = 'preferred') {
|
||||
var process_time_limit = Number.MAX_SAFE_INTEGER
|
||||
if (window._fido2_pending_request) {
|
||||
return {status:'failed', errorMessage: 'Fido2LibErr106:A request is already pending'};
|
||||
}
|
||||
window._fido2_pending_request = true;
|
||||
try {
|
||||
let authnOptions;
|
||||
/*
|
||||
@@ -511,7 +666,7 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -553,7 +708,7 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
||||
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",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
@@ -576,6 +731,7 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
||||
} catch (err) {
|
||||
var errRtn = {status:'failed', errorMessage: err.message};
|
||||
if(err.name) errRtn.name = err.name
|
||||
|
||||
if(err.name && 'NotAllowedError' === err.name){
|
||||
const nowtm = (new Date()).getTime()
|
||||
if(nowtm > process_time_limit){
|
||||
@@ -583,9 +739,31 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
||||
}else{
|
||||
errRtn.errCode = fido2LibErrCodes.user_canceled
|
||||
}
|
||||
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||
|
||||
} else if(err.name && 'InvalidStateError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.invalid_state
|
||||
} else if(err.name && 'AbortError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.abort
|
||||
} else if(err.name && 'NotSupportedError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.not_supported
|
||||
} else if(err.name && 'SecurityError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.security
|
||||
} else if(err.name && 'NetworkError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.network
|
||||
} else if(err.name && 'ConstraintError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.constraint
|
||||
} else if(err.name && 'NotReadableError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.not_readable
|
||||
} else if(err.name && 'EncodingError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.encoding
|
||||
} else if(err.name && 'DataError' === err.name){
|
||||
errRtn.errCode = fido2LibErrCodes.data_error
|
||||
} else {
|
||||
errRtn.errCode = fido2LibErrCodes.unknown
|
||||
}
|
||||
|
||||
return errRtn;
|
||||
} finally {
|
||||
window._fido2_pending_request = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
268
files/fido2-ui-sdk.css
Normal file
268
files/fido2-ui-sdk.css
Normal file
@@ -0,0 +1,268 @@
|
||||
.fido2-sdk-modal .modal-dialog {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.fido2-sdk-modal .modal-content {
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.fido2-sdk-card {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.fido2-sdk-header {
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.fido2-sdk-logo {
|
||||
max-height: 40px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.fido2-sdk-container {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.fido2-sdk-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.fido2-sdk-text {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
}
|
||||
|
||||
.fido2-sdk-table {
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.fido2-sdk-table th {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
padding: 12px 16px;
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
}
|
||||
|
||||
.fido2-sdk-table td {
|
||||
padding: 12px 16px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.fido2-sdk-btn {
|
||||
padding: 10px 24px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fido2-sdk-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.fido2-sdk-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.fido2-sdk-btn-primary {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.fido2-sdk-status-badge {
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fido2-sdk-user-info {
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
background-color: #e7f1ff;
|
||||
border-left: 4px solid #0d6efd;
|
||||
}
|
||||
|
||||
.fido2-sdk-table .text-danger {
|
||||
color: #dc3545 !important;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.fido2-sdk-table .text-danger:hover {
|
||||
color: #c82333 !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.fido2-sdk-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.fido2-sdk-modal .modal-dialog {
|
||||
margin: 10px;
|
||||
max-width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.fido2-sdk-table th,
|
||||
.fido2-sdk-table td {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fido2-sdk-btn {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fido2-sdk-logo {
|
||||
max-height: 30px;
|
||||
}
|
||||
|
||||
.fido2-sdk-status-badge {
|
||||
width: 100%;
|
||||
order: 3;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.fido2-sdk-header .btn-close {
|
||||
order: 2;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.fido2-sdk-header .modal-title {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.fido2-sdk-header .fido2-sdk-logo {
|
||||
order: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fido2-sdk-loading {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #fff;
|
||||
animation: fido2-sdk-spin 1s ease-in-out infinite;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
@keyframes fido2-sdk-spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.fido2-sdk-device-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: #e7f1ff;
|
||||
color: #0d6efd;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.fido2-sdk-empty-state {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.fido2-sdk-empty-state i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.fido2-sdk-empty-state p {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.fido2-sdk-tooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.fido2-sdk-tooltip::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
padding: 8px 12px;
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.fido2-sdk-tooltip:hover::after {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.fido2-sdk-btn-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fido2-sdk-btn-group .btn {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#fido2AddDeviceBtn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.fido2-sdk-alert {
|
||||
padding: 12px 16px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
border-left: 4px solid;
|
||||
}
|
||||
|
||||
.fido2-sdk-alert-success {
|
||||
background-color: #d4edda;
|
||||
border-color: #28a745;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.fido2-sdk-alert-error {
|
||||
background-color: #f8d7da;
|
||||
border-color: #dc3545;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.fido2-sdk-alert-info {
|
||||
background-color: #d1ecf1;
|
||||
border-color: #17a2b8;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.fido2-sdk-alert-warning {
|
||||
background-color: #fff3cd;
|
||||
border-color: #ffc107;
|
||||
color: #856404;
|
||||
}
|
||||
2702
files/fido2-ui-sdk.js
Normal file
2702
files/fido2-ui-sdk.js
Normal file
File diff suppressed because it is too large
Load Diff
59
login.html
59
login.html
@@ -47,7 +47,6 @@
|
||||
<!--? 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/jquery.js"></script>
|
||||
<script src="files/popper.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/perfect-scrollbar.js"></script>
|
||||
@@ -62,7 +61,7 @@
|
||||
// For stand alone: 'https://local.dqj-macpro.com'
|
||||
// For proxy: set 'https://mac-air-m2.dqj-home.com'
|
||||
-->
|
||||
setFidoServerURL('https://mac-air-m2.dqj-home.com');//'https://fido2.amipro.me');
|
||||
setFidoServerURL('https://local.dqj-macpro.com');//'https://fido2.amipro.me');
|
||||
|
||||
const i18n_messages = new Map();
|
||||
|
||||
@@ -114,6 +113,42 @@
|
||||
lang_map.set("ja", "パスワードレス ログイン");
|
||||
i18n_messages.set("title_fido2_login", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "This sample is production-oriented.");
|
||||
lang_map.set("zh-CN", "此示例面向生产环境。");
|
||||
lang_map.set("ja", "このサンプルは本番指向です。");
|
||||
i18n_messages.set("msg_prod_note_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Handles real-world browser differences");
|
||||
lang_map.set("zh-CN", "处理真实世界的浏览器差异");
|
||||
lang_map.set("ja", "実際のブラウザー差異に対応");
|
||||
i18n_messages.set("msg_prod_note_1", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Uses recommended WebAuthn options");
|
||||
lang_map.set("zh-CN", "使用推荐的 WebAuthn 选项");
|
||||
lang_map.set("ja", "推奨される WebAuthn オプションを使用");
|
||||
i18n_messages.set("msg_prod_note_2", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Mirrors production flow (RP ID, challenge, verification)");
|
||||
lang_map.set("zh-CN", "模拟生产流程(RP ID、质询、验证)");
|
||||
lang_map.set("ja", "本番フロー(RP ID、チャレンジ、検証)を再現");
|
||||
i18n_messages.set("msg_prod_note_3", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Safe to use as a starting point");
|
||||
lang_map.set("zh-CN", "可安全作为起点使用");
|
||||
lang_map.set("ja", "スターターとして安全に利用可能");
|
||||
i18n_messages.set("msg_prod_note_4", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "15-minute integration guide");
|
||||
lang_map.set("zh-CN", "15分钟接入指南");
|
||||
lang_map.set("ja", "15分での導入ガイド");
|
||||
i18n_messages.set("msg_integration_link", lang_map);
|
||||
|
||||
window.onload = function() {
|
||||
logoutFido2UserSession();
|
||||
|
||||
@@ -131,13 +166,12 @@
|
||||
}
|
||||
|
||||
async function authenticate(){
|
||||
var uid = $('#uid').val()
|
||||
var uid = document.getElementById('uid').value
|
||||
if(uid && 0==uid.length)uid=null
|
||||
|
||||
const result = await authenticateFido2(uid)
|
||||
|
||||
if(result.status === 'ok'){
|
||||
//Set your logged in status and jump to user's top page.
|
||||
location.href = 'devices.html?uid='+result.username
|
||||
}else{
|
||||
errProcessFido2(result)
|
||||
@@ -145,9 +179,9 @@
|
||||
}
|
||||
|
||||
function checkInput(){
|
||||
const uid = $('#uid').val()
|
||||
const uid = document.getElementById('uid').value
|
||||
if(!uid || 0>=uid.length){
|
||||
alert($('#msg_uid_input').html())
|
||||
alert(document.getElementById('msg_uid_input').textContent)
|
||||
return false;
|
||||
}else return true;
|
||||
}
|
||||
@@ -173,6 +207,19 @@
|
||||
<h4 class="mb-2" id="msg_welcome">Welcome to amiPro sample site!</h4>
|
||||
<p class="mb-4" id="msg_intro">Please sign-in to your account and start the adventure</p>
|
||||
|
||||
<div class="mb-4">
|
||||
<p class="fw-semibold mb-2" id="msg_prod_note_title">This sample is production-oriented.</p>
|
||||
<ul class="ps-3 mb-4" style="list-style: disc;">
|
||||
<li id="msg_prod_note_1">Handles real-world browser differences</li>
|
||||
<li id="msg_prod_note_2">Uses recommended WebAuthn options</li>
|
||||
<li id="msg_prod_note_3">Mirrors production flow (RP ID, challenge, verification)</li>
|
||||
<li id="msg_prod_note_4">Safe to use as a starting point</li>
|
||||
</ul>
|
||||
<p class="mb-4">
|
||||
<a id="msg_integration_link" href="https://www.amipro.me/integration.html" target="_blank" rel="noopener">30-minute integration guide</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="msg_uid_input" style="display:none;">Input User ID, please!</div>
|
||||
|
||||
<form class="mb-3" action="devices.html" method="GET" onsubmit="return checkInput();">
|
||||
|
||||
936
modal-demo.html
Normal file
936
modal-demo.html
Normal file
@@ -0,0 +1,936 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FIDO2 UI SDK - Modal Demo</title>
|
||||
|
||||
<link rel="stylesheet" href="files/core.css">
|
||||
<link rel="stylesheet" href="files/theme-default.css">
|
||||
<link rel="stylesheet" href="files/demo.css">
|
||||
<link rel="stylesheet" href="files/boxicons.css">
|
||||
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
color: #333;
|
||||
margin: 0 0 10px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
color: #666;
|
||||
margin: 0 0 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
color: #333;
|
||||
margin: 0 0 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.demo-section p {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-btn {
|
||||
padding: 12px 32px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-right: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.demo-btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demo-btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||
}
|
||||
|
||||
.demo-btn-secondary {
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.demo-btn-secondary:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.demo-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.feature-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.feature-list i {
|
||||
color: #28a745;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#log-container {
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.log-entry:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #569cd6;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.log-type-info {
|
||||
color: #4ec9b0;
|
||||
}
|
||||
|
||||
.log-type-success {
|
||||
color: #6a9955;
|
||||
}
|
||||
|
||||
.log-type-error {
|
||||
color: #f48771;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="files/popper.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/dfido2-lib.js"></script>
|
||||
<script src="files/fido2-ui-sdk.js"></script>
|
||||
<script src="files/amipro_utils.js"></script>
|
||||
|
||||
<script>
|
||||
const i18n_messages = new Map();
|
||||
|
||||
var lang_map = new Map();
|
||||
lang_map.set("en-US", "🔐 FIDO2 UI SDK - Modal Demo");
|
||||
lang_map.set("zh-CN", "🔐 FIDO2 UI SDK - 模态框演示");
|
||||
lang_map.set("ja", "🔐 FIDO2 UI SDK - モーダル デモ");
|
||||
i18n_messages.set("msg_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Demonstrate how to use FIDO2 UI SDK to manage devices in a popup window");
|
||||
lang_map.set("zh-CN", "演示如何使用 FIDO2 UI SDK 在弹出窗口中管理设备");
|
||||
lang_map.set("ja", "FIDO2 UI SDK を使用してポップアップ ウィンドウでデバイスを管理する方法をデモします");
|
||||
i18n_messages.set("msg_subtitle", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "👤 Step1: Login");
|
||||
lang_map.set("zh-CN", "👤 步骤1:登录");
|
||||
lang_map.set("ja", "👤 ステップ1:ログイン");
|
||||
i18n_messages.set("msg_section_login_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Click the button below to open the login modal:");
|
||||
lang_map.set("zh-CN", "点击下面的按钮打开登录模态框:");
|
||||
lang_map.set("ja", "以下のボタンを押してログインモーダルを開いてください:");
|
||||
i18n_messages.set("msg_login_desc", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Login");
|
||||
lang_map.set("zh-CN", "登录");
|
||||
lang_map.set("ja", "ログイン");
|
||||
i18n_messages.set("msg_btn_passkey_login", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Custom Style");
|
||||
lang_map.set("zh-CN", "自定义样式");
|
||||
lang_map.set("ja", "カスタム スタイル");
|
||||
i18n_messages.set("msg_btn_password_login", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Logout");
|
||||
lang_map.set("zh-CN", "退出登录");
|
||||
lang_map.set("ja", "ログアウト");
|
||||
i18n_messages.set("msg_btn_logout", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Logged out successfully");
|
||||
lang_map.set("zh-CN", "退出登录成功");
|
||||
lang_map.set("ja", "ログアウトしました");
|
||||
i18n_messages.set("msg_logout_success", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Please login first to access device management");
|
||||
lang_map.set("zh-CN", "请先登录以访问设备管理");
|
||||
lang_map.set("ja", "デバイス管理にアクセスするにはまずログインしてください");
|
||||
i18n_messages.set("msg_login_required", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Login successful");
|
||||
lang_map.set("zh-CN", "登录成功");
|
||||
lang_map.set("ja", "ログイン成功");
|
||||
i18n_messages.set("msg_login_success", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Opening login modal...");
|
||||
lang_map.set("zh-CN", "打开登录模态框...");
|
||||
lang_map.set("ja", "ログインモーダルを開いています...");
|
||||
i18n_messages.set("msg_log_open_login", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Opening password login modal...");
|
||||
lang_map.set("zh-CN", "打开密码登录模态框...");
|
||||
lang_map.set("ja", "パスワードログイン模ーダルを開いています...");
|
||||
i18n_messages.set("msg_log_open_password_login", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "📌 Step2: Device Management");
|
||||
lang_map.set("zh-CN", "📌 步骤2:设备管理");
|
||||
lang_map.set("ja", "📌 ステップ2: デバイス管理");
|
||||
i18n_messages.set("msg_section_quick_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Login first by Step1, then manage your FIDO2 devices");
|
||||
lang_map.set("zh-CN", "先在步骤1登录,再管理您的FIDO2设备");
|
||||
lang_map.set("ja", "まずステップ1でログインして、FIDO2デバイスを管理します");
|
||||
i18n_messages.set("msg_quick_desc", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Manage Devices");
|
||||
lang_map.set("zh-CN", "管理设备");
|
||||
lang_map.set("ja", "デバイス管理");
|
||||
i18n_messages.set("msg_btn_manage", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Custom Style");
|
||||
lang_map.set("zh-CN", "自定义样式");
|
||||
lang_map.set("ja", "カスタム スタイル");
|
||||
i18n_messages.set("msg_btn_custom", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "💻 Code Examples");
|
||||
lang_map.set("zh-CN", "💻 代码示例");
|
||||
lang_map.set("ja", "💻 コード例");
|
||||
i18n_messages.set("msg_section_code_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Simplest integration:");
|
||||
lang_map.set("zh-CN", "最简单的集成方式:");
|
||||
lang_map.set("ja", "最も簡単な統合:");
|
||||
i18n_messages.set("msg_code_simple", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "With theme customization:");
|
||||
lang_map.set("zh-CN", "带主题定制:");
|
||||
lang_map.set("ja", "テーマのカスタマイズあり:");
|
||||
i18n_messages.set("msg_code_theme", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "✨ Features");
|
||||
lang_map.set("zh-CN", "✨ 功能特性");
|
||||
lang_map.set("ja", "✨ 機能");
|
||||
i18n_messages.set("msg_section_features_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Popup mode, stay on current page");
|
||||
lang_map.set("zh-CN", "弹出窗口模式,不离开当前页面");
|
||||
lang_map.set("ja", "ポップアップ モードで現在のページに留まる");
|
||||
i18n_messages.set("msg_feature_1", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Auto FIDO2/Passkey authentication");
|
||||
lang_map.set("zh-CN", "自动 FIDO2/Passkey 认证");
|
||||
lang_map.set("ja", "自動 FIDO2/Passkey 認証");
|
||||
i18n_messages.set("msg_feature_2", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Password login fallback option");
|
||||
lang_map.set("zh-CN", "密码登录回退选项");
|
||||
lang_map.set("ja", "パスワード ログイン フォールバック");
|
||||
i18n_messages.set("msg_feature_3", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Add/delete FIDO2 devices support");
|
||||
lang_map.set("zh-CN", "支持添加/删除 FIDO2 设备");
|
||||
lang_map.set("ja", "FIDO2 デバイスの追加/削除サポート");
|
||||
i18n_messages.set("msg_feature_4", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Real-time device list display");
|
||||
lang_map.set("zh-CN", "实时显示设备列表");
|
||||
lang_map.set("ja", "リアルタイム デバイスリスト表示");
|
||||
i18n_messages.set("msg_feature_5", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Session status monitoring");
|
||||
lang_map.set("zh-CN", "会话状态监控");
|
||||
lang_map.set("ja", "セッション ステータス監視");
|
||||
i18n_messages.set("msg_feature_6", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Theme color and style customization");
|
||||
lang_map.set("zh-CN", "主题色和样式定制");
|
||||
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
|
||||
i18n_messages.set("msg_feature_7", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Multi-language support (English/Japanese/Chinese)");
|
||||
lang_map.set("zh-CN", "多语言支持(英/日/中)");
|
||||
lang_map.set("ja", "多言語対応(英/日/中)");
|
||||
i18n_messages.set("msg_feature_8", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Event callback system");
|
||||
lang_map.set("zh-CN", "事件回调系统");
|
||||
lang_map.set("ja", "イベント コールバック システム");
|
||||
i18n_messages.set("msg_feature_9", lang_map);
|
||||
lang_map.set("zh-CN", "主题色和样式定制");
|
||||
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
|
||||
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.set("en-US", "Multi-language support (English/Japanese/Chinese)");
|
||||
lang_map.set("zh-CN", "多语言支持(英/日/中)");
|
||||
lang_map.set("ja", "多言語対応(英/日/中)");
|
||||
i18n_messages.set("msg_feature_8", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Event callback system");
|
||||
lang_map.set("zh-CN", "事件回调系统");
|
||||
lang_map.set("ja", "イベント コールバック システム");
|
||||
i18n_messages.set("msg_feature_9", lang_map);
|
||||
lang_map.set("zh-CN", "多语言支持(英/日/中)");
|
||||
lang_map.set("ja", "多言語対応(英/日/中)");
|
||||
i18n_messages.set("msg_feature_8", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Event callback system");
|
||||
lang_map.set("zh-CN", "事件回调系统");
|
||||
lang_map.set("ja", "イベント コールバック システム");
|
||||
i18n_messages.set("msg_feature_9", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "📊 Event Log");
|
||||
lang_map.set("zh-CN", "📊 事件日志");
|
||||
lang_map.set("ja", "📊 イベント ログ");
|
||||
i18n_messages.set("msg_section_log_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Clear Log");
|
||||
lang_map.set("zh-CN", "清空日志");
|
||||
lang_map.set("ja", "ログをクリア");
|
||||
i18n_messages.set("msg_btn_clear", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Opening default device manager...");
|
||||
lang_map.set("zh-CN", "打开默认设备管理器...");
|
||||
lang_map.set("ja", "デフォルト デバイス マネージャーを開いています...");
|
||||
i18n_messages.set("msg_log_open_default", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Opening custom style device manager...");
|
||||
lang_map.set("zh-CN", "打开自定义样式设备管理器...");
|
||||
lang_map.set("ja", "カスタム スタイル デバイス マネージャーを開いています...");
|
||||
i18n_messages.set("msg_log_open_custom", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device manager initialized successfully");
|
||||
lang_map.set("zh-CN", "设备管理器初始化完成");
|
||||
lang_map.set("ja", "デバイス マネージャーの初期化が完了しました");
|
||||
i18n_messages.set("msg_log_init", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Custom style device manager initialized successfully");
|
||||
lang_map.set("zh-CN", "自定义样式设备管理器初始化完成");
|
||||
lang_map.set("ja", "カスタム スタイル デバイス マネージャーの初期化が完了しました");
|
||||
i18n_messages.set("msg_log_init_custom", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device added successfully");
|
||||
lang_map.set("zh-CN", "设备添加成功");
|
||||
lang_map.set("ja", "デバイスの追加が完了しました");
|
||||
i18n_messages.set("msg_log_device_added", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device deleted successfully");
|
||||
lang_map.set("zh-CN", "设备删除成功");
|
||||
lang_map.set("ja", "デバイスの削除が完了しました");
|
||||
i18n_messages.set("msg_log_device_deleted", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device list loaded, total");
|
||||
lang_map.set("zh-CN", "设备列表加载完成,共");
|
||||
lang_map.set("ja", "デバイスリストが読み込まれました。合計");
|
||||
i18n_messages.set("msg_log_devices_loaded", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "devices");
|
||||
lang_map.set("zh-CN", "个设备");
|
||||
lang_map.set("ja", "デバイス");
|
||||
i18n_messages.set("msg_log_devices_suffix", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "An error occurred");
|
||||
lang_map.set("zh-CN", "发生错误");
|
||||
lang_map.set("ja", "エラーが発生しました");
|
||||
i18n_messages.set("msg_log_error", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device manager closed");
|
||||
lang_map.set("zh-CN", "设备管理器已关闭");
|
||||
lang_map.set("ja", "デバイス マネージャーが閉じられました");
|
||||
i18n_messages.set("msg_log_closed", lang_map);
|
||||
|
||||
window.onload = function() {
|
||||
if (window.location.hostname === '127.0.0.1') {
|
||||
alert('本地测试请使用 localhost');
|
||||
}
|
||||
setI18NText(i18n_messages);
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="demo-header">
|
||||
<h1 id="msg_title">🔐 FIDO2 UI SDK - Modal Demo</h1>
|
||||
<p id="msg_subtitle">Demonstrate how to use FIDO2 UI SDK to manage devices in a popup window</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_login_title">👤 Step1: Login</h2>
|
||||
<p id="msg_login_desc">Click the button below to open the login modal:</p>
|
||||
<button class="demo-btn demo-btn-primary" onclick="openPasskeyLogin()">
|
||||
<i class="bx bx-key"></i> <span id="msg_btn_passkey_login">Passkey Login</span>
|
||||
</button>
|
||||
<button class="demo-btn demo-btn-secondary" onclick="openPasswordLogin()">
|
||||
<i class="bx bx-palette"></i> <span id="msg_btn_password_login">Custom Style</span>
|
||||
</button>
|
||||
<button class="demo-btn demo-btn-secondary" id="btn-logout" onclick="logout()" disabled>
|
||||
<i class="bx bx-log-out"></i> <span id="msg_btn_logout">Logout</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_quick_title">📌 Step2: Device Management</h2>
|
||||
<p id="msg_quick_desc">Step1: Login first, then manage your FIDO2 devices</p>
|
||||
<button class="demo-btn demo-btn-primary" id="btn-manage-devices" onclick="openDeviceManager()" disabled>
|
||||
<i class="bx bx-device"></i> <span id="msg_btn_manage">Manage Devices</span>
|
||||
</button>
|
||||
<button class="demo-btn demo-btn-secondary" id="btn-custom-style" onclick="openCustomDeviceManager()" disabled>
|
||||
<i class="bx bx-palette"></i> <span id="msg_btn_custom">Custom Style</span>
|
||||
</button>
|
||||
<p id="msg_login_required" style="color: #999; margin-top: 10px;">Please login first to access device management</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_log_title">📊 Event Log</h2>
|
||||
<div id="log-container"></div>
|
||||
<button class="demo-btn demo-btn-secondary" onclick="clearLog()" style="margin-top: 15px;">
|
||||
<span id="msg_btn_clear">Clear Log</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_code_title">💻 Code Examples</h2>
|
||||
|
||||
<p id="msg_js_import_examples">JS Import Examples:</p>
|
||||
<div class="code-block">
|
||||
<code><script src="files/popper.js"></script><br>
|
||||
<script src="files/bootstrap.js"></script><br>
|
||||
<script src="files/dfido2-lib.js"></script><br>
|
||||
<script src="files/fido2-ui-sdk.js"></script><br>
|
||||
<script src="files/amipro_utils.js"></script></code>
|
||||
</div>
|
||||
|
||||
<p id="msg_code_login_default">Login Default:</p>
|
||||
<div class="code-block">
|
||||
<code>// Login Default<br>
|
||||
Fido2UIManager.renderLogin({<br>
|
||||
container: '#login-container',<br>
|
||||
mode: 'modal',<br>
|
||||
serverUrl: SERVER_URL,<br>
|
||||
language: CURRENT_LANG<br>
|
||||
});</code>
|
||||
</div>
|
||||
|
||||
<p id="msg_code_login_custom">Login Custom:</p>
|
||||
<div class="code-block">
|
||||
<code>// Login with Custom Theme<br>
|
||||
Fido2UIManager.renderLogin({<br>
|
||||
container: '#login-container',<br>
|
||||
mode: 'modal',<br>
|
||||
serverUrl: SERVER_URL,<br>
|
||||
language: 'ja',<br>
|
||||
theme: {<br>
|
||||
logo: 'files/favicon.ico',<br>
|
||||
primaryColor: '#ce59d9',<br>
|
||||
backgroundColor: '#faf5ff',<br>
|
||||
textColor: '#6b21a8',<br>
|
||||
borderRadius: '12px'<br>
|
||||
},<br>
|
||||
features: {<br>
|
||||
autoAuth: false,<br>
|
||||
enablePasswordLogin: true,<br>
|
||||
autoShowPassword: true,<br>
|
||||
maxPasswordAttempts: 3<br>
|
||||
},<br>
|
||||
callbacks: {<br>
|
||||
onFido2Success: function(username, session) {<br>
|
||||
// Handle login success<br>
|
||||
},<br>
|
||||
onFido2Error: function(error) {<br>
|
||||
// Handle error<br>
|
||||
}<br>
|
||||
}<br>
|
||||
});</code>
|
||||
</div>
|
||||
|
||||
<p id="msg_code_device_default">Device Manager Default:</p>
|
||||
<div class="code-block">
|
||||
<code>// Device Manager Default<br>
|
||||
Fido2UIManager.renderDeviceManager({<br>
|
||||
userId: currentUserId,<br>
|
||||
container: '#device-container',<br>
|
||||
mode: 'modal',<br>
|
||||
serverUrl: SERVER_URL,<br>
|
||||
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>
|
||||
userId: currentUserId,<br>
|
||||
container: '#device-container',<br>
|
||||
mode: 'modal',<br>
|
||||
serverUrl: SERVER_URL,<br>
|
||||
language: 'ja',<br>
|
||||
theme: {<br>
|
||||
logo: 'files/favicon.ico',<br>
|
||||
primaryColor: '#ce59d9',<br>
|
||||
backgroundColor: '#faf5ff',<br>
|
||||
textColor: '#6b21a8',<br>
|
||||
borderRadius: '12px'<br>
|
||||
},<br>
|
||||
features: {<br>
|
||||
showAddButton: true,<br>
|
||||
showDeleteButton: true,<br>
|
||||
showUserInfo: true,<br>
|
||||
showSessionStatus: true<br>
|
||||
},<br>
|
||||
callbacks: {<br>
|
||||
onInit: function(manager) {<br>
|
||||
// Manager initialized<br>
|
||||
},<br>
|
||||
onDeviceAdded: function(device) {<br>
|
||||
// Device added<br>
|
||||
},<br>
|
||||
onDeviceDeleted: function(deviceId) {<br>
|
||||
// Device deleted<br>
|
||||
},<br>
|
||||
onClose: function() {<br>
|
||||
// Manager closed<br>
|
||||
}<br>
|
||||
}<br>
|
||||
});</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_features_title">✨ Features</h2>
|
||||
<ul class="feature-list">
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_1">Popup mode, stay on current page</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_2">Auto FIDO2/Passkey authentication</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_3">Password login fallback option</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_4">Add/delete FIDO2 devices support</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_5">Real-time device list display</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_6">Session status monitoring</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_7">Theme color and style customization</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_8">Multi-language support (English/Japanese/Chinese)</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_9">Event callback system</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="login-container"></div>
|
||||
<div id="device-container"></div>
|
||||
|
||||
<script>
|
||||
const SERVER_URL = 'https://local.dqj-macpro.com';//'https://fido2.amipro.me';
|
||||
|
||||
let currentUserId = null;
|
||||
let isLoggedIn = false;
|
||||
let loginManager = null;
|
||||
|
||||
function getBrowserLanguage() {
|
||||
const lang = window.navigator.language || window.navigator.userLanguage;
|
||||
const supportedLangs = ['en-US', 'zh-CN', 'ja'];
|
||||
if (supportedLangs.includes(lang)) {
|
||||
return lang;
|
||||
}
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
const CURRENT_LANG = getBrowserLanguage();
|
||||
|
||||
function enableDeviceManagementButtons() {
|
||||
document.getElementById('btn-manage-devices').disabled = false;
|
||||
document.getElementById('btn-custom-style').disabled = false;
|
||||
document.getElementById('msg_login_required').style.display = 'none';
|
||||
document.getElementById('btn-logout').disabled = false;
|
||||
}
|
||||
|
||||
function disableDeviceManagementButtons() {
|
||||
document.getElementById('btn-manage-devices').disabled = true;
|
||||
document.getElementById('btn-custom-style').disabled = true;
|
||||
document.getElementById('msg_login_required').style.display = 'block';
|
||||
document.getElementById('btn-logout').disabled = true;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
Fido2UIManager.logout();
|
||||
currentUserId = null;
|
||||
isLoggedIn = false;
|
||||
log('success', getI18NText(i18n_messages, 'msg_logout_success'));
|
||||
disableDeviceManagementButtons();
|
||||
}
|
||||
|
||||
function openPasskeyLogin() {
|
||||
clearLog();
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_open_login'));
|
||||
|
||||
if (loginManager) {
|
||||
loginManager.destroy();
|
||||
}
|
||||
|
||||
loginManager = Fido2UIManager.renderLogin({
|
||||
container: '#login-container',
|
||||
mode: 'modal',
|
||||
serverUrl: SERVER_URL,
|
||||
language: CURRENT_LANG,
|
||||
features: {
|
||||
autoAuth: true,
|
||||
enablePasswordLogin: true,
|
||||
autoShowPassword: false,
|
||||
maxPasswordAttempts: 3,
|
||||
showRemainingAttempts: true,
|
||||
},
|
||||
callbacks: {
|
||||
onFido2Success: function(username, session) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_login_success') + ': ' + username);
|
||||
currentUserId = username;
|
||||
isLoggedIn = true;
|
||||
enableDeviceManagementButtons();
|
||||
openDeviceManager();
|
||||
},
|
||||
onFido2Error: function(error) {
|
||||
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + error.message);
|
||||
},
|
||||
onPasswordLogin: function(userId, password) {
|
||||
return new Promise(function(resolve) {
|
||||
log('info', 'Password login for user: ' + userId);
|
||||
currentUserId = userId;
|
||||
isLoggedIn = true;
|
||||
enableDeviceManagementButtons();
|
||||
resolve(true);
|
||||
});
|
||||
},
|
||||
onPasswordExhausted: function(userId, attemptCount, maxAttempts) {
|
||||
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + getI18NText(i18n_messages, 'msg_password_exhausted'));
|
||||
},
|
||||
onLoginClosed: function() {
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_closed'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openPasswordLogin() {
|
||||
clearLog();
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_open_password_login'));
|
||||
|
||||
if (loginManager) {
|
||||
loginManager.destroy();
|
||||
}
|
||||
|
||||
loginManager = Fido2UIManager.renderLogin({
|
||||
container: '#login-container',
|
||||
mode: 'modal',
|
||||
serverUrl: SERVER_URL,
|
||||
language: CURRENT_LANG,
|
||||
theme: {
|
||||
logo: 'files/favicon.ico',
|
||||
primaryColor: '#ce59d9',
|
||||
backgroundColor: '#faf5ff',
|
||||
textColor: '#6b21a8',
|
||||
borderRadius: '12px'
|
||||
},
|
||||
features: {
|
||||
autoAuth: false,
|
||||
enablePasswordLogin: true,
|
||||
autoShowPassword: true,
|
||||
maxPasswordAttempts: 3,
|
||||
showRemainingAttempts: true,
|
||||
},
|
||||
callbacks: {
|
||||
onFido2Success: function(username, session) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_login_success') + ': ' + username);
|
||||
currentUserId = username;
|
||||
isLoggedIn = true;
|
||||
enableDeviceManagementButtons();
|
||||
},
|
||||
onFido2Error: function(error) {
|
||||
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + error.message);
|
||||
},
|
||||
onPasswordLogin: function(userId, password) {
|
||||
return new Promise(function(resolve) {
|
||||
log('info', 'Password login for user: ' + userId);
|
||||
currentUserId = userId;
|
||||
isLoggedIn = true;
|
||||
enableDeviceManagementButtons();
|
||||
resolve(true);
|
||||
});
|
||||
},
|
||||
onPasswordExhausted: function(userId, attemptCount, maxAttempts) {
|
||||
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + getI18NText(i18n_messages, 'msg_password_exhausted'));
|
||||
},
|
||||
onLoginClosed: function() {
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_closed'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openDeviceManager() {
|
||||
if (!isLoggedIn) {
|
||||
log('info', getI18NText(i18n_messages, 'msg_login_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
clearLog();
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_open_default'));
|
||||
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
userId: currentUserId,
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: SERVER_URL,
|
||||
language: CURRENT_LANG,
|
||||
callbacks: {
|
||||
onInit: function(manager) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_log_init'));
|
||||
},
|
||||
onDeviceAdded: function(device) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_log_device_added') + ': ' + JSON.stringify(device));
|
||||
},
|
||||
onDeviceDeleted: function(deviceId) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_log_device_deleted') + ': ' + deviceId);
|
||||
},
|
||||
onDeviceListLoaded: function(devices) {
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_devices_loaded') + ' ' + devices.length + ' ' + getI18NText(i18n_messages, 'msg_log_devices_suffix'));
|
||||
},
|
||||
onError: function(error) {
|
||||
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + error.message);
|
||||
},
|
||||
onClose: function() {
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_closed'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openCustomDeviceManager() {
|
||||
if (!isLoggedIn) {
|
||||
log('info', getI18NText(i18n_messages, 'msg_login_required'));
|
||||
return;
|
||||
}
|
||||
|
||||
clearLog();
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_open_custom'));
|
||||
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
userId: currentUserId,
|
||||
container: '#device-container',
|
||||
mode: 'modal',
|
||||
serverUrl: SERVER_URL,
|
||||
language: CURRENT_LANG,
|
||||
theme: {
|
||||
logo: 'files/favicon.ico',
|
||||
primaryColor: '#ce59d9',
|
||||
backgroundColor: '#faf5ff',
|
||||
textColor: '#6b21a8',
|
||||
borderRadius: '12px'
|
||||
},
|
||||
features: {
|
||||
showAddButton: true,
|
||||
showDeleteButton: true,
|
||||
showUserInfo: true,
|
||||
showSessionStatus: true
|
||||
},
|
||||
callbacks: {
|
||||
onInit: function(manager) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_log_init_custom'));
|
||||
},
|
||||
onDeviceAdded: function(device) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_log_device_added') + ': ' + JSON.stringify(device));
|
||||
},
|
||||
onDeviceDeleted: function(deviceId) {
|
||||
log('success', getI18NText(i18n_messages, 'msg_log_device_deleted') + ': ' + deviceId);
|
||||
},
|
||||
onDeviceListLoaded: function(devices) {
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_devices_loaded') + ' ' + devices.length + ' ' + getI18NText(i18n_messages, 'msg_log_devices_suffix'));
|
||||
},
|
||||
onError: function(error) {
|
||||
log('error', getI18NText(i18n_messages, 'msg_log_error') + ': ' + error.message);
|
||||
},
|
||||
onClose: function() {
|
||||
log('info', getI18NText(i18n_messages, 'msg_log_closed'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function log(type, message) {
|
||||
const container = document.getElementById('log-container');
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'log-entry';
|
||||
|
||||
const time = new Date().toLocaleTimeString('zh-CN');
|
||||
const typeClass = 'log-type-' + type;
|
||||
|
||||
entry.innerHTML = `<span class="log-time">[${time}]</span><span class="${typeClass}">[${type.toUpperCase()}]</span> ${message}`;
|
||||
container.appendChild(entry);
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
document.getElementById('log-container').innerHTML = '';
|
||||
}
|
||||
</script>
|
||||
<p style="text-align: center; margin-top: 20px;">
|
||||
<a href="https://amipro.me/fido2_top.html" target="_blank" style="display: inline-block; padding: 12px 24px; background: #667eea; color: white; text-decoration: none; border-radius: 8px; font-weight: bold;">
|
||||
https://amipro.me/fido2_top.html
|
||||
</a>
|
||||
</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>
|
||||
</html>
|
||||
535
standalone-demo.html
Normal file
535
standalone-demo.html
Normal file
@@ -0,0 +1,535 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FIDO2 UI SDK - Standalone Demo</title>
|
||||
|
||||
<link rel="stylesheet" href="files/core.css">
|
||||
<link rel="stylesheet" href="files/theme-default.css">
|
||||
<link rel="stylesheet" href="files/demo.css">
|
||||
<link rel="stylesheet" href="files/boxicons.css">
|
||||
<link rel="stylesheet" href="files/fido2-ui-sdk.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.demo-header {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
padding: 40px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-header h1 {
|
||||
color: #333;
|
||||
margin: 0 0 10px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.demo-header p {
|
||||
color: #666;
|
||||
margin: 0 0 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.demo-section h2 {
|
||||
color: #333;
|
||||
margin: 0 0 20px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.demo-section p {
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-btn {
|
||||
padding: 12px 32px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-right: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.demo-btn-primary {
|
||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.demo-btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(240, 147, 251, 0.4);
|
||||
}
|
||||
|
||||
.demo-btn-secondary {
|
||||
background: #f8f9fa;
|
||||
color: #333;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.demo-btn-secondary:hover {
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.code-block code {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.feature-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.feature-list li {
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.feature-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.feature-list i {
|
||||
color: #28a745;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #e7f3ff;
|
||||
border-left: 4px solid #007bff;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.info-box strong {
|
||||
color: #0056b3;
|
||||
}
|
||||
|
||||
.info-box p {
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script src="files/popper.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/dfido2-lib.js"></script>
|
||||
<script src="files/fido2-ui-sdk.js"></script>
|
||||
<script src="files/amipro_utils.js"></script>
|
||||
|
||||
<script>
|
||||
const i18n_messages = new Map();
|
||||
|
||||
var lang_map = new Map();
|
||||
lang_map.set("en-US", "🔐 FIDO2 UI SDK - Standalone Demo");
|
||||
lang_map.set("zh-CN", "🔐 FIDO2 UI SDK - 独立页面演示");
|
||||
lang_map.set("ja", "🔐 FIDO2 UI SDK - スタンドアロン デモ");
|
||||
i18n_messages.set("msg_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Demonstrate how to use FIDO2 UI SDK to manage devices in a standalone page");
|
||||
lang_map.set("zh-CN", "演示如何使用 FIDO2 UI SDK 在独立页面中管理设备");
|
||||
lang_map.set("ja", "FIDO2 UI SDK を使用してスタンドアロン ページでデバイスを管理する方法をデモします");
|
||||
i18n_messages.set("msg_subtitle", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "📌 What is Standalone Page Mode?");
|
||||
lang_map.set("zh-CN", "📌 什么是独立页面模式?");
|
||||
lang_map.set("ja", "📌 スタンドアロン ページ モードとは?");
|
||||
i18n_messages.set("msg_section_mode_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Standalone page mode renders a complete device management interface on the current page, suitable for scenarios requiring a dedicated device management page.");
|
||||
lang_map.set("zh-CN", "独立页面模式会在当前页面渲染完整的设备管理界面,适用于需要独立设备管理页面的场景。");
|
||||
lang_map.set("ja", "スタンドアロン ページ モードは、現在のページに完全なデバイス管理インターフェイスをレンダリングし、専用のデバイス管理ページが必要なシナリオに適しています。");
|
||||
i18n_messages.set("msg_section_mode_desc", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "⚠️ Note:");
|
||||
lang_map.set("zh-CN", "⚠️ 注意:");
|
||||
lang_map.set("ja", "⚠️ 注意:");
|
||||
i18n_messages.set("msg_note", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Standalone page mode will replace the current page content. If your page already has a complete layout (like login.html or devices.html), it is recommended to use modal mode.");
|
||||
lang_map.set("zh-CN", "独立页面模式会替换当前页面的内容。如果您的页面已经加载了完整的布局(例如 login.html 或 devices.html),建议使用模态框模式。");
|
||||
lang_map.set("ja", "スタンドアロン ページ モードは現在のページ内容を置き換えます。ページにすでに完全なレイアウト(login.html や devices.html など)が読み込まれている場合は、モーダル モードの使用をお勧めします。");
|
||||
i18n_messages.set("msg_note_content", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "💻 Code Examples");
|
||||
lang_map.set("zh-CN", "💻 代码示例");
|
||||
lang_map.set("ja", "💻 コード例");
|
||||
i18n_messages.set("msg_section_code_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Simplest standalone integration:");
|
||||
lang_map.set("zh-CN", "最简单的独立页面集成方式:");
|
||||
lang_map.set("ja", "最も簡単なスタンドアロン統合:");
|
||||
i18n_messages.set("msg_code_simple", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "With theme customization:");
|
||||
lang_map.set("zh-CN", "带主题定制:");
|
||||
lang_map.set("ja", "テーマのカスタマイズあり:");
|
||||
i18n_messages.set("msg_code_theme", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "✨ Features");
|
||||
lang_map.set("zh-CN", "✨ 功能特性");
|
||||
lang_map.set("ja", "✨ 機能");
|
||||
i18n_messages.set("msg_section_features_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Standalone page with complete device management interface");
|
||||
lang_map.set("zh-CN", "独立页面,完整的设备管理界面");
|
||||
lang_map.set("ja", "スタンドアロン ページで完全なデバイス管理インターフェイス");
|
||||
i18n_messages.set("msg_feature_1", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Add/delete FIDO2 devices support");
|
||||
lang_map.set("zh-CN", "支持添加/删除 FIDO2 设备");
|
||||
lang_map.set("ja", "FIDO2 デバイスの追加/削除サポート");
|
||||
i18n_messages.set("msg_feature_2", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Real-time device list display");
|
||||
lang_map.set("zh-CN", "实时显示设备列表");
|
||||
lang_map.set("ja", "リアルタイム デバイスリスト表示");
|
||||
i18n_messages.set("msg_feature_3", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Session status monitoring");
|
||||
lang_map.set("zh-CN", "会话状态监控");
|
||||
lang_map.set("ja", "セッション ステータス監視");
|
||||
i18n_messages.set("msg_feature_4", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Theme color and style customization");
|
||||
lang_map.set("zh-CN", "主题色和样式定制");
|
||||
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
|
||||
i18n_messages.set("msg_feature_5", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Multi-language support (Chinese/English/Japanese)");
|
||||
lang_map.set("zh-CN", "多语言支持(中/英/日)");
|
||||
lang_map.set("ja", "多言語対応(中/英/日)");
|
||||
i18n_messages.set("msg_feature_6", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Event callback system");
|
||||
lang_map.set("zh-CN", "事件回调系统");
|
||||
lang_map.set("ja", "イベント コールバック システム");
|
||||
i18n_messages.set("msg_feature_7", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Responsive design, mobile support");
|
||||
lang_map.set("zh-CN", "响应式设计,支持移动端");
|
||||
lang_map.set("ja", "レスポンシブ デザイン、モバイル対応");
|
||||
i18n_messages.set("msg_feature_8", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "🚀 Demo");
|
||||
lang_map.set("zh-CN", "🚀 演示");
|
||||
lang_map.set("ja", "🚀 デモ");
|
||||
i18n_messages.set("msg_section_demo_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Click the buttons below to experience standalone page mode:");
|
||||
lang_map.set("zh-CN", "点击下面的按钮体验独立页面模式:");
|
||||
lang_map.set("ja", "以下のボタンを押してスタンドアロン ページ モードを体験してください:");
|
||||
i18n_messages.set("msg_demo_desc", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Default Style");
|
||||
lang_map.set("zh-CN", "默认样式");
|
||||
lang_map.set("ja", "デフォルト スタイル");
|
||||
i18n_messages.set("msg_btn_default", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Custom Style");
|
||||
lang_map.set("zh-CN", "自定义样式");
|
||||
lang_map.set("ja", "カスタム スタイル");
|
||||
i18n_messages.set("msg_btn_custom", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "💡 Tip:");
|
||||
lang_map.set("zh-CN", "💡 提示:");
|
||||
lang_map.set("ja", "💡 ヒント:");
|
||||
i18n_messages.set("msg_tip", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "After clicking the button, the page will be replaced with the device management interface. To return to this demo page, please refresh the browser.");
|
||||
lang_map.set("zh-CN", "点击按钮后,页面将被替换为设备管理界面。要返回此演示页面,请刷新浏览器。");
|
||||
lang_map.set("ja", "ボタンを押すと、ページはデバイス管理インターフェイスに置き換えられます。このデモ ページに戻るには、ブラウザを更新してください。");
|
||||
i18n_messages.set("msg_tip_content", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "📖 Use Cases");
|
||||
lang_map.set("zh-CN", "📖 使用场景");
|
||||
lang_map.set("ja", "📖 ユースケース");
|
||||
i18n_messages.set("msg_section_usecases_title", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Standalone device management page (similar to existing devices.html)");
|
||||
lang_map.set("zh-CN", "需要独立的设备管理页面(类似现有的 devices.html)");
|
||||
lang_map.set("ja", "専用のデバイス管理ページ(既存の devices.html と同様)");
|
||||
i18n_messages.set("msg_usecase_1", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "User accesses device management directly via link");
|
||||
lang_map.set("zh-CN", "用户需要通过链接直接访问设备管理");
|
||||
lang_map.set("ja", "ユーザーがリンクからデバイス管理に直接アクセス");
|
||||
i18n_messages.set("msg_usecase_2", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device management as a standalone page feature");
|
||||
lang_map.set("zh-CN", "设备管理作为独立的页面功能");
|
||||
lang_map.set("ja", "デバイス管理がスタンドアロン ページ機能として");
|
||||
i18n_messages.set("msg_usecase_3", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Need more screen space to display device information");
|
||||
lang_map.set("zh-CN", "需要更多屏幕空间显示设备信息");
|
||||
lang_map.set("ja", "デバイス情報を表示するためにより多くの画面スペースが必要");
|
||||
i18n_messages.set("msg_usecase_4", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Return to Device Management Page");
|
||||
lang_map.set("zh-CN", "返回设备管理页面");
|
||||
lang_map.set("ja", "デバイス管理ページに戻る");
|
||||
i18n_messages.set("msg_btn_back", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "This operation will replace the current page content. Are you sure you want to continue?");
|
||||
lang_map.set("zh-CN", "此操作将替换当前页面的内容。确定要继续吗?");
|
||||
lang_map.set("ja", "この操作は現在のページ内容を置き換えます。よろしいですか?");
|
||||
i18n_messages.set("msg_confirm", lang_map);
|
||||
|
||||
window.onload = function() {
|
||||
setI18NText(i18n_messages);
|
||||
};
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="demo-header">
|
||||
<h1 id="msg_title">🔐 FIDO2 UI SDK - Standalone Demo</h1>
|
||||
<p id="msg_subtitle">Demonstrate how to use FIDO2 UI SDK to manage devices in a standalone page</p>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_mode_title">📌 What is Standalone Page Mode?</h2>
|
||||
<p id="msg_section_mode_desc">Standalone page mode renders a complete device management interface on the current page, suitable for scenarios requiring a dedicated device management page.</p>
|
||||
<div class="info-box">
|
||||
<strong id="msg_note">⚠️ Note:</strong>
|
||||
<p id="msg_note_content">Standalone page mode will replace the current page content. If your page already has a complete layout (like login.html or devices.html), it is recommended to use modal mode.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_code_title">💻 Code Examples</h2>
|
||||
<p id="msg_code_simple">Simplest standalone integration:</p>
|
||||
<div class="code-block">
|
||||
<code>Fido2UIManager.renderDeviceManager({
|
||||
mode: 'standalone',
|
||||
serverUrl: SERVER_URL
|
||||
});</code>
|
||||
</div>
|
||||
|
||||
<p id="msg_code_theme">With theme customization:</p>
|
||||
<div class="code-block">
|
||||
<code>Fido2UIManager.renderDeviceManager({
|
||||
mode: 'standalone',
|
||||
serverUrl: SERVER_URL,
|
||||
theme: {
|
||||
logo: 'path/to/logo.png',
|
||||
primaryColor: '#f5576c',
|
||||
backgroundColor: '#ffffff'
|
||||
},
|
||||
language: 'zh-CN'
|
||||
});</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_features_title">✨ Features</h2>
|
||||
<ul class="feature-list">
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_1">Standalone page with complete device management interface</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_2">Add/delete FIDO2 devices support</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_3">Real-time device list display</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_4">Session status monitoring</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_5">Theme color and style customization</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_6">Multi-language support (Chinese/English/Japanese)</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_7">Event callback system</span></li>
|
||||
<li><i class="bx bx-check-circle"></i> <span id="msg_feature_8">Responsive design, mobile support</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_demo_title">🚀 Demo</h2>
|
||||
<p id="msg_demo_desc">Click the buttons below to experience standalone page mode:</p>
|
||||
<button class="demo-btn demo-btn-primary" onclick="openStandaloneDefault()">
|
||||
<i class="bx bx-desktop"></i> <span id="msg_btn_default">Default Style</span>
|
||||
</button>
|
||||
<button class="demo-btn demo-btn-secondary" onclick="openStandaloneCustom()">
|
||||
<i class="bx bx-palette"></i> <span id="msg_btn_custom">Custom Style</span>
|
||||
</button>
|
||||
|
||||
<div class="info-box" style="margin-top: 20px;">
|
||||
<strong id="msg_tip">💡 Tip:</strong>
|
||||
<p id="msg_tip_content">After clicking the button, the page will be replaced with the device management interface. To return to this demo page, please refresh the browser.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h2 id="msg_section_usecases_title">📖 Use Cases</h2>
|
||||
<p>Standalone page mode is suitable for the following scenarios:</p>
|
||||
<ul class="feature-list">
|
||||
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_1">Standalone device management page (similar to existing devices.html)</span></li>
|
||||
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_2">User accesses device management directly via link</span></li>
|
||||
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_3">Device management as a standalone page feature</span></li>
|
||||
<li><i class="bx bx-right-arrow"></i> <span id="msg_usecase_4">Need more screen space to display device information</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<a href="devices.html" class="demo-btn demo-btn-secondary">
|
||||
<i class="bx bx-arrow-back"></i> <span id="msg_btn_back">Return to Device Management Page</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const SERVER_URL = 'https://fido2.amipro.me';// 'https://local.dqj-macpro.com';
|
||||
|
||||
function getBrowserLanguage() {
|
||||
const lang = window.navigator.language || window.navigator.userLanguage;
|
||||
const supportedLangs = ['en-US', 'zh-CN', 'ja'];
|
||||
if (supportedLangs.includes(lang)) {
|
||||
return lang;
|
||||
}
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
const CURRENT_LANG = getBrowserLanguage();// 'https://fido2.amipro.me';
|
||||
|
||||
function openStandaloneDefault() {
|
||||
if (!confirm(getI18NText(i18n_messages, 'msg_confirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Opening standalone device manager with default style...');
|
||||
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
userId: 'user@example.com',
|
||||
mode: 'standalone',
|
||||
serverUrl: SERVER_URL,
|
||||
language: CURRENT_LANG,
|
||||
callbacks: {
|
||||
onInit: function(manager) {
|
||||
console.log('Standalone device manager initialized');
|
||||
},
|
||||
onDeviceAdded: function(device) {
|
||||
console.log('Device added:', device);
|
||||
},
|
||||
onDeviceDeleted: function(deviceId) {
|
||||
console.log('Device deleted:', deviceId);
|
||||
},
|
||||
onDeviceListLoaded: function(devices) {
|
||||
console.log('Device list loaded:', devices.length, 'devices');
|
||||
},
|
||||
onError: function(error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function openStandaloneCustom() {
|
||||
if (!confirm(getI18NText(i18n_messages, 'msg_confirm'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Opening standalone device manager with custom style...');
|
||||
|
||||
Fido2UIManager.renderDeviceManager({
|
||||
userId: 'user@example.com',
|
||||
mode: 'standalone',
|
||||
serverUrl: SERVER_URL,
|
||||
language: CURRENT_LANG,
|
||||
theme: {
|
||||
logo: 'files/favicon.ico',
|
||||
primaryColor: '#f5576c',
|
||||
backgroundColor: '#fff5f7',
|
||||
textColor: '#9f1239',
|
||||
borderRadius: '12px'
|
||||
},
|
||||
features: {
|
||||
showAddButton: true,
|
||||
showDeleteButton: true,
|
||||
showUserInfo: true,
|
||||
showSessionStatus: true
|
||||
},
|
||||
callbacks: {
|
||||
onInit: function(manager) {
|
||||
console.log('Custom standalone device manager initialized');
|
||||
},
|
||||
onDeviceAdded: function(device) {
|
||||
console.log('Device added:', device);
|
||||
},
|
||||
onDeviceDeleted: function(deviceId) {
|
||||
console.log('Device deleted:', deviceId);
|
||||
},
|
||||
onDeviceListLoaded: function(devices) {
|
||||
console.log('Device list loaded:', devices.length, 'devices');
|
||||
},
|
||||
onError: function(error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user