Compare commits
2 Commits
main
...
feature/RE
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bd3ca0e2b | ||
|
|
9218662a4b |
@@ -1,127 +0,0 @@
|
|||||||
# FIDO2 UI SDK 修改说明
|
|
||||||
|
|
||||||
## 修改概述
|
|
||||||
|
|
||||||
根据需求,对UI SDK进行了以下修改:
|
|
||||||
|
|
||||||
1. **增加用户ID参数**:renderDeviceManager函数现在需要userId参数
|
|
||||||
2. **删除登录功能**:移除了登录相关的UI和事件处理
|
|
||||||
3. **简化设备管理**:设备操作现在直接使用传入的用户ID
|
|
||||||
|
|
||||||
## 主要修改
|
|
||||||
|
|
||||||
### 1. renderDeviceManager函数
|
|
||||||
|
|
||||||
**修改前:**
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
**修改后:**
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
userId: 'user@example.com', // 新增必需参数
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. DeviceManager修改
|
|
||||||
|
|
||||||
- `addDevice()`: 使用`this.config.userId`代替从sessionStorage获取用户ID
|
|
||||||
- `deleteDevice()`: 使用`this.config.userId`代替从sessionStorage获取用户ID
|
|
||||||
- `checkSession()`: 简化为始终返回true(假设用户已登录)
|
|
||||||
- `getUserId()`: 返回`this.config.userId`
|
|
||||||
|
|
||||||
### 3. 删除的功能
|
|
||||||
|
|
||||||
- 移除了登录按钮的UI渲染
|
|
||||||
- 移除了login事件处理器
|
|
||||||
- 移除了session检查逻辑
|
|
||||||
- 移除了getSessionData方法
|
|
||||||
|
|
||||||
### 4. 错误处理
|
|
||||||
|
|
||||||
如果未提供userId参数,renderDeviceManager会抛出错误:
|
|
||||||
```
|
|
||||||
'userId is required for device management operations'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用示例
|
|
||||||
|
|
||||||
### 模式1:模态框模式
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
userId: 'user@example.com',
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: 'https://your-server.com',
|
|
||||||
language: 'en-US',
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
console.log('Device manager initialized');
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
console.log('Device added:', device);
|
|
||||||
},
|
|
||||||
onDeviceDeleted: function(deviceId) {
|
|
||||||
console.log('Device deleted:', deviceId);
|
|
||||||
},
|
|
||||||
onDeviceListLoaded: function(devices) {
|
|
||||||
console.log('Devices loaded:', devices);
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
console.error('Error:', error);
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
console.log('Device manager closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 模式2:独立页面模式
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
userId: 'user@example.com',
|
|
||||||
mode: 'standalone',
|
|
||||||
serverUrl: 'https://your-server.com',
|
|
||||||
language: 'zh-CN',
|
|
||||||
theme: {
|
|
||||||
primaryColor: '#007bff',
|
|
||||||
backgroundColor: '#f8f9fa'
|
|
||||||
},
|
|
||||||
features: {
|
|
||||||
showAddButton: true,
|
|
||||||
showDeleteButton: true,
|
|
||||||
showUserInfo: true,
|
|
||||||
showSessionStatus: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. **userId参数是必需的**:不提供userId会导致错误
|
|
||||||
2. **假设用户已登录**:不再进行登录状态检查
|
|
||||||
3. **向后兼容**:此修改可能会影响现有集成,需要更新调用代码
|
|
||||||
4. **删除的API**:renderLogin函数仍然存在,但如果不再需要可以移除
|
|
||||||
|
|
||||||
## 测试
|
|
||||||
|
|
||||||
可以使用test-device-manager.html文件测试修改后的功能。
|
|
||||||
|
|
||||||
## 依赖关系
|
|
||||||
|
|
||||||
确保在调用renderDeviceManager之前已经加载了:
|
|
||||||
- jQuery
|
|
||||||
- Bootstrap JS
|
|
||||||
- dfido2-lib.js
|
|
||||||
- fido2-ui-sdk.js
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>amiPro App Callback</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; margin: 0; padding: 32px; background: #f6f7fb; color: #1f2430; }
|
|
||||||
.card { max-width: 520px; margin: 0 auto; background: #fff; padding: 24px; border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.06); }
|
|
||||||
h1 { margin: 0 0 12px; font-size: 22px; }
|
|
||||||
p { margin: 6px 0; color: #4a5160; }
|
|
||||||
code { background: #eef1ff; padding: 2px 6px; border-radius: 6px; font-size: 12px; }
|
|
||||||
</style>
|
|
||||||
<script>
|
|
||||||
function init() {
|
|
||||||
const params = new URL(window.location.href).searchParams;
|
|
||||||
const session = params.get('session');
|
|
||||||
const username = params.get('username');
|
|
||||||
const rpId = params.get('rpId');
|
|
||||||
document.getElementById('session').textContent = session || '(none)';
|
|
||||||
document.getElementById('username').textContent = username || '(none)';
|
|
||||||
document.getElementById('rpId').textContent = rpId || '(none)';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body onload="init()">
|
|
||||||
<div class="card">
|
|
||||||
<h1>SSO Complete</h1>
|
|
||||||
<p>You can return to the app.</p>
|
|
||||||
<p>Session: <code id="session"></code></p>
|
|
||||||
<p>User: <code id="username"></code></p>
|
|
||||||
<p>rpId: <code id="rpId"></code></p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -54,8 +54,8 @@
|
|||||||
|
|
||||||
<script src="files/ua-parser.js"></script>
|
<script src="files/ua-parser.js"></script>
|
||||||
|
|
||||||
<script src="files/amipro_utils.js?v=20230401402"></script>
|
<script src="files/amipro_utils.js?v=20260118"></script>
|
||||||
<script src="files/dfido2-lib.js?v=20230918"></script>
|
<script src="files/dfido2-lib.js?v=20260118"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
|||||||
641
docs/API.md
641
docs/API.md
@@ -1,641 +0,0 @@
|
|||||||
# FIDO2 UI SDK - API 文档
|
|
||||||
|
|
||||||
## 目录
|
|
||||||
|
|
||||||
- [全局对象](#全局对象)
|
|
||||||
- [配置选项](#配置选项)
|
|
||||||
- [API 方法](#api-方法)
|
|
||||||
- [事件系统](#事件系统)
|
|
||||||
- [国际化](#国际化)
|
|
||||||
- [主题定制](#主题定制)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 全局对象
|
|
||||||
|
|
||||||
### Fido2UIManager
|
|
||||||
|
|
||||||
全局 FIDO2 UI 管理对象。
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
window.Fido2UIManager
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 配置选项
|
|
||||||
|
|
||||||
### config
|
|
||||||
|
|
||||||
配置对象,用于初始化设备管理器。
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| serverUrl | String | 'https://fido2.amipro.me' | FIDO2 服务器地址 |
|
|
||||||
| mode | String | 'modal' | 渲染模式:'modal' \| 'standalone' |
|
|
||||||
| container | String \| Element | null | 容器选择器或 DOM 元素(modal 模式必需) |
|
|
||||||
| theme | Object | 见下方 | 主题配置 |
|
|
||||||
| language | String | 'zh-CN' | 语言:'en-US' \| 'zh-CN' \| 'ja' |
|
|
||||||
| customI18n | Object | {} | 自定义翻译 |
|
|
||||||
| features | Object | 见下方 | 功能开关 |
|
|
||||||
| callbacks | Object | {} | 事件回调 |
|
|
||||||
| rpId | String \| null | null | Relying Party ID |
|
|
||||||
| autoRefresh | Boolean | true | 是否自动刷新设备列表 |
|
|
||||||
| refreshInterval | Number | 5000 | 刷新间隔(毫秒) |
|
|
||||||
|
|
||||||
### config.theme
|
|
||||||
|
|
||||||
主题配置对象。
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| logo | String \| null | null | Logo 图片 URL |
|
|
||||||
| primaryColor | String | '#696cff' | 主色调(十六进制颜色值) |
|
|
||||||
| backgroundColor | String | '#ffffff' | 背景色 |
|
|
||||||
| textColor | String | '#333333' | 文字颜色 |
|
|
||||||
| borderRadius | String | '8px' | 圆角大小 |
|
|
||||||
|
|
||||||
### config.features
|
|
||||||
|
|
||||||
功能开关对象。
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| showAddButton | Boolean | true | 是否显示添加按钮 |
|
|
||||||
| showDeleteButton | Boolean | true | 是否显示删除按钮 |
|
|
||||||
| showUserInfo | Boolean | true | 是否显示用户信息 |
|
|
||||||
| showSessionStatus | Boolean | true | 是否显示会话状态 |
|
|
||||||
|
|
||||||
### config.callbacks
|
|
||||||
|
|
||||||
事件回调对象。
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| onInit | Function | null | 初始化完成回调 |
|
|
||||||
| onDeviceAdded | Function | null | 设备添加成功回调 |
|
|
||||||
| onDeviceDeleted | Function | null | 设备删除成功回调 |
|
|
||||||
| onDeviceListLoaded | Function | null | 设备列表加载完成回调 |
|
|
||||||
| onError | Function | null | 错误发生回调 |
|
|
||||||
| onClose | Function | null | 窗口关闭回调(仅 modal 模式) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API 方法
|
|
||||||
|
|
||||||
### renderDeviceManager(config)
|
|
||||||
|
|
||||||
渲染设备管理器。
|
|
||||||
|
|
||||||
**语法:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager(config)
|
|
||||||
```
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `config` (Object) - 配置对象,详见[配置选项](#配置选项)
|
|
||||||
|
|
||||||
**返回值:**
|
|
||||||
|
|
||||||
- (Object) - 设备管理器实例
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const manager = Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: 'https://fido2.amipro.me'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### close()
|
|
||||||
|
|
||||||
关闭模态框(仅 modal 模式有效)。
|
|
||||||
|
|
||||||
**语法:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.close()
|
|
||||||
```
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.close();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### refresh()
|
|
||||||
|
|
||||||
刷新设备列表和会话状态。
|
|
||||||
|
|
||||||
**语法:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.refresh()
|
|
||||||
```
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.refresh();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### destroy()
|
|
||||||
|
|
||||||
销毁设备管理器实例,清理资源。
|
|
||||||
|
|
||||||
**语法:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.destroy()
|
|
||||||
```
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.destroy();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 事件系统
|
|
||||||
|
|
||||||
### 事件列表
|
|
||||||
|
|
||||||
| 事件名称 | 触发时机 | 回调参数 |
|
|
||||||
|---------|---------|---------|
|
|
||||||
| `init` | 初始化完成 | (manager: Object) |
|
|
||||||
| `deviceAdded` | 设备添加成功 | (device: Object) |
|
|
||||||
| `deviceDeleted` | 设备删除成功 | (deviceId: String) |
|
|
||||||
| `deviceListLoaded` | 设备列表加载完成 | (devices: Array) |
|
|
||||||
| `sessionStatusChanged` | 会话状态改变 | (isValid: Boolean) |
|
|
||||||
| `error` | 发生错误 | (error: Error) |
|
|
||||||
| `close` | 窗口关闭 | 无 |
|
|
||||||
|
|
||||||
### 事件参数详解
|
|
||||||
|
|
||||||
#### init
|
|
||||||
|
|
||||||
初始化完成时触发。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `manager` (Object) - 设备管理器实例
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
console.log('设备管理器已初始化', manager);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### deviceAdded
|
|
||||||
|
|
||||||
设备添加成功时触发。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `device` (Object) - 添加的设备信息
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
console.log('设备添加成功', device);
|
|
||||||
// 刷新用户界面
|
|
||||||
refreshUserInterface();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### deviceDeleted
|
|
||||||
|
|
||||||
设备删除成功时触发。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `deviceId` (String) - 被删除设备的 ID
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onDeviceDeleted: function(deviceId) {
|
|
||||||
console.log('设备删除成功', deviceId);
|
|
||||||
// 显示成功提示
|
|
||||||
showNotification('设备删除成功');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### deviceListLoaded
|
|
||||||
|
|
||||||
设备列表加载完成时触发。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `devices` (Array) - 设备列表
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onDeviceListLoaded: function(devices) {
|
|
||||||
console.log('设备列表加载完成,共', devices.length, '个设备');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### sessionStatusChanged
|
|
||||||
|
|
||||||
会话状态改变时触发。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `isValid` (Boolean) - 会话是否有效
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onSessionStatusChanged: function(isValid) {
|
|
||||||
if (isValid) {
|
|
||||||
console.log('会话有效');
|
|
||||||
} else {
|
|
||||||
console.log('会话无效,请重新登录');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### error
|
|
||||||
|
|
||||||
发生错误时触发。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- `error` (Error) - 错误对象
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onError: function(error) {
|
|
||||||
console.error('发生错误:', error.message);
|
|
||||||
// 显示错误提示
|
|
||||||
showErrorNotification(error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### close
|
|
||||||
|
|
||||||
模态框关闭时触发(仅 modal 模式)。
|
|
||||||
|
|
||||||
**参数:**
|
|
||||||
|
|
||||||
- 无
|
|
||||||
|
|
||||||
**示例:**
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
callbacks: {
|
|
||||||
onClose: function() {
|
|
||||||
console.log('模态框已关闭');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 国际化
|
|
||||||
|
|
||||||
### 支持的语言
|
|
||||||
|
|
||||||
- `en-US` - 英文
|
|
||||||
- `zh-CN` - 简体中文
|
|
||||||
- `ja` - 日文
|
|
||||||
|
|
||||||
### 设置语言
|
|
||||||
|
|
||||||
通过 `language` 参数设置语言:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
language: 'zh-CN'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 默认翻译键
|
|
||||||
|
|
||||||
| 键 | 英文 | 中文 | 日文 |
|
|
||||||
|----|------|------|------|
|
|
||||||
| `my_devices` | My devices | 我的设备 | マイデバイス |
|
|
||||||
| `btn_add` | Add device | 添加设备 | デバイスを追加 |
|
|
||||||
| `title_device` | Device | 设备 | デバイス |
|
|
||||||
| `title_time` | Registered time | 添加时间 | 登録時間 |
|
|
||||||
| `title_act` | Actions | 操作 | 操作 |
|
|
||||||
| `title_del` | Delete | 删除 | 削除 |
|
|
||||||
| `title_logout` | Log out | 登出 | ログアウト |
|
|
||||||
| `title_empty_list` | No devices, please add. | 无设备,请添加。 | デバイスがなし、追加してください。 |
|
|
||||||
| `msg_register_ok` | Device registered successfully | 添加设备成功 | デバイス登録完了 |
|
|
||||||
| `msg_deldev_ok` | Device deleted successfully | 设备删除成功 | デバイスを削除しました |
|
|
||||||
| `msg_confirm_deldev` | Do you want to delete this device? | 确认删除此设备吗? | デバイスを削除しますか? |
|
|
||||||
| `msg_session_status_ok` | FIDO2 session is valid | FIDO2会话正常 | FIDO2セッションは正常です |
|
|
||||||
| `msg_session_status_fail` | FIDO2 session is invalid | FIDO2会话无效 | FIDO2セッションは無効です |
|
|
||||||
| `btn_close` | Close | 关闭 | 閉じる |
|
|
||||||
| `title_welcome` | Welcome | 欢迎 | ようこそ |
|
|
||||||
|
|
||||||
### 自定义翻译
|
|
||||||
|
|
||||||
通过 `customI18n` 参数自定义翻译:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
language: 'zh-CN',
|
|
||||||
customI18n: {
|
|
||||||
'my_devices': {
|
|
||||||
'zh-CN': '我的 FIDO2 设备'
|
|
||||||
},
|
|
||||||
'btn_add': {
|
|
||||||
'zh-CN': '添加新设备'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 主题定制
|
|
||||||
|
|
||||||
### 主题配置参数
|
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| logo | String \| null | null | Logo 图片 URL |
|
|
||||||
| primaryColor | String | '#696cff' | 主色调 |
|
|
||||||
| backgroundColor | String | '#ffffff' | 背景色 |
|
|
||||||
| textColor | String | '#333333' | 文字颜色 |
|
|
||||||
| borderRadius | String | '8px' | 圆角大小 |
|
|
||||||
|
|
||||||
### 基础主题定制
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
theme: {
|
|
||||||
logo: '/assets/my-logo.png',
|
|
||||||
primaryColor: '#ce59d9',
|
|
||||||
backgroundColor: '#ffffff'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 完整主题定制
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
theme: {
|
|
||||||
logo: '/assets/my-logo.png',
|
|
||||||
primaryColor: '#0066cc',
|
|
||||||
backgroundColor: '#f8f9fa',
|
|
||||||
textColor: '#333333',
|
|
||||||
borderRadius: '12px'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 深色主题示例
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
theme: {
|
|
||||||
logo: '/assets/logo.png',
|
|
||||||
primaryColor: '#4a9eff',
|
|
||||||
backgroundColor: '#1a1a2e',
|
|
||||||
textColor: '#ffffff',
|
|
||||||
borderRadius: '8px'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 设备管理器实例方法
|
|
||||||
|
|
||||||
如果需要访问设备管理器实例,可以保存 `renderDeviceManager` 的返回值:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const manager = Fido2UIManager.renderDeviceManager(config);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 实例方法
|
|
||||||
|
|
||||||
| 方法 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `close()` | 关闭模态框(仅 modal 模式) |
|
|
||||||
| `refresh()` | 刷新设备列表和会话状态 |
|
|
||||||
| `destroy()` | 销毁设备管理器实例 |
|
|
||||||
|
|
||||||
### 示例
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const manager = Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 手动刷新
|
|
||||||
manager.refresh();
|
|
||||||
|
|
||||||
// 关闭模态框
|
|
||||||
manager.close();
|
|
||||||
|
|
||||||
// 销毁实例
|
|
||||||
manager.destroy();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## CSS 类名
|
|
||||||
|
|
||||||
SDK 使用以下 CSS 类名,可以通过 CSS 覆盖样式:
|
|
||||||
|
|
||||||
| 类名 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `.fido2-sdk-modal` | 模态框容器 |
|
|
||||||
| `.fido2-sdk-card` | 卡片容器 |
|
|
||||||
| `.fido2-sdk-header` | 头部区域 |
|
|
||||||
| `.fido2-sdk-container` | 内容容器 |
|
|
||||||
| `.fido2-sdk-body` | 主体内容 |
|
|
||||||
| `.fido2-sdk-text` | 文字样式 |
|
|
||||||
| `.fido2-sdk-table` | 表格样式 |
|
|
||||||
| `.fido2-sdk-btn` | 按钮样式 |
|
|
||||||
| `.fido2-sdk-btn-primary` | 主要按钮样式 |
|
|
||||||
| `.fido2-sdk-status-badge` | 状态徽章样式 |
|
|
||||||
| `.fido2-sdk-user-info` | 用户信息样式 |
|
|
||||||
| `.fido2-sdk-standalone` | 独立页面样式 |
|
|
||||||
| `.fido2-sdk-logo` | Logo 样式 |
|
|
||||||
|
|
||||||
### 自定义 CSS 示例
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* 自定义按钮样式 */
|
|
||||||
.fido2-sdk-btn-primary {
|
|
||||||
background-color: #ff6b6b !important;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义表格样式 */
|
|
||||||
.fido2-sdk-table th {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 自定义模态框样式 */
|
|
||||||
.fido2-sdk-modal .modal-content {
|
|
||||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 类型定义
|
|
||||||
|
|
||||||
### ConfigObject
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface ConfigObject {
|
|
||||||
serverUrl?: string;
|
|
||||||
mode?: 'modal' | 'standalone';
|
|
||||||
container?: string | Element;
|
|
||||||
theme?: ThemeObject;
|
|
||||||
language?: string;
|
|
||||||
customI18n?: Record<string, Record<string, string>>;
|
|
||||||
features?: FeaturesObject;
|
|
||||||
callbacks?: CallbacksObject;
|
|
||||||
rpId?: string | null;
|
|
||||||
autoRefresh?: boolean;
|
|
||||||
refreshInterval?: number;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ThemeObject
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface ThemeObject {
|
|
||||||
logo?: string | null;
|
|
||||||
primaryColor?: string;
|
|
||||||
backgroundColor?: string;
|
|
||||||
textColor?: string;
|
|
||||||
borderRadius?: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### FeaturesObject
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface FeaturesObject {
|
|
||||||
showAddButton?: boolean;
|
|
||||||
showDeleteButton?: boolean;
|
|
||||||
showUserInfo?: boolean;
|
|
||||||
showSessionStatus?: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### CallbacksObject
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface CallbacksObject {
|
|
||||||
onInit?: (manager: any) => void;
|
|
||||||
onDeviceAdded?: (device: any) => void;
|
|
||||||
onDeviceDeleted?: (deviceId: string) => void;
|
|
||||||
onDeviceListLoaded?: (devices: any[]) => void;
|
|
||||||
onError?: (error: Error) => void;
|
|
||||||
onClose?: () => void;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常见问题
|
|
||||||
|
|
||||||
### Q: 如何在页面加载后自动打开设备管理器?
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
window.onload = function() {
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 如何在用户点击按钮后才打开设备管理器?
|
|
||||||
|
|
||||||
```html
|
|
||||||
<button onclick="openDeviceManager()">管理设备</button>
|
|
||||||
<div id="device-container"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function openDeviceManager() {
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 如何禁用自动刷新?
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
autoRefresh: false
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Q: 如何更改刷新间隔?
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
autoRefresh: true,
|
|
||||||
refreshInterval: 10000 // 10秒
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 版本历史
|
|
||||||
|
|
||||||
- **v1.0.0** - 初始版本
|
|
||||||
- 支持模态框和独立页面两种模式
|
|
||||||
- 支持主题定制
|
|
||||||
- 支持国际化
|
|
||||||
- 完整的事件系统
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
# FIDO2 UI SDK - 实施总结
|
|
||||||
|
|
||||||
## 创建的文件
|
|
||||||
|
|
||||||
### 核心 SDK 文件
|
|
||||||
- `files/fido2-ui-sdk.js` (27KB) - 核心 JavaScript SDK
|
|
||||||
- `files/fido2-ui-sdk.css` (4.5KB) - SDK 样式文件
|
|
||||||
|
|
||||||
### 演示文件
|
|
||||||
- `modal-demo.html` (9.0KB) - 模态框模式演示
|
|
||||||
- `standalone-demo.html` (8.9KB) - 独立页面模式演示
|
|
||||||
|
|
||||||
### 文档文件
|
|
||||||
- `docs/README.md` (7.6KB) - 使用文档
|
|
||||||
- `docs/API.md` (13KB) - API 参考文档
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
sample-site/
|
|
||||||
├── files/
|
|
||||||
│ ├── fido2-lib.js (现有,核心 FIDO2 库)
|
|
||||||
│ ├── fido2-ui-sdk.js (新增 - SDK 核心)
|
|
||||||
│ ├── fido2-ui-sdk.css (新增 - SDK 样式)
|
|
||||||
│ └── (其他现有文件保持不变)
|
|
||||||
├── modal-demo.html (新增 - 模态框模式演示)
|
|
||||||
├── standalone-demo.html (新增 - 独立页面模式演示)
|
|
||||||
├── devices.html (现有 - 保持不变)
|
|
||||||
├── login.html (现有 - 保持不变)
|
|
||||||
└── docs/
|
|
||||||
├── README.md (新增 - 使用指南)
|
|
||||||
└── API.md (新增 - API 文档)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
### ✅ 已实现的功能
|
|
||||||
|
|
||||||
1. **双模式支持**
|
|
||||||
- 模态框模式 (Modal): 在当前页面弹出设备管理窗口
|
|
||||||
- 独立页面模式 (Standalone): 渲染完整的设备管理页面
|
|
||||||
|
|
||||||
2. **主题定制**
|
|
||||||
- Logo 自定义
|
|
||||||
- 主色调 (primaryColor)
|
|
||||||
- 背景色 (backgroundColor)
|
|
||||||
- 文字颜色 (textColor)
|
|
||||||
- 圆角 (borderRadius)
|
|
||||||
|
|
||||||
3. **国际化**
|
|
||||||
- 内置中文、英文、日文
|
|
||||||
- 支持自定义翻译
|
|
||||||
- 根据浏览器语言自动切换
|
|
||||||
|
|
||||||
4. **功能开关**
|
|
||||||
- 显示/隐藏添加按钮
|
|
||||||
- 显示/隐藏删除按钮
|
|
||||||
- 显示/隐藏用户信息
|
|
||||||
- 显示/隐藏会话状态
|
|
||||||
|
|
||||||
5. **事件系统**
|
|
||||||
- 初始化完成 (init)
|
|
||||||
- 设备添加成功 (deviceAdded)
|
|
||||||
- 设备删除成功 (deviceDeleted)
|
|
||||||
- 设备列表加载 (deviceListLoaded)
|
|
||||||
- 会话状态改变 (sessionStatusChanged)
|
|
||||||
- 错误发生 (error)
|
|
||||||
- 窗口关闭 (close)
|
|
||||||
|
|
||||||
6. **自动刷新**
|
|
||||||
- 支持自动刷新设备列表
|
|
||||||
- 可配置刷新间隔
|
|
||||||
|
|
||||||
7. **响应式设计**
|
|
||||||
- 支持桌面端和移动端
|
|
||||||
- 自适应布局
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 模态框模式(推荐)
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="files/jquery.js"></script>
|
|
||||||
<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>
|
|
||||||
<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">
|
|
||||||
|
|
||||||
<div id="device-container"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: 'https://fido2.amipro.me'
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 独立页面模式
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
mode: 'standalone',
|
|
||||||
serverUrl: 'https://fido2.amipro.me'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 带主题定制
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: 'https://fido2.amipro.me',
|
|
||||||
theme: {
|
|
||||||
logo: '/assets/my-logo.png',
|
|
||||||
primaryColor: '#ce59d9',
|
|
||||||
backgroundColor: '#ffffff'
|
|
||||||
},
|
|
||||||
language: 'zh-CN'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 带事件回调
|
|
||||||
|
|
||||||
```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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## API 方法
|
|
||||||
|
|
||||||
| 方法 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `Fido2UIManager.renderDeviceManager(config)` | 渲染设备管理器 |
|
|
||||||
| `Fido2UIManager.close()` | 关闭模态框(仅 modal 模式) |
|
|
||||||
| `Fido2UIManager.refresh()` | 刷新设备列表和会话状态 |
|
|
||||||
| `Fido2UIManager.destroy()` | 销毁设备管理器实例 |
|
|
||||||
|
|
||||||
## 向后兼容性
|
|
||||||
|
|
||||||
✅ **完全兼容现有对接方式**
|
|
||||||
|
|
||||||
- `devices.html` - 继续使用现有逻辑,无需修改
|
|
||||||
- `login.html` - 继续使用现有逻辑,无需修改
|
|
||||||
- `dfido2-lib.js` - 核心库保持不变
|
|
||||||
- 所有现有的 CSS 和 JavaScript 文件保持不变
|
|
||||||
|
|
||||||
## 浏览器支持
|
|
||||||
|
|
||||||
- Chrome 67+
|
|
||||||
- Firefox 60+
|
|
||||||
- Safari 13+
|
|
||||||
- Edge 18+
|
|
||||||
|
|
||||||
## 依赖要求
|
|
||||||
|
|
||||||
- jQuery 3.x
|
|
||||||
- Bootstrap 5.x
|
|
||||||
- Popper.js
|
|
||||||
- dfido2-lib.js(核心 FIDO2 库)
|
|
||||||
- UA-Parser.js(设备描述解析)
|
|
||||||
|
|
||||||
## 演示
|
|
||||||
|
|
||||||
### 模态框模式演示
|
|
||||||
访问 `modal-demo.html` 查看模态框模式的演示
|
|
||||||
|
|
||||||
### 独立页面模式演示
|
|
||||||
访问 `standalone-demo.html` 查看独立页面模式的演示
|
|
||||||
|
|
||||||
## 文档
|
|
||||||
|
|
||||||
详细文档请查看:
|
|
||||||
- `docs/README.md` - 使用指南和快速开始
|
|
||||||
- `docs/API.md` - 完整的 API 参考文档
|
|
||||||
|
|
||||||
## 下一步建议
|
|
||||||
|
|
||||||
1. **测试功能**
|
|
||||||
- 在浏览器中打开 `modal-demo.html` 测试模态框模式
|
|
||||||
- 在浏览器中打开 `standalone-demo.html` 测试独立页面模式
|
|
||||||
- 测试添加、删除设备功能
|
|
||||||
- 测试主题定制功能
|
|
||||||
- 测试国际化功能
|
|
||||||
|
|
||||||
2. **集成到现有项目**
|
|
||||||
- 在您的页面中引入 SDK
|
|
||||||
- 根据需求选择模式(modal 或 standalone)
|
|
||||||
- 配置主题和回调函数
|
|
||||||
- 测试所有功能
|
|
||||||
|
|
||||||
3. **自定义开发**
|
|
||||||
- 根据需要调整样式
|
|
||||||
- 添加自定义翻译
|
|
||||||
- 扩展事件处理逻辑
|
|
||||||
- 集成到现有用户管理系统
|
|
||||||
|
|
||||||
## 注意事项
|
|
||||||
|
|
||||||
1. 确保 `dfido2-lib.js` 在 `fido2-ui-sdk.js` 之前加载
|
|
||||||
2. 模态框模式需要提供有效的容器选择器
|
|
||||||
3. 独立页面模式会替换当前页面的内容
|
|
||||||
4. 确保浏览器支持 WebAuthn API
|
|
||||||
5. 确保所有依赖文件(jQuery、Bootstrap 等)都已正确加载
|
|
||||||
|
|
||||||
## 技术架构
|
|
||||||
|
|
||||||
### 模块组成
|
|
||||||
|
|
||||||
1. **I18nManager** - 国际化管理
|
|
||||||
- 支持多语言
|
|
||||||
- 支持自定义翻译
|
|
||||||
- 自动语言检测
|
|
||||||
|
|
||||||
2. **EventManager** - 事件管理
|
|
||||||
- 发布-订阅模式
|
|
||||||
- 支持多个监听器
|
|
||||||
- 事件触发和冒泡
|
|
||||||
|
|
||||||
3. **ThemeManager** - 主题管理
|
|
||||||
- CSS 变量注入
|
|
||||||
- 动态样式应用
|
|
||||||
- 样式清理
|
|
||||||
|
|
||||||
4. **DeviceManager** - 设备管理
|
|
||||||
- 加载设备列表
|
|
||||||
- 添加/删除设备
|
|
||||||
- 会话状态检查
|
|
||||||
- 自动刷新
|
|
||||||
|
|
||||||
5. **UIRenderer** - UI 渲染
|
|
||||||
- 模态框渲染
|
|
||||||
- 独立页面渲染
|
|
||||||
- 设备列表更新
|
|
||||||
- 事件绑定
|
|
||||||
|
|
||||||
6. **Fido2UIManager** - 主管理器
|
|
||||||
- 配置管理
|
|
||||||
- 模块协调
|
|
||||||
- API 暴露
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
[根据您的项目许可证填写]
|
|
||||||
|
|
||||||
## 联系方式
|
|
||||||
|
|
||||||
如有问题或建议,请联系 [您的联系方式]
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
# Fido2Login SDK API 文档
|
|
||||||
|
|
||||||
## 概述
|
|
||||||
|
|
||||||
`Fido2UIManager.renderLogin()` 是一个用于渲染登录组件的API,支持:
|
|
||||||
- Fido2/Passkey 自动登录
|
|
||||||
- 密码登录回退
|
|
||||||
- 密码重试次数限制
|
|
||||||
- 完全的回调驱动(SDK不跳转页面)
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 1. 引入依赖
|
|
||||||
|
|
||||||
```html
|
|
||||||
<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>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. 添加容器
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="login-container"></div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 初始化登录组件
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loginManager = Fido2UIManager.renderLogin({
|
|
||||||
container: '#login-container',
|
|
||||||
features: {
|
|
||||||
autoAuth: true,
|
|
||||||
enablePasswordLogin: true,
|
|
||||||
maxPasswordAttempts: 3,
|
|
||||||
showRemainingAttempts: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onFido2Success: (userId, session) => {
|
|
||||||
// 处理Fido2登录成功
|
|
||||||
},
|
|
||||||
onFido2Error: (error) => {
|
|
||||||
// 处理Fido2登录失败
|
|
||||||
},
|
|
||||||
onPasswordLogin: (userId, password) => {
|
|
||||||
// 返回 Promise<boolean>
|
|
||||||
},
|
|
||||||
onPasswordExhausted: (userId, attempts, maxAttempts) => {
|
|
||||||
// 密码重试次数耗尽
|
|
||||||
},
|
|
||||||
onLoginClosed: () => {
|
|
||||||
// UI被关闭
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 配置参数
|
|
||||||
|
|
||||||
### container
|
|
||||||
- **类型**: `string | Element`
|
|
||||||
- **必填**: 是
|
|
||||||
- **说明**: 模态框容器元素
|
|
||||||
|
|
||||||
### features
|
|
||||||
- **类型**: `Object`
|
|
||||||
- **默认值**: 见下方
|
|
||||||
|
|
||||||
| 属性 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| `autoAuth` | boolean | true | 有注册设备时自动Fido2登录 |
|
|
||||||
| `enablePasswordLogin` | boolean | true | 是否启用密码登录功能 |
|
|
||||||
| `autoShowPassword` | boolean | false | Fido2失败后是否自动显示密码框 |
|
|
||||||
| `maxPasswordAttempts` | number | 3 | 密码最大重试次数 |
|
|
||||||
| `showRemainingAttempts` | boolean | true | 是否显示剩余尝试次数 |
|
|
||||||
|
|
||||||
### theme
|
|
||||||
- **类型**: `Object`
|
|
||||||
- **说明**: 复用 `renderDeviceManager` 的theme配置
|
|
||||||
|
|
||||||
| 属性 | 类型 | 默认值 | 说明 |
|
|
||||||
|------|------|--------|------|
|
|
||||||
| `logo` | string | null | Logo图片URL |
|
|
||||||
| `primaryColor` | string | '#696cff' | 主色调 |
|
|
||||||
| `backgroundColor` | string | '#ffffff' | 背景色 |
|
|
||||||
| `textColor` | string | '#333333' | 文字颜色 |
|
|
||||||
| `borderRadius` | string | '8px' | 边框圆角 |
|
|
||||||
|
|
||||||
### language
|
|
||||||
- **类型**: `string`
|
|
||||||
- **默认值**: `'zh-CN'`
|
|
||||||
- **说明**: 支持9种语言
|
|
||||||
|
|
||||||
### callbacks
|
|
||||||
|
|
||||||
#### onFido2Success(userId, session)
|
|
||||||
- **触发时机**: Fido2登录认证成功
|
|
||||||
- **参数**:
|
|
||||||
- `userId`: string - 用户ID
|
|
||||||
- `session`: object - 会话信息
|
|
||||||
- **注意**: SDK不跳转页面,由调用端处理跳转
|
|
||||||
|
|
||||||
#### onFido2Error(error)
|
|
||||||
- **触发时机**: Fido2登录失败或取消
|
|
||||||
- **参数**: `error` 对象
|
|
||||||
- `code`: string - 错误码
|
|
||||||
- `message`: string - 错误信息
|
|
||||||
- `originalError`: object - 原始错误
|
|
||||||
|
|
||||||
**error.code 值**:
|
|
||||||
| code | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `CANCELED` | 用户取消认证 |
|
|
||||||
| `AUTH_FAILED` | 认证失败 |
|
|
||||||
| `NO_REGISTRATION` | 无注册设备且未启用密码登录 |
|
|
||||||
| `NOT_SUPPORTED` | 浏览器不支持WebAuthn |
|
|
||||||
|
|
||||||
#### onPasswordLogin(userId, password)
|
|
||||||
- **触发时机**: 用户点击密码登录按钮
|
|
||||||
- **参数**:
|
|
||||||
- `userId`: string - 用户ID
|
|
||||||
- `password`: string - 密码
|
|
||||||
- **返回值**: `Promise<boolean>`
|
|
||||||
- `true`: 登录成功
|
|
||||||
- `false`: 登录失败
|
|
||||||
|
|
||||||
#### onPasswordExhausted(userId, attempts, maxAttempts)
|
|
||||||
- **触发时机**: 密码重试次数耗尽
|
|
||||||
- **参数**:
|
|
||||||
- `userId`: string - 用户ID
|
|
||||||
- `attempts`: number - 已尝试次数
|
|
||||||
- `maxAttempts`: number - 最大允许次数
|
|
||||||
- **注意**: UI已自动关闭
|
|
||||||
|
|
||||||
#### onLoginClosed()
|
|
||||||
- **触发时机**: 用户关闭模态框(点击×或调用hide())
|
|
||||||
|
|
||||||
## 实例方法
|
|
||||||
|
|
||||||
### show()
|
|
||||||
显示登录模态框
|
|
||||||
|
|
||||||
### hide()
|
|
||||||
隐藏登录模态框,触发 `onLoginClosed` 回调
|
|
||||||
|
|
||||||
### destroy()
|
|
||||||
销毁登录组件实例,不触发任何回调
|
|
||||||
|
|
||||||
### setMode(mode)
|
|
||||||
切换登录模式
|
|
||||||
- `mode`: `'fido2'` 或 `'password'`
|
|
||||||
|
|
||||||
### getUserId()
|
|
||||||
获取当前输入的用户ID
|
|
||||||
|
|
||||||
### getState()
|
|
||||||
获取当前状态
|
|
||||||
- 返回值: `'loading'` | `'fido2'` | `'password'` | `'closed'`
|
|
||||||
|
|
||||||
### getMode()
|
|
||||||
获取当前模式
|
|
||||||
- 返回值: `'fido2'` | `'password'`
|
|
||||||
|
|
||||||
### getAttemptCount()
|
|
||||||
获取当前密码尝试次数
|
|
||||||
|
|
||||||
### getRemainingAttempts()
|
|
||||||
获取剩余尝试次数
|
|
||||||
|
|
||||||
### resetPasswordAttempts()
|
|
||||||
重置密码尝试计数器
|
|
||||||
|
|
||||||
## 国际化支持
|
|
||||||
|
|
||||||
默认支持9种语言:
|
|
||||||
- `en-US` (英语)
|
|
||||||
- `zh-CN` (简体中文)
|
|
||||||
- `ja` (日语)
|
|
||||||
- `es` (西班牙语)
|
|
||||||
- `de` (德语)
|
|
||||||
- `fr` (法语)
|
|
||||||
- `pt` (葡萄牙语)
|
|
||||||
- `ko` (韩语)
|
|
||||||
- `ru` (俄语)
|
|
||||||
- `it` (意大利语)
|
|
||||||
|
|
||||||
可通过 `customI18n` 配置自定义文本:
|
|
||||||
```javascript
|
|
||||||
Fido2UIManager.renderLogin({
|
|
||||||
customI18n: {
|
|
||||||
'title_login': {
|
|
||||||
'en-US': 'Custom Login',
|
|
||||||
'zh-CN': '自定义登录'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 完整示例
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Login Example</title>
|
|
||||||
<link rel="stylesheet" href="files/bootstrap.css">
|
|
||||||
<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>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="login-container"></div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const loginManager = Fido2UIManager.renderLogin({
|
|
||||||
container: '#login-container',
|
|
||||||
features: {
|
|
||||||
autoAuth: true,
|
|
||||||
enablePasswordLogin: true,
|
|
||||||
maxPasswordAttempts: 3,
|
|
||||||
showRemainingAttempts: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onFido2Success: (userId, session) => {
|
|
||||||
// 保存登录状态
|
|
||||||
localStorage.setItem('userId', userId);
|
|
||||||
localStorage.setItem('session', JSON.stringify(session));
|
|
||||||
// 跳转页面
|
|
||||||
window.location.href = '/dashboard';
|
|
||||||
},
|
|
||||||
onFido2Error: (error) => {
|
|
||||||
switch (error.code) {
|
|
||||||
case 'CANCELED':
|
|
||||||
console.log('用户取消登录');
|
|
||||||
break;
|
|
||||||
case 'AUTH_FAILED':
|
|
||||||
console.log('认证失败:', error.message);
|
|
||||||
break;
|
|
||||||
case 'NO_REGISTRATION':
|
|
||||||
alert('暂未注册设备,请联系管理员');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPasswordLogin: (userId, password) => {
|
|
||||||
return fetch('/api/login', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ userId, password })
|
|
||||||
}).then(r => r.ok);
|
|
||||||
},
|
|
||||||
onPasswordExhausted: (userId, attempts, maxAttempts) => {
|
|
||||||
alert('尝试次数过多,请稍后重试');
|
|
||||||
},
|
|
||||||
onLoginClosed: () => {
|
|
||||||
console.log('登录窗口已关闭');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 版本信息
|
|
||||||
|
|
||||||
- **FIDO2 UI SDK**: v1.0.0+
|
|
||||||
- **文件**: `files/fido2-ui-sdk.js`
|
|
||||||
- **新增功能**: `Fido2UIManager.renderLogin()`
|
|
||||||
@@ -1,12 +1,3 @@
|
|||||||
/**
|
|
||||||
*
|
|
||||||
* @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_USER_SESSION = 'fido2_user_session'
|
||||||
const DFIDO2_LIB_LOCALSTG_NAME_REGISTERED = 'dfido2_lib_registered'
|
const DFIDO2_LIB_LOCALSTG_NAME_REGISTERED = 'dfido2_lib_registered'
|
||||||
const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url'
|
const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url'
|
||||||
@@ -249,65 +240,17 @@ function errMessageFido2(result){
|
|||||||
break;
|
break;
|
||||||
case fido2LibErrCodes.timeout:
|
case fido2LibErrCodes.timeout:
|
||||||
rtn=getI18NErrorMessage('Fido2LibErr103:');
|
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:
|
default:
|
||||||
rtn=result.errorMessage?result.errorMessage:getI18NErrorMessage('Fido2LibErr104:');
|
rtn=result.errorMessage?result.errorMessage:getI18NErrorMessage('Fido2LibErr104:');
|
||||||
}
|
}
|
||||||
}else if(result.name && "InvalidStateError" === result.name){
|
}else if(result.name && "InvalidStateError" === result.name){
|
||||||
rtn=getI18NErrorMessage('Fido2LibErr105:');
|
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){
|
}else if(result.errorMessage){
|
||||||
const msg = getI18NErrorMessage(result.errorMessage);
|
const msg = getI18NErrorMessage(result.errorMessage);
|
||||||
rtn=msg?msg:result.errorMessage;
|
rtn=msg?msg:result.errorMessage;
|
||||||
}else{
|
}else{
|
||||||
rtn=getI18NErrorMessage('Fido2LibErr104:');
|
rtn=getI18NErrorMessage(i18n_messages, 'Fido2LibErr104:');
|
||||||
}
|
}
|
||||||
|
|
||||||
return rtn;
|
return rtn;
|
||||||
@@ -316,17 +259,7 @@ function errMessageFido2(result){
|
|||||||
const fido2LibErrCodes = {
|
const fido2LibErrCodes = {
|
||||||
user_canceled : -101,
|
user_canceled : -101,
|
||||||
timeout : -102,
|
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();
|
const errMsgs = new Map();
|
||||||
@@ -356,23 +289,11 @@ errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr118:', 'Signature is not
|
|||||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr119:', 'No user session!');
|
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('SvrErr120:', 'User has reached the device limit!');
|
||||||
|
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr101:', 'Your browser does not support FIDO2/WebAuthn.');
|
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr101:', 'Your browser does not support FIDO2.');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr102:', 'The operation was canceled by the user.');
|
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr102:', 'The user canceled.');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr103:', 'The operation timed out.');
|
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr103:', 'The process timeout.');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr104:', 'A system error occurred.');
|
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr104:', 'System error.');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr105:', 'The authenticator contains credentials that are already registered with this website.');
|
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr105:', 'The same authenticator cannot be registered again.');
|
||||||
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.set(fido2LibErrMsgLanguages.japanese, new Map());
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr101:', '登録されていないエンタープライズ認証デバイス aaguid!');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr101:', '登録されていないエンタープライズ認証デバイス aaguid!');
|
||||||
@@ -394,23 +315,11 @@ errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr118:', '署名は base6
|
|||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr119:', 'ユーザーセッションがありません!');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr119:', 'ユーザーセッションがありません!');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr120:', 'ユーザーはデバイスの制限数に達しました!');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr120:', 'ユーザーはデバイスの制限数に達しました!');
|
||||||
|
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr101:', 'お使いのブラウザは FIDO2/WebAuthn をサポートしていません。');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr101:', 'お使いのブラウザは FIDO2 をサポートしていません。');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr102:', 'ユーザーによって操作がキャンセルされました。');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr102:', 'ユーザーがキャンセルしました。');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr103:', '操作がタイムアウトしました。');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr103:', 'プロセスがタイムアウトしました。');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr104:', 'システムエラーが発生しました。');
|
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr104:', 'システムエラー。');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr105:', 'この認証デバイスには、すでにこの网站に登録されている憑拠が含まれています。');
|
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.set(fido2LibErrMsgLanguages.chinese_cn, new Map());
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr101:', '未注册的企业认证器 aaguid!');
|
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr101:', '未注册的企业认证器 aaguid!');
|
||||||
@@ -432,23 +341,11 @@ errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr118:', '签名不是
|
|||||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr119:', '未建立用户会话!');
|
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr119:', '未建立用户会话!');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr120:', '用户已达到设备限制数!');
|
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr120:', '用户已达到设备限制数!');
|
||||||
|
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr106:', '另一个请求正在进行中。');
|
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr101:', '您的浏览器不支持FIDO2.');
|
||||||
|
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr102:', '用户取消了操作。');
|
||||||
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('Fido2LibErr103:', '操作超时。');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr104:', '发生系统错误。');
|
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr104:', '系统错误。');
|
||||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr105:', '您的认证器包含已在此网站注册的凭据。');
|
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:', '发生数据错误。');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -592,7 +489,6 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
var errRtn = {status:'failed', errorMessage: err.message};
|
var errRtn = {status:'failed', errorMessage: err.message};
|
||||||
if(err.name) errRtn.name = err.name
|
if(err.name) errRtn.name = err.name
|
||||||
|
|
||||||
if(err.name && 'NotAllowedError' === err.name){
|
if(err.name && 'NotAllowedError' === err.name){
|
||||||
const nowtm = (new Date()).getTime()
|
const nowtm = (new Date()).getTime()
|
||||||
if(nowtm > process_time_limit){
|
if(nowtm > process_time_limit){
|
||||||
@@ -600,27 +496,7 @@ async function doAttestation(username, displayName, rpId, userVerification = 'pr
|
|||||||
}else{
|
}else{
|
||||||
errRtn.errCode = fido2LibErrCodes.user_canceled
|
errRtn.errCode = fido2LibErrCodes.user_canceled
|
||||||
}
|
}
|
||||||
} else if(err.name && 'InvalidStateError' === err.name){
|
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||||
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;
|
return errRtn;
|
||||||
} finally {
|
} finally {
|
||||||
@@ -731,7 +607,6 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
var errRtn = {status:'failed', errorMessage: err.message};
|
var errRtn = {status:'failed', errorMessage: err.message};
|
||||||
if(err.name) errRtn.name = err.name
|
if(err.name) errRtn.name = err.name
|
||||||
|
|
||||||
if(err.name && 'NotAllowedError' === err.name){
|
if(err.name && 'NotAllowedError' === err.name){
|
||||||
const nowtm = (new Date()).getTime()
|
const nowtm = (new Date()).getTime()
|
||||||
if(nowtm > process_time_limit){
|
if(nowtm > process_time_limit){
|
||||||
@@ -739,27 +614,7 @@ async function doAssertion(username = null, rpId = null, userVerification = 'pre
|
|||||||
}else{
|
}else{
|
||||||
errRtn.errCode = fido2LibErrCodes.user_canceled
|
errRtn.errCode = fido2LibErrCodes.user_canceled
|
||||||
}
|
}
|
||||||
} else if(err.name && 'InvalidStateError' === err.name){
|
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||||
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;
|
return errRtn;
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fido2-sdk-logo {
|
.fido2-sdk-logo {
|
||||||
@@ -129,25 +128,6 @@
|
|||||||
.fido2-sdk-logo {
|
.fido2-sdk-logo {
|
||||||
max-height: 30px;
|
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 {
|
.fido2-sdk-loading {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -53,15 +53,15 @@
|
|||||||
<script src="files/menu.js"></script>
|
<script src="files/menu.js"></script>
|
||||||
<script src="files/main.js"></script>
|
<script src="files/main.js"></script>
|
||||||
|
|
||||||
<script src="files/amipro_utils.js?v=20230414"></script>
|
<script src="files/amipro_utils.js?v=20260118"></script>
|
||||||
<script src="files/dfido2-lib.js?v=2025092701"></script>
|
<script src="files/dfido2-lib.js?v=20260118"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
<!--
|
<!--
|
||||||
// For stand alone: 'https://local.dqj-macpro.com'
|
// For stand alone: 'https://local.dqj-macpro.com'
|
||||||
// For proxy: set 'https://mac-air-m2.dqj-home.com'
|
// For proxy: set 'https://mac-air-m2.dqj-home.com'
|
||||||
-->
|
-->
|
||||||
setFidoServerURL('https://local.dqj-macpro.com');//'https://fido2.amipro.me');
|
setFidoServerURL('https://fido2.amipro.me'); //'https://local.dqj-macpro.com');
|
||||||
|
|
||||||
const i18n_messages = new Map();
|
const i18n_messages = new Map();
|
||||||
|
|
||||||
|
|||||||
@@ -181,7 +181,7 @@
|
|||||||
|
|
||||||
<script src="files/popper.js"></script>
|
<script src="files/popper.js"></script>
|
||||||
<script src="files/bootstrap.js"></script>
|
<script src="files/bootstrap.js"></script>
|
||||||
<script src="files/dfido2-lib.js?v=2026020501"></script>
|
<script src="files/dfido2-lib.js"></script>
|
||||||
<script src="files/fido2-ui-sdk.js"></script>
|
<script src="files/fido2-ui-sdk.js"></script>
|
||||||
<script src="files/amipro_utils.js"></script>
|
<script src="files/amipro_utils.js"></script>
|
||||||
|
|
||||||
@@ -213,9 +213,9 @@
|
|||||||
i18n_messages.set("msg_login_desc", lang_map);
|
i18n_messages.set("msg_login_desc", lang_map);
|
||||||
|
|
||||||
lang_map = new Map();
|
lang_map = new Map();
|
||||||
lang_map.set("en-US", "Login");
|
lang_map.set("en-US", "Passkey Login");
|
||||||
lang_map.set("zh-CN", "登录");
|
lang_map.set("zh-CN", "Passkey 登录");
|
||||||
lang_map.set("ja", "ログイン");
|
lang_map.set("ja", "パスキーログイン");
|
||||||
i18n_messages.set("msg_btn_passkey_login", lang_map);
|
i18n_messages.set("msg_btn_passkey_login", lang_map);
|
||||||
|
|
||||||
lang_map = new Map();
|
lang_map = new Map();
|
||||||
@@ -361,9 +361,10 @@
|
|||||||
lang_map.set("zh-CN", "事件回调系统");
|
lang_map.set("zh-CN", "事件回调系统");
|
||||||
lang_map.set("ja", "イベント コールバック システム");
|
lang_map.set("ja", "イベント コールバック システム");
|
||||||
i18n_messages.set("msg_feature_9", lang_map);
|
i18n_messages.set("msg_feature_9", lang_map);
|
||||||
lang_map.set("zh-CN", "主题色和样式定制");
|
lang_map.set("en-US", "Event callback system");
|
||||||
lang_map.set("ja", "テーマの色とスタイルのカスタマイズ");
|
lang_map.set("zh-CN", "事件回调系统");
|
||||||
i18n_messages.set("msg_feature_7", lang_map);
|
lang_map.set("ja", "イベント コールバック システム");
|
||||||
|
i18n_messages.set("msg_feature_9", lang_map);
|
||||||
|
|
||||||
lang_map = new Map();
|
lang_map = new Map();
|
||||||
lang_map.set("en-US", "Login Default");
|
lang_map.set("en-US", "Login Default");
|
||||||
@@ -371,51 +372,6 @@
|
|||||||
lang_map.set("ja", "ログイン デフォルト");
|
lang_map.set("ja", "ログイン デフォルト");
|
||||||
i18n_messages.set("msg_code_login_default", lang_map);
|
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 = new Map();
|
||||||
lang_map.set("en-US", "📊 Event Log");
|
lang_map.set("en-US", "📊 Event Log");
|
||||||
lang_map.set("zh-CN", "📊 事件日志");
|
lang_map.set("zh-CN", "📊 事件日志");
|
||||||
@@ -482,6 +438,12 @@
|
|||||||
lang_map.set("ja", "エラーが発生しました");
|
lang_map.set("ja", "エラーが発生しました");
|
||||||
i18n_messages.set("msg_log_error", lang_map);
|
i18n_messages.set("msg_log_error", lang_map);
|
||||||
|
|
||||||
|
lang_map = new Map();
|
||||||
|
lang_map.set("en-US", "Password attempts exhausted");
|
||||||
|
lang_map.set("zh-CN", "密码尝试次数已用完");
|
||||||
|
lang_map.set("ja", "パスワード試行回数が上限に達しました");
|
||||||
|
i18n_messages.set("msg_password_exhausted", lang_map);
|
||||||
|
|
||||||
lang_map = new Map();
|
lang_map = new Map();
|
||||||
lang_map.set("en-US", "Device manager closed");
|
lang_map.set("en-US", "Device manager closed");
|
||||||
lang_map.set("zh-CN", "设备管理器已关闭");
|
lang_map.set("zh-CN", "设备管理器已关闭");
|
||||||
@@ -665,7 +627,7 @@ Fido2UIManager.renderDeviceManager({<br>
|
|||||||
<div id="device-container"></div>
|
<div id="device-container"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const SERVER_URL = 'https://local.dqj-macpro.com';//'https://fido2.amipro.me';
|
const SERVER_URL = 'https://fido2.amipro.me'; //'https://local.dqj-macpro.com';
|
||||||
|
|
||||||
let currentUserId = null;
|
let currentUserId = null;
|
||||||
let isLoggedIn = false;
|
let isLoggedIn = false;
|
||||||
@@ -904,7 +866,7 @@ Fido2UIManager.renderDeviceManager({<br>
|
|||||||
const entry = document.createElement('div');
|
const entry = document.createElement('div');
|
||||||
entry.className = 'log-entry';
|
entry.className = 'log-entry';
|
||||||
|
|
||||||
const time = new Date().toLocaleTimeString('zh-CN');
|
const time = new Date().toLocaleTimeString(CURRENT_LANG);
|
||||||
const typeClass = 'log-type-' + type;
|
const typeClass = 'log-type-' + type;
|
||||||
|
|
||||||
entry.innerHTML = `<span class="log-time">[${time}]</span><span class="${typeClass}">[${type.toUpperCase()}]</span> ${message}`;
|
entry.innerHTML = `<span class="log-time">[${time}]</span><span class="${typeClass}">[${type.toUpperCase()}]</span> ${message}`;
|
||||||
|
|||||||
131
sso.html
131
sso.html
@@ -1,131 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>amiPro SSO</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; margin: 0; padding: 24px; background: #f5f5f7; color: #1f2430; }
|
|
||||||
.card { background: #fff; border-radius: 12px; padding: 20px; box-shadow: 0 6px 24px rgba(0,0,0,0.06); max-width: 520px; margin: 0 auto; }
|
|
||||||
h1 { font-size: 22px; margin: 0 0 12px; }
|
|
||||||
p { margin: 0 0 12px; color: #4a5160; }
|
|
||||||
label { display: block; margin: 12px 0 6px; font-weight: 600; }
|
|
||||||
input, select, button { width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid #cfd2dc; box-sizing: border-box; font-size: 14px; }
|
|
||||||
button { background: #6c63ff; color: #fff; border: none; cursor: pointer; margin-top: 14px; font-weight: 700; }
|
|
||||||
button.secondary { background: #e8e9ef; color: #1f2430; border: 1px solid #cfd2dc; }
|
|
||||||
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
||||||
.muted { font-size: 12px; color: #6c758a; }
|
|
||||||
.pill { display: inline-block; padding: 4px 10px; border-radius: 999px; background: #eef1ff; color: #3c45a0; font-size: 12px; margin-left: 6px; }
|
|
||||||
#status { margin-top: 10px; font-size: 13px; min-height: 18px; }
|
|
||||||
</style>
|
|
||||||
<script src="files/dfido2-lib.js"></script>
|
|
||||||
<script>
|
|
||||||
const PROD_URL = 'https://fido2.amipro.me';
|
|
||||||
const DEV_DEFAULT = localStorage.getItem('dfido2_dev_url') || '';
|
|
||||||
|
|
||||||
let redirectUri = '';
|
|
||||||
|
|
||||||
function setServer(url) {
|
|
||||||
if (!url) return;
|
|
||||||
setFidoServerURL(url);
|
|
||||||
document.getElementById('currentServer').textContent = url;
|
|
||||||
if (url !== PROD_URL) localStorage.setItem('dfido2_dev_url', url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
const params = new URL(window.location.href).searchParams;
|
|
||||||
redirectUri = params.get('redirect_uri') || `${window.location.origin}/app-callback`;
|
|
||||||
|
|
||||||
const devInput = document.getElementById('devUrl');
|
|
||||||
devInput.value = DEV_DEFAULT;
|
|
||||||
|
|
||||||
const rpInput = document.getElementById('rpId');
|
|
||||||
rpInput.value = location.hostname;
|
|
||||||
|
|
||||||
setServer(PROD_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useProd() {
|
|
||||||
setServer(PROD_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDev() {
|
|
||||||
const v = document.getElementById('devUrl').value.trim();
|
|
||||||
if (!v) {
|
|
||||||
setStatus('Enter a dev/ngrok URL (https://...) first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setServer(v);
|
|
||||||
setStatus('Dev server set.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStatus(msg) {
|
|
||||||
document.getElementById('status').textContent = msg || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doRegister() {
|
|
||||||
setStatus('Registering...');
|
|
||||||
const uid = document.getElementById('uid').value.trim();
|
|
||||||
const display = document.getElementById('display').value.trim() || uid;
|
|
||||||
const rpId = document.getElementById('rpId').value.trim() || location.hostname;
|
|
||||||
if (!uid) { setStatus('User ID required'); return; }
|
|
||||||
const result = await registerFido2(uid, display, rpId);
|
|
||||||
await handleResult(result, rpId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doLogin() {
|
|
||||||
setStatus('Signing in...');
|
|
||||||
const uid = document.getElementById('uid').value.trim();
|
|
||||||
const rpId = document.getElementById('rpId').value.trim() || location.hostname;
|
|
||||||
if (!uid) { setStatus('User ID required'); return; }
|
|
||||||
const result = await authenticateFido2(uid, rpId);
|
|
||||||
await handleResult(result, rpId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleResult(result, rpId) {
|
|
||||||
if (result.status !== 'ok') {
|
|
||||||
errProcessFido2(result);
|
|
||||||
setStatus(result.errorMessage || 'Failed');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// No server handoff: send session + username directly to redirect_uri for the app to consume.
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
session: result.session,
|
|
||||||
username: result.username,
|
|
||||||
rpId: rpId || ''
|
|
||||||
});
|
|
||||||
setStatus('Success. Returning to app...');
|
|
||||||
window.location.href = `${redirectUri}?${params.toString()}`;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body onload="init()">
|
|
||||||
<div class="card">
|
|
||||||
<h1>amiPro Passkey SSO <span class="pill">Beta</span></h1>
|
|
||||||
<p>Use passkeys to sign in. Choose production or your ngrok dev host.</p>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<button class="secondary" onclick="useProd()">Use production</button>
|
|
||||||
<button class="secondary" onclick="useDev()">Use dev/ngrok</button>
|
|
||||||
</div>
|
|
||||||
<label for="devUrl">Dev / ngrok URL</label>
|
|
||||||
<input id="devUrl" placeholder="https://amipro-dev.ngrok-free.app" />
|
|
||||||
|
|
||||||
<p class="muted">Current server: <span id="currentServer">(not set)</span></p>
|
|
||||||
|
|
||||||
<label for="uid">User ID</label>
|
|
||||||
<input id="uid" placeholder="user@example.com" />
|
|
||||||
|
|
||||||
<label for="display">Display name (optional)</label>
|
|
||||||
<input id="display" placeholder="Name shown to user" />
|
|
||||||
|
|
||||||
<label for="rpId">rpId</label>
|
|
||||||
<input id="rpId" placeholder="fido2.amipro.me or your ngrok host" value="" />
|
|
||||||
|
|
||||||
<button onclick="doLogin()">Passwordless login</button>
|
|
||||||
<button onclick="doRegister()">Register new passkey</button>
|
|
||||||
|
|
||||||
<div id="status" class="muted"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>FIDO2 Device Manager Test</title>
|
|
||||||
<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>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding: 20px;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
#device-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.user-info {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>FIDO2 Device Manager Test</h1>
|
|
||||||
|
|
||||||
<div class="user-info">
|
|
||||||
<h3>User Information</h3>
|
|
||||||
<p><strong>User ID:</strong> <span id="current-user-id">test@example.com</span></p>
|
|
||||||
<p><strong>Status:</strong> Logged in (assumed)</p>
|
|
||||||
<p><small>Note: User ID will be displayed in the device manager UI when showUserInfo is enabled.</small></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="user-id-input">Enter User ID:</label>
|
|
||||||
<input type="text" id="user-id-input" class="form-control" value="test@example.com" style="max-width: 300px;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" style="margin-top: 15px;">
|
|
||||||
<label>Options:</label>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="show-user-info" checked>
|
|
||||||
<label class="form-check-label" for="show-user-info">
|
|
||||||
Show User Info (displays user ID in UI)
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" type="checkbox" id="show-session-status" checked>
|
|
||||||
<label class="form-check-label" for="show-session-status">
|
|
||||||
Show Session Status
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" onclick="openDeviceManager()">
|
|
||||||
Open Device Manager (Modal)
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary" onclick="openStandaloneDeviceManager()">
|
|
||||||
Open Device Manager (Standalone)
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-danger" onclick="closeDeviceManager()">
|
|
||||||
Close Device Manager
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-success" onclick="openRecoveryDeviceManager()">
|
|
||||||
Open Recovery Device Manager
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div id="device-container"></div>
|
|
||||||
|
|
||||||
<div id="error-message" class="alert alert-danger" style="display: none; margin-top: 20px;"></div>
|
|
||||||
<div id="success-message" class="alert alert-success" style="display: none; margin-top: 20px;"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const SERVER_URL = 'https://fido2.amipro.me';
|
|
||||||
|
|
||||||
function showMessage(type, message) {
|
|
||||||
const element = document.getElementById(type + '-message');
|
|
||||||
element.textContent = message;
|
|
||||||
element.style.display = 'block';
|
|
||||||
setTimeout(() => {
|
|
||||||
element.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openDeviceManager() {
|
|
||||||
const userId = document.getElementById('user-id-input').value;
|
|
||||||
const showUserInfo = document.getElementById('show-user-info').checked;
|
|
||||||
const showSessionStatus = document.getElementById('show-session-status').checked;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
userId: userId || null, // userId is optional now
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL,
|
|
||||||
language: 'en-US',
|
|
||||||
features: {
|
|
||||||
showUserInfo: showUserInfo,
|
|
||||||
showSessionStatus: showSessionStatus,
|
|
||||||
showAddButton: true,
|
|
||||||
showDeleteButton: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
showMessage('success', 'Device manager initialized');
|
|
||||||
console.log('Device manager initialized:', manager);
|
|
||||||
console.log('Effective User ID:', manager.deviceManager.getEffectiveUserId());
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
showMessage('success', 'Device added successfully');
|
|
||||||
console.log('Device added:', device);
|
|
||||||
},
|
|
||||||
onDeviceDeleted: function(deviceId) {
|
|
||||||
showMessage('success', 'Device deleted successfully');
|
|
||||||
console.log('Device deleted:', deviceId);
|
|
||||||
},
|
|
||||||
onDeviceListLoaded: function(devices) {
|
|
||||||
console.log('Devices loaded:', devices);
|
|
||||||
},
|
|
||||||
onUserMismatch: function(error) {
|
|
||||||
showMessage('error', 'User mismatch: ' + error);
|
|
||||||
console.error('User mismatch error:', error);
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
showMessage('error', 'Error: ' + error.message);
|
|
||||||
console.error('Device manager error:', error);
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
console.log('Device manager closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showMessage('error', 'Failed to open device manager: ' + error.message);
|
|
||||||
console.error('Error opening device manager:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openStandaloneDeviceManager() {
|
|
||||||
const userId = document.getElementById('user-id-input').value;
|
|
||||||
const showUserInfo = document.getElementById('show-user-info').checked;
|
|
||||||
const showSessionStatus = document.getElementById('show-session-status').checked;
|
|
||||||
|
|
||||||
try {
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
userId: userId || null, // userId is optional now
|
|
||||||
mode: 'standalone',
|
|
||||||
serverUrl: SERVER_URL,
|
|
||||||
language: 'en-US',
|
|
||||||
features: {
|
|
||||||
showUserInfo: showUserInfo,
|
|
||||||
showSessionStatus: showSessionStatus,
|
|
||||||
showAddButton: true,
|
|
||||||
showDeleteButton: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
showMessage('success', 'Standalone device manager initialized');
|
|
||||||
console.log('Standalone device manager initialized:', manager);
|
|
||||||
console.log('Effective User ID:', manager.deviceManager.getEffectiveUserId());
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
showMessage('success', 'Device added successfully');
|
|
||||||
console.log('Device added:', device);
|
|
||||||
},
|
|
||||||
onDeviceDeleted: function(deviceId) {
|
|
||||||
showMessage('success', 'Device deleted successfully');
|
|
||||||
console.log('Device deleted:', deviceId);
|
|
||||||
},
|
|
||||||
onDeviceListLoaded: function(devices) {
|
|
||||||
console.log('Devices loaded:', devices);
|
|
||||||
},
|
|
||||||
onUserMismatch: function(error) {
|
|
||||||
showMessage('error', 'User mismatch: ' + error);
|
|
||||||
console.error('User mismatch error:', error);
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
showMessage('error', 'Error: ' + error.message);
|
|
||||||
console.error('Device manager error:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showMessage('error', 'Failed to open standalone device manager: ' + error.message);
|
|
||||||
console.error('Error opening standalone device manager:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeDeviceManager() {
|
|
||||||
Fido2UIManager.close();
|
|
||||||
showMessage('success', 'Device manager closed');
|
|
||||||
}
|
|
||||||
|
|
||||||
function openRecoveryDeviceManager() {
|
|
||||||
const recoverySessionId = document.getElementById('user-id-input').value;
|
|
||||||
|
|
||||||
if (!recoverySessionId) {
|
|
||||||
showMessage('error', 'Please enter a recovery session ID in the User ID field');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Fido2UIManager.renderDeviceManagerWithRecovery({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL,
|
|
||||||
language: 'en-US',
|
|
||||||
recoverySessionId: recoverySessionId,
|
|
||||||
features: {
|
|
||||||
showUserInfo: true,
|
|
||||||
showSessionStatus: false,
|
|
||||||
showAddButton: false,
|
|
||||||
showDeleteButton: false
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
showMessage('success', 'Recovery device manager initialized');
|
|
||||||
console.log('Recovery manager initialized:', manager);
|
|
||||||
console.log('Is Recovery Mode:', manager.isRecoveryMode());
|
|
||||||
console.log('Recovery User:', manager.getRecoveryUser());
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
showMessage('success', 'Device recovered successfully');
|
|
||||||
console.log('Device recovered:', device);
|
|
||||||
},
|
|
||||||
onRecoveryCompleted: function(result) {
|
|
||||||
showMessage('success', 'Recovery completed for user: ' + result.user.username);
|
|
||||||
console.log('Recovery completed:', result);
|
|
||||||
},
|
|
||||||
onRecoveryCanceled: function(user) {
|
|
||||||
console.log('Recovery canceled for user:', user);
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
showMessage('error', 'Error: ' + error.message);
|
|
||||||
console.error('Device manager error:', error);
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
console.log('Device manager closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showMessage('error', 'Failed to open recovery device manager: ' + error.message);
|
|
||||||
console.error('Error opening recovery device manager:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('FIDO2 Device Manager Test Page loaded');
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>FIDO2 Device Recovery Test</title>
|
|
||||||
<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>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
padding: 20px;
|
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
}
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
#device-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
.user-info {
|
|
||||||
background: #f5f5f5;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
.recovery-info {
|
|
||||||
background: #d4edda;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #c3e6cb;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>FIDO2 Device Recovery Test</h1>
|
|
||||||
|
|
||||||
<div class="user-info">
|
|
||||||
<h3>Normal Device Manager</h3>
|
|
||||||
<p>Use this mode when the user is already logged in and has a valid session.</p>
|
|
||||||
<button class="btn btn-primary" onclick="openNormalDeviceManager()">
|
|
||||||
Open Normal Device Manager
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recovery-info">
|
|
||||||
<h3>Recovery Mode</h3>
|
|
||||||
<p>Use this mode when user clicks a recovery link with <code>?rid=xxx</code> parameter.</p>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="recovery-session-id">Enter Recovery Session ID:</label>
|
|
||||||
<input type="text" id="recovery-session-id" class="form-control"
|
|
||||||
placeholder="Enter recovery session ID from URL"
|
|
||||||
style="max-width: 300px;">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-success" onclick="openRecoveryDeviceManager()" style="margin-top: 15px;">
|
|
||||||
Open Recovery Device Manager
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<h3>Auto-detect Recovery from URL</h3>
|
|
||||||
<p>Add <code>?rid=YOUR_SESSION_ID</code> to the URL and refresh the page.</p>
|
|
||||||
<button class="btn btn-info" onclick="openFromUrlParams()">
|
|
||||||
Render from URL Parameters
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="device-container"></div>
|
|
||||||
|
|
||||||
<div id="error-message" class="alert alert-danger" style="display: none; margin-top: 20px;"></div>
|
|
||||||
<div id="success-message" class="alert alert-success" style="display: none; margin-top: 20px;"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const SERVER_URL = 'https://fido2.amipro.me';
|
|
||||||
|
|
||||||
function showMessage(type, message) {
|
|
||||||
const element = document.getElementById(type + '-message');
|
|
||||||
element.textContent = message;
|
|
||||||
element.style.display = 'block';
|
|
||||||
setTimeout(() => {
|
|
||||||
element.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function openNormalDeviceManager() {
|
|
||||||
try {
|
|
||||||
Fido2UIManager.renderDeviceManager({
|
|
||||||
userId: 'test@example.com',
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL,
|
|
||||||
language: 'en-US',
|
|
||||||
features: {
|
|
||||||
showUserInfo: true,
|
|
||||||
showSessionStatus: true,
|
|
||||||
showAddButton: true,
|
|
||||||
showDeleteButton: true,
|
|
||||||
showRecoveryButton: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
showMessage('success', 'Normal device manager initialized');
|
|
||||||
console.log('Device manager initialized:', manager);
|
|
||||||
console.log('Is Recovery Mode:', manager.isRecoveryMode());
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
showMessage('success', 'Device added successfully');
|
|
||||||
console.log('Device added:', device);
|
|
||||||
},
|
|
||||||
onDeviceDeleted: function(deviceId) {
|
|
||||||
showMessage('success', 'Device deleted successfully');
|
|
||||||
console.log('Device deleted:', deviceId);
|
|
||||||
},
|
|
||||||
onDeviceListLoaded: function(devices) {
|
|
||||||
console.log('Devices loaded:', devices);
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
showMessage('error', 'Error: ' + error.message);
|
|
||||||
console.error('Device manager error:', error);
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
console.log('Device manager closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showMessage('error', 'Failed to open device manager: ' + error.message);
|
|
||||||
console.error('Error opening device manager:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openRecoveryDeviceManager() {
|
|
||||||
const recoverySessionId = document.getElementById('recovery-session-id').value;
|
|
||||||
|
|
||||||
if (!recoverySessionId) {
|
|
||||||
showMessage('error', 'Please enter a recovery session ID');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
Fido2UIManager.renderDeviceManagerWithRecovery({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL,
|
|
||||||
language: 'en-US',
|
|
||||||
recoverySessionId: recoverySessionId,
|
|
||||||
features: {
|
|
||||||
showUserInfo: true,
|
|
||||||
showSessionStatus: false,
|
|
||||||
showAddButton: false,
|
|
||||||
showDeleteButton: false,
|
|
||||||
showRecoveryButton: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
showMessage('success', 'Recovery device manager initialized');
|
|
||||||
console.log('Recovery manager initialized:', manager);
|
|
||||||
console.log('Is Recovery Mode:', manager.isRecoveryMode());
|
|
||||||
console.log('Recovery User:', manager.getRecoveryUser());
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
showMessage('success', 'Device recovered successfully');
|
|
||||||
console.log('Device recovered:', device);
|
|
||||||
},
|
|
||||||
onDeviceListLoaded: function(devices) {
|
|
||||||
console.log('Devices loaded:', devices);
|
|
||||||
},
|
|
||||||
onRecoveryCompleted: function(result) {
|
|
||||||
showMessage('success', 'Recovery completed for user: ' + result.user.username);
|
|
||||||
console.log('Recovery completed:', result);
|
|
||||||
},
|
|
||||||
onRecoveryCanceled: function(user) {
|
|
||||||
console.log('Recovery canceled for user:', user);
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
showMessage('error', 'Error: ' + error.message);
|
|
||||||
console.error('Device manager error:', error);
|
|
||||||
},
|
|
||||||
onClose: function() {
|
|
||||||
console.log('Device manager closed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showMessage('error', 'Failed to open recovery device manager: ' + error.message);
|
|
||||||
console.error('Error opening recovery device manager:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openFromUrlParams() {
|
|
||||||
try {
|
|
||||||
Fido2UIManager.renderFromUrlParams({
|
|
||||||
container: '#device-container',
|
|
||||||
mode: 'modal',
|
|
||||||
serverUrl: SERVER_URL,
|
|
||||||
language: 'en-US',
|
|
||||||
features: {
|
|
||||||
showUserInfo: true,
|
|
||||||
showSessionStatus: true,
|
|
||||||
showAddButton: true,
|
|
||||||
showDeleteButton: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onInit: function(manager) {
|
|
||||||
if (manager.isRecoveryMode()) {
|
|
||||||
showMessage('success', 'Recovery mode started from URL');
|
|
||||||
console.log('Recovery User:', manager.getRecoveryUser());
|
|
||||||
} else {
|
|
||||||
showMessage('success', 'Normal mode');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDeviceAdded: function(device) {
|
|
||||||
showMessage('success', 'Device added successfully');
|
|
||||||
},
|
|
||||||
onRecoveryCompleted: function(result) {
|
|
||||||
showMessage('success', 'Recovery completed!');
|
|
||||||
},
|
|
||||||
onError: function(error) {
|
|
||||||
showMessage('error', 'Error: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showMessage('error', 'Failed: ' + error.message);
|
|
||||||
console.error('Error:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('FIDO2 Device Recovery Test Page loaded');
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,316 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN" class="light-style customizer-hide">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Fido2 Login SDK Test</title>
|
|
||||||
<link rel="stylesheet" href="files/core.css" />
|
|
||||||
<link rel="stylesheet" href="files/theme-default.css" />
|
|
||||||
<link rel="stylesheet" href="files/fido2-ui-sdk.css" />
|
|
||||||
<style>
|
|
||||||
.status-indicator {
|
|
||||||
display: inline-block;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
.status-registered { background-color: #28a745; }
|
|
||||||
.status-unregistered { background-color: #dc3545; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container py-5">
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-md-10">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<h4 class="mb-0">Fido2 Login SDK 测试</h4>
|
|
||||||
<div>
|
|
||||||
<span class="status-indicator" id="regStatusIndicator"></span>
|
|
||||||
<small class="text-muted" id="regStatusText">检查中...</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5>设备注册测试</h5>
|
|
||||||
<div class="mb-2">
|
|
||||||
<input type="text" class="form-control" id="testUserId" placeholder="输入User ID" value="testuser">
|
|
||||||
</div>
|
|
||||||
<div class="mb-2">
|
|
||||||
<input type="text" class="form-control" id="testDisplayName" placeholder="显示名称" value="测试设备">
|
|
||||||
</div>
|
|
||||||
<div class="alert alert-info py-1 px-2 mb-2" style="font-size: 12px;">
|
|
||||||
<small>💡 <strong>提示:</strong>使用新User ID注册可测试自动登录流程</small>
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-success btn-sm me-2" id="registerDeviceBtn">
|
|
||||||
注册Passkey设备
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-outline-primary btn-sm" id="checkRegBtn">
|
|
||||||
检查状态
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="card bg-light">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5>登录组件测试</h5>
|
|
||||||
<button class="btn btn-primary me-2" id="testLoginBtn">
|
|
||||||
启动登录组件
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-secondary me-2" id="testDestroyBtn">
|
|
||||||
销毁组件
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-outline-info btn-sm" id="checkStateBtn">
|
|
||||||
检查状态
|
|
||||||
</button>
|
|
||||||
<div class="mt-2">
|
|
||||||
<small class="text-muted" id="loginStateText">未启动</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<h5>测试日志:</h5>
|
|
||||||
<div id="testLog" class="bg-light p-3" style="max-height: 300px; overflow-y: auto; font-family: monospace; font-size: 12px;">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="login-container"></div>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
// 清理旧会话(只清理会话,不影响注册状态)
|
|
||||||
logoutFido2UserSession();
|
|
||||||
console.log('已清理旧会话');
|
|
||||||
console.log('dfido2_lib_registered:', localStorage.getItem('dfido2_lib_registered'));
|
|
||||||
|
|
||||||
// 通过配置传递服务器URL,不直接操作localStorage
|
|
||||||
window.serverUrl = 'https://local.dqj-macpro.com';
|
|
||||||
|
|
||||||
// 设置调试日志函数
|
|
||||||
window.fido2LoginDebug = function(message) {
|
|
||||||
log(`[SDK] ${message}`, 'info');
|
|
||||||
};
|
|
||||||
|
|
||||||
function log(message, type = 'info') {
|
|
||||||
const logEl = document.getElementById('testLog');
|
|
||||||
const time = new Date().toLocaleTimeString();
|
|
||||||
const color = type === 'error' ? 'red' : type === 'success' ? 'green' : type === 'warn' ? 'orange' : type === 'debug' ? 'purple' : 'blue';
|
|
||||||
logEl.innerHTML += `<div style="color: ${color};">[${time}] ${message}</div>`;
|
|
||||||
logEl.scrollTop = logEl.scrollHeight;
|
|
||||||
console.log(`[${type.toUpperCase()}]`, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置调试日志函数
|
|
||||||
window.fido2LoginDebug = function(message) {
|
|
||||||
log(`[SDK] ${message}`, 'info');
|
|
||||||
};
|
|
||||||
|
|
||||||
function log(message, type = 'info') {
|
|
||||||
const logEl = document.getElementById('testLog');
|
|
||||||
const time = new Date().toLocaleTimeString();
|
|
||||||
const color = type === 'error' ? 'red' : type === 'success' ? 'green' : type === 'warn' ? 'orange' : type === 'debug' ? 'purple' : 'blue';
|
|
||||||
logEl.innerHTML += `<div style="color: ${color};">[${time}] ${message}</div>`;
|
|
||||||
logEl.scrollTop = logEl.scrollHeight;
|
|
||||||
console.log(`[${type.toUpperCase()}]`, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateRegStatus() {
|
|
||||||
const registered = localStorage.getItem('dfido2_lib_registered');
|
|
||||||
const indicator = document.getElementById('regStatusIndicator');
|
|
||||||
const text = document.getElementById('regStatusText');
|
|
||||||
|
|
||||||
if (registered) {
|
|
||||||
indicator.className = 'status-indicator status-registered';
|
|
||||||
text.textContent = '已注册设备';
|
|
||||||
} else {
|
|
||||||
indicator.className = 'status-indicator status-unregistered';
|
|
||||||
text.textContent = '未注册设备';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLoginState(state) {
|
|
||||||
document.getElementById('loginStateText').textContent = state || '未启动';
|
|
||||||
}
|
|
||||||
|
|
||||||
let loginManager = null;
|
|
||||||
|
|
||||||
// 页面加载时检查状态
|
|
||||||
updateRegStatus();
|
|
||||||
log('测试页面已加载完成');
|
|
||||||
log('💡 使用新User ID注册设备,然后启动登录组件测试自动Fido2登录');
|
|
||||||
|
|
||||||
// 注册设备
|
|
||||||
document.getElementById('registerDeviceBtn').addEventListener('click', async function() {
|
|
||||||
const userId = document.getElementById('testUserId').value.trim();
|
|
||||||
const displayName = document.getElementById('testDisplayName').value.trim() || 'Device-' + userId;
|
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
log('请输入User ID', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log(`正在为用户 ${userId} 注册设备...`);
|
|
||||||
log(`服务器URL: ${window.serverUrl}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await registerFido2(userId, displayName, null);
|
|
||||||
if (result.status === 'ok') {
|
|
||||||
log(`✅ 设备注册成功!`, 'success');
|
|
||||||
log(`dfido2_lib_registered: ${localStorage.getItem('dfido2_lib_registered')}`);
|
|
||||||
updateRegStatus();
|
|
||||||
} else {
|
|
||||||
log(`❌ 设备注册失败: ${result.errorMessage}`, 'error');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
log(`❌ 注册异常: ${error.message}`, 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查注册状态
|
|
||||||
document.getElementById('checkRegBtn').addEventListener('click', function() {
|
|
||||||
const registered = localStorage.getItem('dfido2_lib_registered');
|
|
||||||
const session = sessionStorage.getItem('fido2_user_session');
|
|
||||||
log(`dfido2_lib_registered: ${registered || 'null'}`);
|
|
||||||
log(`fido2_user_session: ${session ? '存在' : 'null'}`);
|
|
||||||
log(`canTryAutoAuthentication(): ${canTryAutoAuthentication()}`);
|
|
||||||
updateRegStatus();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 检查登录状态
|
|
||||||
document.getElementById('checkStateBtn').addEventListener('click', function() {
|
|
||||||
if (loginManager) {
|
|
||||||
log(`登录组件状态: ${loginManager.getState()}`);
|
|
||||||
log(`登录模式: ${loginManager.getMode()}`);
|
|
||||||
log(`尝试次数: ${loginManager.getAttemptCount()}/${loginManager.maxAttempts}`);
|
|
||||||
updateLoginState(loginManager.getState() + ' - ' + loginManager.getMode());
|
|
||||||
} else {
|
|
||||||
log('登录组件未启动');
|
|
||||||
updateLoginState('未启动');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 启动登录组件
|
|
||||||
document.getElementById('testLoginBtn').addEventListener('click', function() {
|
|
||||||
// 确保调试函数已设置
|
|
||||||
window.fido2LoginDebug = function(message) {
|
|
||||||
log(`[SDK] ${message}`, 'info');
|
|
||||||
};
|
|
||||||
|
|
||||||
log('========================================');
|
|
||||||
log('启动登录组件...');
|
|
||||||
log(`canTryAutoAuthentication() = ${canTryAutoAuthentication()}`);
|
|
||||||
log(`dfido2_lib_registered = ${localStorage.getItem('dfido2_lib_registered')}`);
|
|
||||||
log('========================================');
|
|
||||||
|
|
||||||
if (loginManager) {
|
|
||||||
log('已有登录组件实例,先销毁');
|
|
||||||
loginManager.destroy();
|
|
||||||
loginManager = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 清理旧的modal元素和容器内容
|
|
||||||
const container = document.querySelector('#login-container');
|
|
||||||
if (container) {
|
|
||||||
container.innerHTML = '';
|
|
||||||
}
|
|
||||||
const oldModal = document.querySelector('.fido2-sdk-login-modal');
|
|
||||||
if (oldModal) {
|
|
||||||
oldModal.remove();
|
|
||||||
log('清理旧modal元素');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 短暂延迟确保DOM清理完成
|
|
||||||
setTimeout(function() {
|
|
||||||
loginManager = Fido2UIManager.renderLogin({
|
|
||||||
serverUrl: window.serverUrl, // 通过SDK配置传递服务器URL
|
|
||||||
container: '#login-container',
|
|
||||||
features: {
|
|
||||||
autoAuth: true,
|
|
||||||
enablePasswordLogin: true,
|
|
||||||
autoShowPassword: false,
|
|
||||||
maxPasswordAttempts: 3,
|
|
||||||
showRemainingAttempts: true
|
|
||||||
},
|
|
||||||
callbacks: {
|
|
||||||
onFido2Success: function(userId, session) {
|
|
||||||
log(`✅✅✅ Fido2登录成功: ${userId}`, 'success');
|
|
||||||
log(`Session: ${session ? '存在' : 'null'}`);
|
|
||||||
updateLoginState('登录成功');
|
|
||||||
},
|
|
||||||
onFido2Error: function(error) {
|
|
||||||
log(`❌ Fido2失败: [${error.code}] ${error.message}`, 'error');
|
|
||||||
updateLoginState('Fido2失败: ' + error.code);
|
|
||||||
if (error.originalError) {
|
|
||||||
log(`原始错误: ${JSON.stringify(error.originalError).substring(0, 300)}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onPasswordLogin: function(userId, password) {
|
|
||||||
log(`🔐 密码登录尝试: userId="${userId}", password="***${password.slice(-2)}"`);
|
|
||||||
// 模拟密码验证
|
|
||||||
return new Promise(function(resolve) {
|
|
||||||
setTimeout(function() {
|
|
||||||
// This is demo code, accept any password
|
|
||||||
log('✅ 密码验证成功', 'success');
|
|
||||||
updateLoginState('密码登录成功');
|
|
||||||
resolve(true);
|
|
||||||
}, 500);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPasswordExhausted: function(userId, attempts, maxAttempts) {
|
|
||||||
log(`🚫 密码重试次数耗尽: ${userId} (${attempts}/${maxAttempts})`, 'error');
|
|
||||||
updateLoginState('密码次数耗尽');
|
|
||||||
},
|
|
||||||
onLoginClosed: function() {
|
|
||||||
log('🔒 登录UI已关闭');
|
|
||||||
updateLoginState('已关闭');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
primaryColor: '#ce59d6',
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
borderRadius: '8px'
|
|
||||||
},
|
|
||||||
language: 'zh-CN'
|
|
||||||
});
|
|
||||||
|
|
||||||
log('✅ 登录组件已启动');
|
|
||||||
}, 100);
|
|
||||||
} catch (error) {
|
|
||||||
log(`❌ 启动失败: ${error.message}`, 'error');
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 销毁组件
|
|
||||||
document.getElementById('testDestroyBtn').addEventListener('click', function() {
|
|
||||||
if (loginManager) {
|
|
||||||
log('销毁登录组件...');
|
|
||||||
loginManager.destroy();
|
|
||||||
loginManager = null;
|
|
||||||
updateLoginState('已销毁');
|
|
||||||
log('✅ 组件已销毁');
|
|
||||||
} else {
|
|
||||||
log('没有活动的登录组件');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
Reference in New Issue
Block a user