Moved ito Gitea
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.DS_Store
|
||||
7
.well-known/apple-app-site-association
Normal file
7
.well-known/apple-app-site-association
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"webcredentials":{
|
||||
"apps":[
|
||||
"RQQ6N82NA8.dqj.dFido2LibDemo"
|
||||
]
|
||||
}
|
||||
}
|
||||
5
.well-known/apple-app-site-association.bak
Normal file
5
.well-known/apple-app-site-association.bak
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"webcredentials":{
|
||||
"apps":["TWH42HVGKA.dqj.dFido2LibDemo"]
|
||||
}
|
||||
}
|
||||
25
.well-known/assetlinks.json
Normal file
25
.well-known/assetlinks.json
Normal file
@@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"relation" : [
|
||||
"delegate_permission/common.handle_all_urls",
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"target" : {
|
||||
"namespace" : "web",
|
||||
"site" : "https://test.amipro.me"
|
||||
}
|
||||
},
|
||||
{
|
||||
"relation" : [
|
||||
"delegate_permission/common.handle_all_urls",
|
||||
"delegate_permission/common.get_login_creds"
|
||||
],
|
||||
"target" : {
|
||||
"namespace" : "android_app",
|
||||
"package_name" : "dqj.dfido2lib.demo",
|
||||
"sha256_cert_fingerprints" : [
|
||||
"08:A0:7E:3C:6A:40:E1:6E:BB:DC:F1:79:0E:1B:A4:4D:24:6F:DE:D2:1A:DC:D2:13:CD:A2:0A:AA:1D:48:D8:EC"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
406
devices.html
Normal file
406
devices.html
Normal file
@@ -0,0 +1,406 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html
|
||||
lang="en-US"
|
||||
class="light-style"
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
|
||||
/>
|
||||
|
||||
<title>Devices - amiPro sample site</title>
|
||||
|
||||
<meta name="description" content="" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="files/favicon.ico" />
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons. Uncomment required icon fonts -->
|
||||
<link rel="stylesheet" href="files/boxicons.css?v=20230405" />
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link rel="stylesheet" href="files/core.css" class="template-customizer-core-css" />
|
||||
<link rel="stylesheet" href="files/theme-default.css" class="template-customizer-theme-css" />
|
||||
<link rel="stylesheet" href="files/demo.css" />
|
||||
|
||||
<!-- Vendors CSS -->
|
||||
<link rel="stylesheet" href="files/perfect-scrollbar.css" />
|
||||
|
||||
<!-- Page CSS -->
|
||||
|
||||
<!-- Helpers -->
|
||||
<script src="files/helpers.js"></script>
|
||||
|
||||
<!--! Template customizer & Theme config files MUST be included after core stylesheets and helpers.js in the <head> section -->
|
||||
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
|
||||
<script src="files/config.js"></script>
|
||||
|
||||
<script src="files/jquery.js"></script>
|
||||
<script src="files/popper.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/perfect-scrollbar.js"></script>
|
||||
<script src="files/menu.js"></script>
|
||||
<script src="files/main.js"></script>
|
||||
|
||||
<script src="files/ua-parser.js"></script>
|
||||
|
||||
<script src="files/amipro_utils.js?v=20230401402"></script>
|
||||
<script src="files/dfido2-lib.js?v=20230918"></script>
|
||||
|
||||
<script>
|
||||
|
||||
var user_id, reg_session_id;
|
||||
|
||||
const i18n_messages = new Map();
|
||||
|
||||
var lang_map = new Map();
|
||||
lang_map.set("en-US", "Add device");
|
||||
lang_map.set("zh-CN", "添加设备");
|
||||
lang_map.set("ja", "デバイスを追加");
|
||||
i18n_messages.set("btn_add", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "My devices");
|
||||
lang_map.set("zh-CN", "我的设备");
|
||||
lang_map.set("ja", "マイデバイス");
|
||||
i18n_messages.set("my_devices", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device registered");
|
||||
lang_map.set("zh-CN", "添加设备成功");
|
||||
lang_map.set("ja", "デバイス登録完了。");
|
||||
i18n_messages.set("msg_register_ok", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Device");
|
||||
lang_map.set("zh-CN", "设备");
|
||||
lang_map.set("ja", "デバイス");
|
||||
i18n_messages.set("title_device", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Registered time");
|
||||
lang_map.set("zh-CN", "添加时间");
|
||||
lang_map.set("ja", "登録時間");
|
||||
i18n_messages.set("title_time", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Actions");
|
||||
lang_map.set("zh-CN", "操作");
|
||||
lang_map.set("ja", "操作");
|
||||
i18n_messages.set("title_act", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Delete");
|
||||
lang_map.set("zh-CN", "删除");
|
||||
lang_map.set("ja", "削除");
|
||||
i18n_messages.set("title_del", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Contact us");
|
||||
lang_map.set("zh-CN", "联系我们");
|
||||
lang_map.set("ja", "お問い合わせ");
|
||||
i18n_messages.set("title_contact", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Log out");
|
||||
lang_map.set("zh-CN", "登出");
|
||||
lang_map.set("ja", "ログアウト");
|
||||
i18n_messages.set("title_logout", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "No devices, please add.");
|
||||
lang_map.set("zh-CN", "无设备,请添加。");
|
||||
lang_map.set("ja", "デバイスがなし、追加してください。");
|
||||
i18n_messages.set("title_empty_list", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Deleted device.");
|
||||
lang_map.set("zh-CN", "设备删除成功。");
|
||||
lang_map.set("ja", "デバイスを削除しました。");
|
||||
i18n_messages.set("msg_deldev_ok", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Do you want to delete this device?");
|
||||
lang_map.set("zh-CN", "确认删除此设备吗?");
|
||||
lang_map.set("ja", "デバイスを削除しますか?");
|
||||
i18n_messages.set("msg_confirm_deldev", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "The fido2 session is good.");
|
||||
lang_map.set("zh-CN", "FIDO2会话正常。");
|
||||
lang_map.set("ja", "FIDO2セッションは正常です。");
|
||||
i18n_messages.set("msg_session_status_ok", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "The fido2 session is invalid. You can refuse accessing to your site's pages.");
|
||||
lang_map.set("zh-CN", "FIDO2会话无效。您可以拒绝访问您网站的页面。");
|
||||
lang_map.set("ja", "FIDO2セッションは無効です。あなたのサイトのページにアクセスすることを拒否できます。");
|
||||
i18n_messages.set("msg_session_status_fail", lang_map);
|
||||
|
||||
window.onload = async function() {
|
||||
setI18NText(i18n_messages)
|
||||
|
||||
|
||||
setSessionStatus();
|
||||
|
||||
let url = new URL(window.location.href);
|
||||
let params = url.searchParams;
|
||||
reg_session_id = params.get("rid");
|
||||
user_id = params.get("uid");
|
||||
|
||||
if(reg_session_id && 0 < reg_session_id.length){
|
||||
const reg_unm_json = await getRegistrationUser(reg_session_id);
|
||||
user_id = ''
|
||||
if(reg_unm_json && reg_unm_json.status === 'ok'){
|
||||
user_id = reg_unm_json.username;
|
||||
await addDevice(reg_unm_json.displayname)
|
||||
}else{
|
||||
window.location.href = "login.html";
|
||||
}
|
||||
}
|
||||
$('#user_id').html(user_id);
|
||||
listDevices();
|
||||
}
|
||||
|
||||
async function setSessionStatus(){
|
||||
const sessionOk = await validSession();
|
||||
if(sessionOk){
|
||||
$('#session_status').html(getI18NText(i18n_messages, 'msg_session_status_ok'))
|
||||
}else{
|
||||
$('#session_status').html(getI18NText(i18n_messages, 'msg_session_status_fail'))
|
||||
}
|
||||
}
|
||||
|
||||
async function addDevice(display_name){
|
||||
//You can change the display name or ask the user to input it, if the display_name is null.
|
||||
const result = await registerFido2(user_id, display_name?display_name:'dis_'+user_id);
|
||||
|
||||
if(result.status === 'ok'){
|
||||
//You can try to insert user_id into your user db now if this user_id does not exist in your db.
|
||||
|
||||
alert($('#msg_register_ok').html());
|
||||
listDevices();
|
||||
}else{
|
||||
errProcessFido2(result)
|
||||
}
|
||||
|
||||
setSessionStatus();
|
||||
}
|
||||
|
||||
async function delDevice(device_id){
|
||||
if(window.confirm(getI18NText(i18n_messages, 'msg_confirm_deldev'))){
|
||||
const result = await delUserDeviceFido2(device_id)
|
||||
if("ok" === result.status){
|
||||
alert(getI18NText(i18n_messages, 'msg_deldev_ok'));
|
||||
listDevices();
|
||||
}else{
|
||||
const msg = getI18NErrorMessage(result.errorMessage);
|
||||
alert(msg?msg:result.errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function listDevices(){
|
||||
const result = await listUserDevicesFido2()
|
||||
$("#devices_list").html("");
|
||||
if("ok" === result.status){
|
||||
let lst_html = ""
|
||||
|
||||
for(let dev of result.devices){
|
||||
var dev_desc = dev.desc
|
||||
if(!dev_desc || 0 === dev_desc.length){
|
||||
let parser = new UAParser(dev.userAgent);
|
||||
|
||||
if(parser.getOS().name){
|
||||
dev_desc = parser.getDevice().model + ',' + parser.getOS().name + ',' + parser.getBrowser().name;
|
||||
}else{
|
||||
dev_desc = dev.userAgent;
|
||||
}
|
||||
}
|
||||
|
||||
var date = new Date(dev.registered_time);
|
||||
|
||||
lst_html += '<tr><td><strong>'+ dev_desc +'</strong></td><td>'+
|
||||
date.toLocaleString()+'</td><td>'+
|
||||
"<a style=\"padding-left: 0px;\" class=\"dropdown-item\" href=\"javascript:delDevice('"+dev.device_id+"');\">"+
|
||||
'<i class="bx bx-trash me-1"></i><span id="title_del"> '+ getI18NText(i18n_messages, 'title_del') +'</span></a></div></td></tr>'
|
||||
}
|
||||
$("#devices_list").html(lst_html);
|
||||
}else{
|
||||
const msg = getI18NErrorMessage(result.errorMessage); //fido2LibErrMsgLanguages.japanese
|
||||
alert(msg?msg:result.errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="msg_register_ok" style="display:none;">Device registered</div>
|
||||
|
||||
<!-- Layout wrapper -->
|
||||
<div class="layout-wrapper layout-content-navbar">
|
||||
<div class="layout-container">
|
||||
<!-- Layout container -->
|
||||
<div class="layout-page">
|
||||
<!-- Navbar -->
|
||||
|
||||
<nav
|
||||
class="layout-navbar container-xxl navbar navbar-expand-xl navbar-detached align-items-center bg-navbar-theme"
|
||||
id="layout-navbar"
|
||||
>
|
||||
<div class="layout-menu-toggle navbar-nav align-items-xl-center me-3 me-xl-0 d-xl-none">
|
||||
<a class="nav-item nav-link px-0 me-xl-4" href="javascript:void(0)">
|
||||
<i class="bx bx-menu bx-sm"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-nav-right d-flex align-items-center" id="navbar-collapse">
|
||||
<!-- Search -->
|
||||
<div class="navbar-nav align-items-center">
|
||||
<div class="nav-item d-flex align-items-center">
|
||||
<i class="bx bx-user fs-4 lh-0"></i>
|
||||
WELCOME <span id="session_status" style="padding-left: 20px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Search -->
|
||||
|
||||
<ul class="navbar-nav flex-row align-items-center ms-auto">
|
||||
|
||||
<!-- User -->
|
||||
<li class="nav-item navbar-dropdown dropdown-user dropdown">
|
||||
<a class="nav-link dropdown-toggle hide-arrow" href="javascript:void(0);" data-bs-toggle="dropdown">
|
||||
<div class="avatar avatar-online">
|
||||
<img src="files/avatar.png" alt class="w-px-40 h-auto rounded-circle" />
|
||||
</div>
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<a class="dropdown-item" href="#">
|
||||
<div class="d-flex">
|
||||
<div class="flex-shrink-0 me-3">
|
||||
<div class="avatar avatar-online">
|
||||
<img src="files/avatar.png" alt class="w-px-40 h-auto rounded-circle" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<span class="fw-semibold d-block" id="user_id">User</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div class="dropdown-divider"></div>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="login.html">
|
||||
<i class="bx bx-power-off me-2"></i>
|
||||
<span class="align-middle" id="title_logout">Log Out</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<!--/ User -->
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- / Navbar -->
|
||||
|
||||
<!-- Content wrapper -->
|
||||
<div class="content-wrapper">
|
||||
<!-- Content -->
|
||||
|
||||
<div class="container-xxl flex-grow-1 container-p-y">
|
||||
|
||||
<!-- Basic Bootstrap Table -->
|
||||
<div class="card">
|
||||
<h5 class="card-header" id="my_devices">My devices</h5>
|
||||
<button style="width:30%;float: right;margin-left: 66%;" class="btn btn-info" onclick="addDevice();"
|
||||
id="btn_add">Add device</button>
|
||||
<!-- button style="width:30%;float: right;margin-left: 66%;" class="btn btn-info" onclick="listDevices();"
|
||||
id="btn_add">List</button -->
|
||||
<div class="table-responsive text-nowrap">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="title_device">Device</th>
|
||||
<th id="title_time">Registered time</th>
|
||||
<th id="title_act">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="table-border-bottom-0" id="devices_list">
|
||||
<tr>
|
||||
<td colspan="3" style="text-align: center;" id="title_empty_list"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--/ Basic Bootstrap Table -->
|
||||
|
||||
</div>
|
||||
<!-- / Content -->
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="content-footer footer bg-footer-theme">
|
||||
<div class="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
|
||||
<div class="mb-2 mb-md-0">
|
||||
©
|
||||
<script>
|
||||
document.write(new Date().getFullYear());
|
||||
</script>
|
||||
<a href="https://www.amipro.me" target="_blank" class="footer-link fw-bolder">amiPro</a>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<a
|
||||
href="mailto:sales@amipro.me?subject=contact"
|
||||
target="_blank"
|
||||
class="footer-link me-4"
|
||||
id="title_contact"
|
||||
>Contact</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<!-- / Footer -->
|
||||
|
||||
<div class="content-backdrop fade"></div>
|
||||
</div>
|
||||
<!-- Content wrapper -->
|
||||
</div>
|
||||
<!-- / Layout page -->
|
||||
</div>
|
||||
|
||||
<!-- Overlay -->
|
||||
<div class="layout-overlay layout-menu-toggle"></div>
|
||||
</div>
|
||||
<!-- / Layout wrapper -->
|
||||
|
||||
<!-- div class="buy-now">
|
||||
<a
|
||||
href="https://themeselection.com/products/sneat-bootstrap-html-admin-template/"
|
||||
target="_blank"
|
||||
class="btn btn-danger btn-buy-now"
|
||||
>Upgrade to Pro</a
|
||||
>
|
||||
</div -->
|
||||
|
||||
<!-- Page JS -->
|
||||
|
||||
<!-- Place this tag in your head or just before your close body tag. -->
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
36
files/amipro_utils.js
Normal file
36
files/amipro_utils.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* amiPro utils
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function setI18NText(i18n_map){
|
||||
for (const key of i18n_map.keys()) {
|
||||
const elm = $("#"+key);
|
||||
if(elm){
|
||||
const lang = window.navigator.language;
|
||||
var elem = i18n_map.get(key)
|
||||
var msg = null
|
||||
if(elem){
|
||||
msg = elem.get(lang)
|
||||
if(!msg)msg=elem.get('en-US');
|
||||
}
|
||||
if(!msg)msg = key+"-"+lang
|
||||
|
||||
$("#"+key).html(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getI18NText(i18n_map, key){
|
||||
const lang = window.navigator.language;
|
||||
var elem = i18n_map.get(key)
|
||||
var msg = null
|
||||
if(elem){
|
||||
msg = elem.get(lang)
|
||||
if(!msg)msg=elem.get('en-US');
|
||||
}
|
||||
if(!msg)msg = key+"-"+lang
|
||||
|
||||
return msg
|
||||
}
|
||||
BIN
files/avatar.png
Normal file
BIN
files/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
809
files/bootstrap.js
vendored
Normal file
809
files/bootstrap.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6511
files/boxicons.css
Normal file
6511
files/boxicons.css
Normal file
File diff suppressed because it is too large
Load Diff
BIN
files/boxicons/boxicons.eot
Normal file
BIN
files/boxicons/boxicons.eot
Normal file
Binary file not shown.
1551
files/boxicons/boxicons.svg
Normal file
1551
files/boxicons/boxicons.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.1 MiB |
BIN
files/boxicons/boxicons.ttf
Normal file
BIN
files/boxicons/boxicons.ttf
Normal file
Binary file not shown.
BIN
files/boxicons/boxicons.woff
Normal file
BIN
files/boxicons/boxicons.woff
Normal file
Binary file not shown.
BIN
files/boxicons/boxicons.woff2
Normal file
BIN
files/boxicons/boxicons.woff2
Normal file
Binary file not shown.
27
files/config.js
Normal file
27
files/config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Config
|
||||
* -------------------------------------------------------------------------------------
|
||||
* ! IMPORTANT: Make sure you clear the browser local storage In order to see the config changes in the template.
|
||||
* ! To clear local storage: (https://www.leadshook.com/help/how-to-clear-local-storage-in-google-chrome-browser/).
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// JS global variables
|
||||
let config = {
|
||||
colors: {
|
||||
primary: '#696cff',
|
||||
secondary: '#8592a3',
|
||||
success: '#71dd37',
|
||||
info: '#03c3ec',
|
||||
warning: '#ffab00',
|
||||
danger: '#ff3e1d',
|
||||
dark: '#233446',
|
||||
black: '#000',
|
||||
white: '#fff',
|
||||
body: '#f4f5fb',
|
||||
headingColor: '#566a7f',
|
||||
axisColor: '#a1acb8',
|
||||
borderColor: '#eceef1'
|
||||
}
|
||||
};
|
||||
16643
files/core.css
Normal file
16643
files/core.css
Normal file
File diff suppressed because one or more lines are too long
107
files/demo.css
Normal file
107
files/demo.css
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* demo.css
|
||||
* File include item demo only specific css only
|
||||
******************************************************************************/
|
||||
|
||||
.menu .app-brand.demo {
|
||||
height: 64px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.app-brand-logo.demo svg {
|
||||
width: 22px;
|
||||
height: 38px;
|
||||
}
|
||||
|
||||
.app-brand-text.demo {
|
||||
font-size: 1.75rem;
|
||||
letter-spacing: -0.5px;
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
/* ! For .layout-navbar-fixed added fix padding top tpo .layout-page */
|
||||
/* Detached navbar */
|
||||
.layout-navbar-fixed .layout-wrapper:not(.layout-horizontal):not(.layout-without-menu) .layout-page {
|
||||
padding-top: 76px !important;
|
||||
}
|
||||
/* Default navbar */
|
||||
.layout-navbar-fixed .layout-wrapper:not(.layout-without-menu) .layout-page {
|
||||
padding-top: 64px !important;
|
||||
}
|
||||
|
||||
/* Navbar page z-index issue solution */
|
||||
.content-wrapper .navbar {
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
* Content
|
||||
******************************************************************************/
|
||||
|
||||
.demo-blocks > * {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.demo-inline-spacing > * {
|
||||
margin: 1rem 0.375rem 0 0 !important;
|
||||
}
|
||||
|
||||
/* ? .demo-vertical-spacing class is used to have vertical margins between elements. To remove margin-top from the first-child, use .demo-only-element class with .demo-vertical-spacing class. For example, we have used this class in forms-input-groups.html file. */
|
||||
.demo-vertical-spacing > * {
|
||||
margin-top: 1rem !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.demo-vertical-spacing.demo-only-element > :first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.demo-vertical-spacing-lg > * {
|
||||
margin-top: 1.875rem !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.demo-vertical-spacing-lg.demo-only-element > :first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.demo-vertical-spacing-xl > * {
|
||||
margin-top: 5rem !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.demo-vertical-spacing-xl.demo-only-element > :first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.rtl-only {
|
||||
display: none !important;
|
||||
text-align: left !important;
|
||||
direction: ltr !important;
|
||||
}
|
||||
|
||||
[dir='rtl'] .rtl-only {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/*
|
||||
* Layout demo
|
||||
******************************************************************************/
|
||||
|
||||
.layout-demo-wrapper {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
align-items: center;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-box-direction: normal;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.layout-demo-placeholder img {
|
||||
width: 900px;
|
||||
}
|
||||
.layout-demo-info {
|
||||
text-align: center;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
616
files/dfido2-lib.js
Normal file
616
files/dfido2-lib.js
Normal file
@@ -0,0 +1,616 @@
|
||||
const DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION = 'fido2_user_session'
|
||||
const DFIDO2_LIB_LOCALSTG_NAME_REGISTERED = 'dfido2_lib_registered'
|
||||
const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url'
|
||||
|
||||
/** ===APIs=== */
|
||||
|
||||
if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)){
|
||||
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL, 'https://fido2.amipro.me')
|
||||
}
|
||||
|
||||
function setFidoServerURL(url){
|
||||
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL, url);
|
||||
}
|
||||
|
||||
function canTryAutoAuthentication(){
|
||||
//const session_text = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
//alert('canTryAuth:'+session_text+"|"+(null != localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED)))
|
||||
return null != localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED)
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} userId
|
||||
* @param {String} rpId
|
||||
*/
|
||||
async function authenticateFido2(userId = null, rpId = null) {
|
||||
var result
|
||||
result = await doAssertion(userId, rpId);
|
||||
if(result.status === 'ok'){
|
||||
sessionStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION,
|
||||
JSON.stringify({session:result.session, uid:result.username}))
|
||||
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED, new Date());
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} userId
|
||||
* @param {String} rpId
|
||||
*/
|
||||
async function registerFido2(userId, userDisplay, rpId = null) {
|
||||
if (isWebAuthnSupported()) {
|
||||
const result = await doAttestation(userId, userDisplay, rpId);
|
||||
if(result.status === 'ok'){
|
||||
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED, new Date());
|
||||
sessionStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION, JSON.stringify({session:result.session, uid:result.username}))
|
||||
}
|
||||
return result
|
||||
}else return {status:'failed', errorMessage: getI18NErrorMessage('Fido2LibErr101:')}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} rpId
|
||||
* @returns
|
||||
*/
|
||||
async function listUserDevicesFido2(rpId = null) {
|
||||
try {
|
||||
const session_text = sessionStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
if(!session_text) return {status:'ok', devices:[]}
|
||||
|
||||
const session_data = JSON.parse(session_text)
|
||||
|
||||
let req = {session: session_data.session}
|
||||
if (rpId && 0 < rpId.length) {
|
||||
req.rp = { id: rpId };
|
||||
}
|
||||
|
||||
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/dvc/lst", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
});
|
||||
const resp = await response.json();
|
||||
if ('ok' === resp.status && resp.session === session_data.session) {
|
||||
return {status:'ok', devices:resp.devices}
|
||||
} else {
|
||||
return {status:'failed', errorMessage: resp.errorMessage}
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
let msg = err.message ? err.message : err;
|
||||
//console.error("Assertion err: ", err);
|
||||
var errRtn = {status:'failed', errorMessage: msg};
|
||||
if(err.name) errRtn.name = err.name
|
||||
return errRtn;
|
||||
}
|
||||
}
|
||||
|
||||
async function delUserDeviceFido2(device_id, rpId = null) {
|
||||
try {
|
||||
const session_text = sessionStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
const session_data = JSON.parse(session_text)
|
||||
|
||||
let req = {session: session_data.session, device_id: device_id}
|
||||
if (rpId && 0 < rpId.length) {
|
||||
req.rp = { id: rpId };
|
||||
}
|
||||
|
||||
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/dvc/rm", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
});
|
||||
const resp = await response.json();
|
||||
|
||||
return resp
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
let msg = err.message ? err.message : err;
|
||||
//console.error("Assertion err: ", err);
|
||||
var errRtn = {status:'failed', errorMessage: msg};
|
||||
if(err.name) errRtn.name = err.name
|
||||
return errRtn;
|
||||
}
|
||||
}
|
||||
|
||||
function getSessionId() {
|
||||
var rtn = null
|
||||
try {
|
||||
const session_text = sessionStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
if(session_text){
|
||||
const session_data = JSON.parse(session_text)
|
||||
|
||||
rtn = session_data.session
|
||||
}
|
||||
|
||||
return rtn
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function validSession(rpId = null) {
|
||||
try {
|
||||
const session_text = sessionStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
if(!session_text) return false
|
||||
|
||||
const session_data = JSON.parse(session_text)
|
||||
|
||||
let req = {session: session_data.session}
|
||||
if (rpId && 0 < rpId.length) {
|
||||
req.rp = { id: rpId };
|
||||
}
|
||||
|
||||
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/validsession", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
});
|
||||
const resp = await response.json();
|
||||
|
||||
return resp.status === 'ok'
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function logoutFido2UserSession(){
|
||||
const session_text = sessionStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
|
||||
if(!session_text) return
|
||||
|
||||
const session_data = JSON.parse(session_text)
|
||||
let req = {session: session_data['session'], username: session_data['uid']}
|
||||
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/usr/delsession", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
});
|
||||
|
||||
sessionStorage.removeItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION);
|
||||
}
|
||||
|
||||
async function getRegistrationUser(reg_session_id){
|
||||
try {
|
||||
let req = {session_id: reg_session_id}
|
||||
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/reg/username", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(req)
|
||||
});
|
||||
const resp = await response.json();
|
||||
|
||||
return resp.username
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function errProcessFido2(result){
|
||||
alert(errMessageFido2(result));
|
||||
}
|
||||
|
||||
function errMessageFido2(result){
|
||||
var rtn
|
||||
if(result.errCode && fido2LibErrCodes.unknown != result.errCode ){
|
||||
switch (result.errCode){
|
||||
case fido2LibErrCodes.user_canceled:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr102:');
|
||||
break;
|
||||
case fido2LibErrCodes.timeout:
|
||||
rtn=getI18NErrorMessage('Fido2LibErr103:');
|
||||
break;
|
||||
default:
|
||||
rtn=result.errorMessage?result.errorMessage:getI18NErrorMessage('Fido2LibErr104:');
|
||||
}
|
||||
}else if(result.name && "InvalidStateError" === result.name){
|
||||
rtn=getI18NErrorMessage('Fido2LibErr105:');
|
||||
}else if(result.errorMessage){
|
||||
const msg = getI18NErrorMessage(result.errorMessage);
|
||||
rtn=msg?msg:result.errorMessage;
|
||||
}else{
|
||||
rtn=getI18NErrorMessage(i18n_messages, 'Fido2LibErr104:');
|
||||
}
|
||||
|
||||
return rtn;
|
||||
}
|
||||
|
||||
const fido2LibErrCodes = {
|
||||
user_canceled : -101,
|
||||
timeout : -102,
|
||||
unknown : -999
|
||||
}
|
||||
|
||||
const errMsgs = new Map();
|
||||
const fido2LibErrMsgLanguages = {
|
||||
english: 'en-US',
|
||||
japanese: 'ja',
|
||||
chinese_cn: 'zh-CN',
|
||||
//chinese_tw: 'zh-TW',
|
||||
}
|
||||
errMsgs.set(fido2LibErrMsgLanguages.english, new Map());
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr101:', 'Unregistered enterprise authenticator aaguid!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr102:', 'Unable to authenticate with a unique device binding key from another device!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr103:', 'Unable to verify signature!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr104:', 'Key not found!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr105:', 'Username does not exist!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr106:', 'Unique Device ID is null!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr107:', '/attestation/result request body has no ID field!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr108:', 'ID field is not Base64Url encoded in /attestation/result request body!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr109:', '/attestation/result request body has no TYPE field!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr110:', 'TYPE field is not a DOMString in /attestation/result request body!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr111:', 'The TYPE field is not a public key in the /attestation/result request body!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr112:', 'ID field is not a DOMString in /attestation/result request body!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr115:', 'authenticatorData not found!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr116:', 'authenticatorData is not base64 URL encoded!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr117:', 'Signature not found!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr118:', 'Signature is not base64 URL encoded!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr119:', 'No user session!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('SvrErr120:', 'User has reached the device limit!');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr101:', 'Your browser does not support FIDO2.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr102:', 'The user canceled.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr103:', 'The process timeout.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr104:', 'System error.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr105:', 'The same authenticator cannot be registered again.');
|
||||
|
||||
errMsgs.set(fido2LibErrMsgLanguages.japanese, new Map());
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr101:', '登録されていないエンタープライズ認証デバイス aaguid!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr102:', '別のデバイスからの一意のデバイス バインド キーで認証できません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr103:', '署名を認証できません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr104:', 'キーが見つかりません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr105:', 'ユーザー名は存在しません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr106:', '固有のデバイス ID が null です!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr107:', '/attestation/result request の本文に ID フィールドがありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr108:', 'ID フィールドは、/attestation/result リクエストの本文でエンコードされた Base64Url ではありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr109:', '/attestation/result リクエストのボディに TYPE フィールドがありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr110:', 'TYPE フィールドは、/attestation/result リクエストの本文の DOMString ではありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr111:', 'TYPE フィールドは、/attestation/result リクエストの本文の公開鍵ではありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr112:', 'ID フィールドは、/attestation/result リクエストの本文の DOMString ではありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr115:', 'authenticatorData が見つかりません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr116:', 'authenticatorData は base64 URL エンコードされていません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr117:', '署名が見つかりません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr118:', '署名は base64 URL エンコードされていません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr119:', 'ユーザーセッションがありません!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr120:', 'ユーザーはデバイスの制限数に達しました!');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr101:', 'お使いのブラウザは FIDO2 をサポートしていません。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr102:', 'ユーザーがキャンセルしました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr103:', 'プロセスがタイムアウトしました。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr104:', 'システムエラー。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr105:', '同じ認証デバイスを再登録することはできません。');
|
||||
|
||||
errMsgs.set(fido2LibErrMsgLanguages.chinese_cn, new Map());
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr101:', '未注册的企业认证器 aaguid!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr102:', '无法使用来自其他设备的唯一设备绑定密钥进行身份验证!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr103:', '无法验证签名!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr104:', '认证Key未找到!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr105:', '用户名不存在!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr106:', 'Unique Device ID 为 null!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr107:', '/attestation/result请求体没有ID字段!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr108:', 'ID字段不是/attestation/result请求体中编码的Base64Url!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr109:', '/attestation/result请求体没有TYPE字段!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr110:', '/attestation/result 请求正文中的 TYPE 字段不是 DOMString!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr111:', 'TYPE字段不是/attestation/result请求体中的公钥!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr112:', 'ID 字段不是 /attestation/result 请求体中的 DOMString!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr115:', 'authenticatorData 未找到!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr116:', 'authenticatorData 不是 base64 URL 编码!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr117:', '未找到签名!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr118:', '签名不是 base64 URL 编码!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr119:', '未建立用户会话!');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr120:', '用户已达到设备限制数!');
|
||||
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr101:', '您的浏览器不支持FIDO2.');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr102:', '用户取消了操作。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr103:', '操作超时。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr104:', '系统错误。');
|
||||
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr105:', '无法再次注册相同的认证器。');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {String} errorMessage
|
||||
* @param {errMsgLanguages} language
|
||||
*/
|
||||
function getI18NErrorMessage(errorMessage, language = null){
|
||||
var lang = language ? language : window.navigator.language
|
||||
var msgs = errMsgs.get(lang)
|
||||
if(!msgs)msgs = errMsgs.get(fido2LibErrMsgLanguages.english)
|
||||
if(errorMessage){
|
||||
const msgHeader = 0<errorMessage.indexOf(':')?errorMessage.substring(0, errorMessage.indexOf(':')):errorMessage
|
||||
const msg = msgs.get(msgHeader+":")
|
||||
return msg?msgHeader+":"+msg:errorMessage;
|
||||
} else return errorMessage;
|
||||
}
|
||||
|
||||
/** ===utils=== */
|
||||
|
||||
function isWebAuthnSupported() {
|
||||
if (window.PublicKeyCredential) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function makePublicKey(attOptsResp) {
|
||||
if (attOptsResp.excludeCredentials) {
|
||||
attOptsResp.excludeCredentials = attOptsResp.excludeCredentials.map(
|
||||
function (cred) {
|
||||
cred.id = _base64ToArrayBuffer(_fromBase64URL(cred.id));
|
||||
cred.transports = ["internal", "usb", "ble", "nfc"];
|
||||
return cred;
|
||||
}
|
||||
);
|
||||
|
||||
//console.log("Attestation Options:");
|
||||
//console.log(attOptsResp);
|
||||
}
|
||||
|
||||
const keys = {
|
||||
publicKey: {
|
||||
attestation: attOptsResp.attestation,
|
||||
authenticatorSelection: attOptsResp.authenticatorSelection,
|
||||
excludeCredentials: attOptsResp.excludeCredentials,
|
||||
rp: attOptsResp.rp,
|
||||
user: {
|
||||
id: _stringToArrayBuffer(attOptsResp.user.id), //_base64ToArrayBuffer(_fromBase64URL(attOptsResp.user.id)),
|
||||
name: attOptsResp.user.name,
|
||||
displayName: attOptsResp.user.displayName,
|
||||
},
|
||||
pubKeyCredParams: attOptsResp.pubKeyCredParams,
|
||||
timeout: attOptsResp.timeout,
|
||||
challenge: _base64ToArrayBuffer(_fromBase64URL(attOptsResp.challenge)),
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
async function doAttestation(username, displayName, rpId, userVerification = 'preferred') {
|
||||
var process_time_limit = Number.MAX_SAFE_INTEGER
|
||||
try {
|
||||
const attestationOptions = {
|
||||
username: username,
|
||||
displayName: encodeURIComponent(displayName),
|
||||
authenticatorSelection: {
|
||||
//authenticatorAttachment: "platform",
|
||||
userVerification: userVerification,
|
||||
requireResidentKey: false,
|
||||
},
|
||||
//attestation: "none",
|
||||
};
|
||||
|
||||
if (rpId && 0 < rpId.length) {
|
||||
attestationOptions.rp = { id: rpId }
|
||||
}
|
||||
|
||||
const svrUrl = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)
|
||||
const response = await fetch(svrUrl + "/attestation/options", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(attestationOptions),
|
||||
});
|
||||
|
||||
const resp = await response.json();
|
||||
if (resp.status === "failed") {
|
||||
return {status:'failed', errorMessage: resp.errorMessage}
|
||||
} else {
|
||||
process_time_limit = (new Date()).getTime() + resp.timeout;
|
||||
const res = await navigator.credentials.create(makePublicKey(resp));
|
||||
if (res) {
|
||||
let attResult = {
|
||||
id: res.id,
|
||||
rawId: _toBase64URL(btoa(_bufferToString(res.rawId)))
|
||||
,
|
||||
type: "public-key",
|
||||
response: {
|
||||
clientDataJSON: _toBase64URL(btoa(_bufferToString(res.response.clientDataJSON)))
|
||||
,
|
||||
attestationObject: _toBase64URL(btoa(_bufferToString(res.response.attestationObject)))
|
||||
,
|
||||
},
|
||||
};
|
||||
|
||||
const result = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/attestation/result", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(attResult),
|
||||
});
|
||||
|
||||
const respResult = await result.json();
|
||||
if (respResult) {
|
||||
if (respResult.status === "ok") {
|
||||
return respResult
|
||||
} else {
|
||||
return {status:'failed', errorMessage: respResult.errorMessage}
|
||||
}
|
||||
} else {
|
||||
return {status:'failed', errorMessage: 'Fido2LibErr999:Svr result error'}
|
||||
}
|
||||
} else {
|
||||
return {status:'failed', errorMessage: 'Fido2LibErr999:Undefined Result'};
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
var errRtn = {status:'failed', errorMessage: err.message};
|
||||
if(err.name) errRtn.name = err.name
|
||||
if(err.name && 'NotAllowedError' === err.name){
|
||||
const nowtm = (new Date()).getTime()
|
||||
if(nowtm > process_time_limit){
|
||||
errRtn.errCode = fido2LibErrCodes.timeout
|
||||
}else{
|
||||
errRtn.errCode = fido2LibErrCodes.user_canceled
|
||||
}
|
||||
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||
|
||||
return errRtn;
|
||||
}
|
||||
}
|
||||
|
||||
async function doAssertion(username = null, rpId = null, userVerification = 'preferred') {
|
||||
var process_time_limit = Number.MAX_SAFE_INTEGER
|
||||
try {
|
||||
let authnOptions;
|
||||
/*if (!username) {
|
||||
authnOptions = {
|
||||
authenticatorSelection: {
|
||||
//authenticatorAttachment: "platform",
|
||||
userVerification: "discouraged"
|
||||
}
|
||||
};
|
||||
} else {
|
||||
authnOptions = {
|
||||
username: username,
|
||||
authenticatorSelection: {
|
||||
//authenticatorAttachment: "platform",
|
||||
userVerification: "preferred"
|
||||
}
|
||||
};
|
||||
}*/
|
||||
authnOptions = {
|
||||
username: username,
|
||||
authenticatorSelection: {
|
||||
//authenticatorAttachment: "platform",
|
||||
userVerification: userVerification
|
||||
}
|
||||
};
|
||||
|
||||
if (rpId && 0 < rpId.length) {
|
||||
authnOptions.rp = { id: rpId };
|
||||
}
|
||||
|
||||
const response = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/assertion/options", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(authnOptions)
|
||||
});
|
||||
const resp = await response.json();
|
||||
if ('ok' === resp.status) {
|
||||
process_time_limit = (new Date()).getTime() + resp.timeout;
|
||||
resp.allowCredentials = resp.allowCredentials || [];
|
||||
let mappedAllowCreds = resp.allowCredentials.map(x => {
|
||||
return {
|
||||
id: _base64ToArrayBuffer(_fromBase64URL(x.id)),
|
||||
type: x.type,
|
||||
transports: x.transports // can set like ['internal', 'usb'] to override server settings
|
||||
};
|
||||
});
|
||||
|
||||
const cred = await navigator.credentials.get({
|
||||
publicKey: {
|
||||
challenge: _base64ToArrayBuffer(_fromBase64URL(resp.challenge)),
|
||||
timeout: resp.timeout,
|
||||
rpId: resp.rpId,
|
||||
userVerification: resp.userVerification,
|
||||
allowCredentials: mappedAllowCreds
|
||||
}
|
||||
});
|
||||
|
||||
if (cred) {
|
||||
let authRequest = {
|
||||
id: cred.id,
|
||||
rawId: Array.from(new Uint8Array(cred.rawId)),
|
||||
type: cred.type,
|
||||
response: {
|
||||
authenticatorData: _toBase64URL(btoa(_bufferToString(cred.response.authenticatorData))),
|
||||
clientDataJSON: _toBase64URL(btoa(_bufferToString(cred.response.clientDataJSON))),
|
||||
signature: _toBase64URL(btoa(_bufferToString(cred.response.signature))),
|
||||
userHandle: _toBase64URL(btoa(_bufferToString(cred.response.userHandle))) //_toBase64URL(btoa(_bufferToString(cred.response.userHandle)))
|
||||
}
|
||||
};
|
||||
const res = await fetch(localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL) + "/assertion/result", {
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify(authRequest)
|
||||
});
|
||||
const result = await res.json();
|
||||
if (result.status === 'ok') {
|
||||
return result
|
||||
} else {
|
||||
return {status:'failed', errorMessage: result.errorMessage}
|
||||
}
|
||||
} else {
|
||||
return {status:'failed', errorMessage: 'Fido2LibErr999:Undefined Result'};
|
||||
}
|
||||
} else {
|
||||
return {status:'failed', errorMessage: resp.errorMessage}
|
||||
}
|
||||
} catch (err) {
|
||||
var errRtn = {status:'failed', errorMessage: err.message};
|
||||
if(err.name) errRtn.name = err.name
|
||||
if(err.name && 'NotAllowedError' === err.name){
|
||||
const nowtm = (new Date()).getTime()
|
||||
if(nowtm > process_time_limit){
|
||||
errRtn.errCode = fido2LibErrCodes.timeout
|
||||
}else{
|
||||
errRtn.errCode = fido2LibErrCodes.user_canceled
|
||||
}
|
||||
}else errRtn.errCode = fido2LibErrCodes.unknown
|
||||
|
||||
return errRtn;
|
||||
}
|
||||
}
|
||||
|
||||
function _toBase64URL(s) {
|
||||
return (s = (s = (s = s.split("=")[0]).replace(/\+/g, "-")).replace(/\//g, "_"));
|
||||
}
|
||||
|
||||
function _base64ToArrayBuffer(base64) {
|
||||
var binary_string = window.atob(base64);
|
||||
var len = binary_string.length;
|
||||
var bytes = new Uint8Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function _stringToArrayBuffer(src) {
|
||||
return (new Uint8Array([].map.call(src, function (c) {
|
||||
return c.charCodeAt(0)
|
||||
}))).buffer;
|
||||
}
|
||||
|
||||
function _fromBase64URL(s) {
|
||||
var chk = (s = s.replace(/-/g, "+").replace(/_/g, "/")).length % 4;
|
||||
if (chk) {
|
||||
if (1 === chk) throw new Error("Base64url string is wrong.");
|
||||
s += new Array(5 - chk).join("=");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function _bufferToString(s) {
|
||||
return new Uint8Array(s).reduce((s, e) => s + String.fromCodePoint(e), "");
|
||||
}
|
||||
BIN
files/favicon.ico
Normal file
BIN
files/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
101
files/helpers.js
Normal file
101
files/helpers.js
Normal file
File diff suppressed because one or more lines are too long
112
files/jquery.js
vendored
Normal file
112
files/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
118
files/main.js
Normal file
118
files/main.js
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Main
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let menu, animate;
|
||||
|
||||
(function () {
|
||||
// Initialize menu
|
||||
//-----------------
|
||||
|
||||
let layoutMenuEl = document.querySelectorAll('#layout-menu');
|
||||
layoutMenuEl.forEach(function (element) {
|
||||
menu = new Menu(element, {
|
||||
orientation: 'vertical',
|
||||
closeChildren: false
|
||||
});
|
||||
// Change parameter to true if you want scroll animation
|
||||
window.Helpers.scrollToActive((animate = false));
|
||||
window.Helpers.mainMenu = menu;
|
||||
});
|
||||
|
||||
// Initialize menu togglers and bind click on each
|
||||
let menuToggler = document.querySelectorAll('.layout-menu-toggle');
|
||||
menuToggler.forEach(item => {
|
||||
item.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
window.Helpers.toggleCollapsed();
|
||||
});
|
||||
});
|
||||
|
||||
// Display menu toggle (layout-menu-toggle) on hover with delay
|
||||
let delay = function (elem, callback) {
|
||||
let timeout = null;
|
||||
elem.onmouseenter = function () {
|
||||
// Set timeout to be a timer which will invoke callback after 300ms (not for small screen)
|
||||
if (!Helpers.isSmallScreen()) {
|
||||
timeout = setTimeout(callback, 300);
|
||||
} else {
|
||||
timeout = setTimeout(callback, 0);
|
||||
}
|
||||
};
|
||||
|
||||
elem.onmouseleave = function () {
|
||||
// Clear any timers set to timeout
|
||||
document.querySelector('.layout-menu-toggle').classList.remove('d-block');
|
||||
clearTimeout(timeout);
|
||||
};
|
||||
};
|
||||
if (document.getElementById('layout-menu')) {
|
||||
delay(document.getElementById('layout-menu'), function () {
|
||||
// not for small screen
|
||||
if (!Helpers.isSmallScreen()) {
|
||||
document.querySelector('.layout-menu-toggle').classList.add('d-block');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Display in main menu when menu scrolls
|
||||
let menuInnerContainer = document.getElementsByClassName('menu-inner'),
|
||||
menuInnerShadow = document.getElementsByClassName('menu-inner-shadow')[0];
|
||||
if (menuInnerContainer.length > 0 && menuInnerShadow) {
|
||||
menuInnerContainer[0].addEventListener('ps-scroll-y', function () {
|
||||
if (this.querySelector('.ps__thumb-y').offsetTop) {
|
||||
menuInnerShadow.style.display = 'block';
|
||||
} else {
|
||||
menuInnerShadow.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Init helpers & misc
|
||||
// --------------------
|
||||
|
||||
// Init BS Tooltip
|
||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
||||
});
|
||||
|
||||
// Accordion active class
|
||||
const accordionActiveFunction = function (e) {
|
||||
if (e.type == 'show.bs.collapse' || e.type == 'show.bs.collapse') {
|
||||
e.target.closest('.accordion-item').classList.add('active');
|
||||
} else {
|
||||
e.target.closest('.accordion-item').classList.remove('active');
|
||||
}
|
||||
};
|
||||
|
||||
const accordionTriggerList = [].slice.call(document.querySelectorAll('.accordion'));
|
||||
const accordionList = accordionTriggerList.map(function (accordionTriggerEl) {
|
||||
accordionTriggerEl.addEventListener('show.bs.collapse', accordionActiveFunction);
|
||||
accordionTriggerEl.addEventListener('hide.bs.collapse', accordionActiveFunction);
|
||||
});
|
||||
|
||||
// Auto update layout based on screen size
|
||||
window.Helpers.setAutoUpdate(true);
|
||||
|
||||
// Toggle Password Visibility
|
||||
window.Helpers.initPasswordToggle();
|
||||
|
||||
// Speech To Text
|
||||
window.Helpers.initSpeechToText();
|
||||
|
||||
// Manage menu expanded/collapsed with templateCustomizer & local storage
|
||||
//------------------------------------------------------------------
|
||||
|
||||
// If current layout is horizontal OR current window screen is small (overlay menu) than return from here
|
||||
if (window.Helpers.isSmallScreen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If current layout is vertical and current window screen is > small
|
||||
|
||||
// Auto update menu collapsed/expanded based on the themeConfig
|
||||
window.Helpers.setCollapsed(true, false);
|
||||
})();
|
||||
101
files/menu.js
Normal file
101
files/menu.js
Normal file
File diff suppressed because one or more lines are too long
68
files/page-auth.css
Normal file
68
files/page-auth.css
Normal file
File diff suppressed because one or more lines are too long
211
files/perfect-scrollbar.css
Normal file
211
files/perfect-scrollbar.css
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Container style
|
||||
*/
|
||||
.ps {
|
||||
overflow: hidden !important;
|
||||
overflow-anchor: none;
|
||||
-ms-overflow-style: none;
|
||||
touch-action: auto;
|
||||
-ms-touch-action: auto;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scrollbar rail styles
|
||||
*/
|
||||
.ps__rail-x {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
-webkit-transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
height: 15px;
|
||||
/* there must be 'bottom' or 'top' for ps__rail-x */
|
||||
bottom: 0px;
|
||||
/* please don't change 'position' */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ps__rail-y {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
-webkit-transition: background-color 0.2s linear, opacity 0.2s linear;
|
||||
width: 15px;
|
||||
/* there must be 'right' or 'left' for ps__rail-y */
|
||||
right: 0;
|
||||
/* please don't change 'position' */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ps--active-x > .ps__rail-x,
|
||||
.ps--active-y > .ps__rail-y {
|
||||
display: block;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.ps:hover > .ps__rail-x,
|
||||
.ps:hover > .ps__rail-y,
|
||||
.ps--focus > .ps__rail-x,
|
||||
.ps--focus > .ps__rail-y,
|
||||
.ps--scrolling-x > .ps__rail-x,
|
||||
.ps--scrolling-y > .ps__rail-y {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.ps .ps__rail-x:hover,
|
||||
.ps .ps__rail-y:hover,
|
||||
.ps .ps__rail-x:focus,
|
||||
.ps .ps__rail-y:focus,
|
||||
.ps .ps__rail-x.ps--clicking,
|
||||
.ps .ps__rail-y.ps--clicking {
|
||||
background-color: #eee;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scrollbar thumb styles
|
||||
*/
|
||||
.ps__thumb-x {
|
||||
background-color: #aaa;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s linear, height 0.2s ease-in-out;
|
||||
-webkit-transition: background-color 0.2s linear, height 0.2s ease-in-out;
|
||||
height: 6px;
|
||||
/* there must be 'bottom' for ps__thumb-x */
|
||||
bottom: 2px;
|
||||
/* please don't change 'position' */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ps__thumb-y {
|
||||
background-color: #aaa;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s linear, width 0.2s ease-in-out;
|
||||
-webkit-transition: background-color 0.2s linear, width 0.2s ease-in-out;
|
||||
width: 6px;
|
||||
/* there must be 'right' for ps__thumb-y */
|
||||
right: 2px;
|
||||
/* please don't change 'position' */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ps__rail-x:hover > .ps__thumb-x,
|
||||
.ps__rail-x:focus > .ps__thumb-x,
|
||||
.ps__rail-x.ps--clicking .ps__thumb-x {
|
||||
background-color: #999;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.ps__rail-y:hover > .ps__thumb-y,
|
||||
.ps__rail-y:focus > .ps__thumb-y,
|
||||
.ps__rail-y.ps--clicking .ps__thumb-y {
|
||||
background-color: #999;
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
/* MS supports */
|
||||
@supports (-ms-overflow-style: none) {
|
||||
.ps {
|
||||
overflow: auto !important;
|
||||
}
|
||||
}
|
||||
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
|
||||
.ps {
|
||||
overflow: auto !important;
|
||||
}
|
||||
}
|
||||
.ps {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ps__rail-x {
|
||||
height: 0.25rem;
|
||||
}
|
||||
|
||||
.ps__rail-y {
|
||||
width: 0.25rem;
|
||||
}
|
||||
|
||||
.ps__rail-x,
|
||||
.ps__rail-y,
|
||||
.ps__thumb-x,
|
||||
.ps__thumb-y {
|
||||
border-radius: 10rem;
|
||||
}
|
||||
|
||||
.ps__rail-x:hover,
|
||||
.ps__rail-x:focus,
|
||||
.ps__rail-x.ps--clicking,
|
||||
.ps__rail-x:hover > .ps__thumb-x,
|
||||
.ps__rail-x:focus > .ps__thumb-x,
|
||||
.ps__rail-x.ps--clicking > .ps__thumb-x {
|
||||
height: 0.375rem;
|
||||
}
|
||||
|
||||
.ps__rail-y:hover,
|
||||
.ps__rail-y:focus,
|
||||
.ps__rail-y.ps--clicking,
|
||||
.ps__rail-y:hover > .ps__thumb-y,
|
||||
.ps__rail-y:focus > .ps__thumb-y,
|
||||
.ps__rail-y.ps--clicking > .ps__thumb-y {
|
||||
width: 0.375rem;
|
||||
}
|
||||
|
||||
.ps__thumb-x {
|
||||
height: 0.25rem;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.ps__thumb-y {
|
||||
width: 0.25rem;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.light-style .ps__thumb-x,
|
||||
.light-style .ps__thumb-y {
|
||||
background-color: rgba(67, 89, 113, 0.4);
|
||||
}
|
||||
.light-style .ps__rail-x:hover,
|
||||
.light-style .ps__rail-y:hover,
|
||||
.light-style .ps__rail-x:focus,
|
||||
.light-style .ps__rail-y:focus,
|
||||
.light-style .ps__rail-x.ps--clicking,
|
||||
.light-style .ps__rail-y.ps--clicking {
|
||||
background-color: rgba(67, 89, 113, 0.2);
|
||||
}
|
||||
.light-style .ps__rail-x:hover > .ps__thumb-x,
|
||||
.light-style .ps__rail-y:hover > .ps__thumb-y,
|
||||
.light-style .ps__rail-x:focus > .ps__thumb-x,
|
||||
.light-style .ps__rail-y:focus > .ps__thumb-y,
|
||||
.light-style .ps__rail-x.ps--clicking > .ps__thumb-x,
|
||||
.light-style .ps__rail-y.ps--clicking > .ps__thumb-y {
|
||||
background-color: rgba(67, 89, 113, 0.7);
|
||||
}
|
||||
.light-style .ps-inverted .ps__rail-x:hover,
|
||||
.light-style .ps-inverted .ps__rail-y:hover,
|
||||
.light-style .ps-inverted .ps__rail-x:focus,
|
||||
.light-style .ps-inverted .ps__rail-y:focus,
|
||||
.light-style .ps-inverted .ps__rail-x.ps--clicking,
|
||||
.light-style .ps-inverted .ps__rail-y.ps--clicking {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
.light-style .ps-inverted .ps__thumb-x,
|
||||
.light-style .ps-inverted .ps__thumb-y {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
.light-style .ps-inverted .ps__rail-x:hover > .ps__thumb-x,
|
||||
.light-style .ps-inverted .ps__rail-y:hover > .ps__thumb-y,
|
||||
.light-style .ps-inverted .ps__rail-x:focus > .ps__thumb-x,
|
||||
.light-style .ps-inverted .ps__rail-y:focus > .ps__thumb-y,
|
||||
.light-style .ps-inverted .ps__rail-x.ps--clicking > .ps__thumb-x,
|
||||
.light-style .ps-inverted .ps__rail-y.ps--clicking > .ps__thumb-y {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
@supports (-moz-appearance: none) {
|
||||
#both-scrollbars-example {
|
||||
max-width: 1080px;
|
||||
margin: 0 auto;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
112
files/perfect-scrollbar.js
Normal file
112
files/perfect-scrollbar.js
Normal file
File diff suppressed because one or more lines are too long
112
files/popper.js
Normal file
112
files/popper.js
Normal file
File diff suppressed because one or more lines are too long
806
files/theme-default.css
Normal file
806
files/theme-default.css
Normal file
File diff suppressed because one or more lines are too long
1212
files/ua-parser.js
Normal file
1212
files/ua-parser.js
Normal file
File diff suppressed because it is too large
Load Diff
238
login.html
Normal file
238
login.html
Normal file
@@ -0,0 +1,238 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html
|
||||
lang="en-US"
|
||||
class="light-style customizer-hide"
|
||||
>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"
|
||||
/>
|
||||
|
||||
<title>Login - amiPro sample site </title>
|
||||
|
||||
<meta name="description" content="" />
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/x-icon" href="files/favicon.ico" />
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;1,300;1,400;1,500;1,600;1,700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Icons. Uncomment required icon fonts -->
|
||||
<!-- link rel="stylesheet" href="../assets/vendor/fonts/boxicons.css" / -->
|
||||
|
||||
<!-- Core CSS -->
|
||||
<link rel="stylesheet" href="files/core.css" class="template-customizer-core-css" />
|
||||
<link rel="stylesheet" href="files/theme-default.css" class="template-customizer-theme-css" />
|
||||
<link rel="stylesheet" href="files/demo.css" />
|
||||
|
||||
<!-- Vendors CSS -->
|
||||
<link rel="stylesheet" href="files/perfect-scrollbar.css" />
|
||||
|
||||
<!-- Page CSS -->
|
||||
<!-- Page -->
|
||||
<link rel="stylesheet" href="files/page-auth.css" />
|
||||
<!-- Helpers -->
|
||||
<script src="files/helpers.js"></script>
|
||||
|
||||
<!--! Template customizer & Theme config files MUST be included after core stylesheets and helpers.js in the <head> section -->
|
||||
<!--? Config: Mandatory theme config file contain global vars & default theme options, Set your preferred theme option in this file. -->
|
||||
<script src="files/config.js"></script>
|
||||
|
||||
<script src="files/jquery.js"></script>
|
||||
<script src="files/popper.js"></script>
|
||||
<script src="files/bootstrap.js"></script>
|
||||
<script src="files/perfect-scrollbar.js"></script>
|
||||
<script src="files/menu.js"></script>
|
||||
<script src="files/main.js"></script>
|
||||
|
||||
<script src="files/amipro_utils.js?v=20230414"></script>
|
||||
<script src="files/dfido2-lib.js?v=2025092701"></script>
|
||||
|
||||
<script>
|
||||
<!--
|
||||
// For stand alone: 'https://local.dqj-macpro.com'
|
||||
// For proxy: set 'https://mac-air-m2.dqj-home.com'
|
||||
-->
|
||||
setFidoServerURL('https://mac-air-m2.dqj-home.com');//'https://fido2.amipro.me');
|
||||
|
||||
const i18n_messages = new Map();
|
||||
|
||||
var lang_map = new Map();
|
||||
lang_map.set("en-US", "Welcome to amiPro sample site!");
|
||||
lang_map.set("zh-CN", "欢迎来到 amiPro 示例网站!");
|
||||
lang_map.set("ja", "amiPro サンプルサイトへようこそ!");
|
||||
i18n_messages.set("msg_welcome", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Please sign in to your account and register your devices.");
|
||||
lang_map.set("zh-CN", "请登录您的帐户并注册设备。");
|
||||
lang_map.set("ja", "アカウントにサインインして、デバイスを登録してください。");
|
||||
i18n_messages.set("msg_intro", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "USER ID");
|
||||
lang_map.set("zh-CN", "用户ID");
|
||||
lang_map.set("ja", "ユーザーID");
|
||||
i18n_messages.set("msg_uid", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "PASSWORD");
|
||||
lang_map.set("zh-CN", "密码");
|
||||
lang_map.set("ja", "パスワード");
|
||||
i18n_messages.set("msg_pw", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Any password can sign in");
|
||||
lang_map.set("zh-CN", "任意密码可登录");
|
||||
lang_map.set("ja", "任意のパスワードでログインできます");
|
||||
i18n_messages.set("msg_pw_intro", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Sign in");
|
||||
lang_map.set("zh-CN", "登录");
|
||||
lang_map.set("ja", "ログイン");
|
||||
i18n_messages.set("msg_sign_in", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Input User ID, please!");
|
||||
lang_map.set("zh-CN", "请输入用户ID!");
|
||||
lang_map.set("ja", "ユーザーIDを入力してください!");
|
||||
i18n_messages.set("msg_uid_input", lang_map);
|
||||
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "Passwordless login");
|
||||
lang_map.set("zh-CN", "无密码登录");
|
||||
lang_map.set("ja", "パスワードレス ログイン");
|
||||
i18n_messages.set("title_fido2_login", lang_map);
|
||||
|
||||
window.onload = function() {
|
||||
logoutFido2UserSession();
|
||||
|
||||
if(canTryAutoAuthentication()){
|
||||
lang_map = new Map();
|
||||
lang_map.set("en-US", "If automatic login fails, enter your user ID and click Passwordless login.");
|
||||
lang_map.set("zh-CN", "如果自动登录失败,请输入用户ID并点击“无密码登录”。");
|
||||
lang_map.set("ja", "自動ログインに失敗した場合は、ユーザーIDを入力して「パスワードレス ログイン」をクリックしてください。");
|
||||
i18n_messages.set("msg_intro", lang_map);
|
||||
setI18NText(i18n_messages)
|
||||
authenticate()
|
||||
}else{
|
||||
setI18NText(i18n_messages)
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticate(){
|
||||
var uid = $('#uid').val()
|
||||
if(uid && 0==uid.length)uid=null
|
||||
|
||||
const result = await authenticateFido2(uid)
|
||||
|
||||
if(result.status === 'ok'){
|
||||
//Set your logged in status and jump to user's top page.
|
||||
location.href = 'devices.html?uid='+result.username
|
||||
}else{
|
||||
errProcessFido2(result)
|
||||
}
|
||||
}
|
||||
|
||||
function checkInput(){
|
||||
const uid = $('#uid').val()
|
||||
if(!uid || 0>=uid.length){
|
||||
alert($('#msg_uid_input').html())
|
||||
return false;
|
||||
}else return true;
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Content -->
|
||||
|
||||
<div class="container-xxl">
|
||||
<div class="authentication-wrapper authentication-basic container-p-y">
|
||||
<div class="authentication-inner">
|
||||
<!-- Register -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<!-- Logo -->
|
||||
<div class="app-brand justify-content-center">
|
||||
<a href="https://www.amiPro.me/" class="app-brand-link gap-2">
|
||||
<img style="width:160px;" src="files/favicon.ico"/>
|
||||
</a>
|
||||
</div>
|
||||
<!-- /Logo -->
|
||||
<h4 class="mb-2" id="msg_welcome">Welcome to amiPro sample site!</h4>
|
||||
<p class="mb-4" id="msg_intro">Please sign-in to your account and start the adventure</p>
|
||||
|
||||
<div id="msg_uid_input" style="display:none;">Input User ID, please!</div>
|
||||
|
||||
<form class="mb-3" action="devices.html" method="GET" onsubmit="return checkInput();">
|
||||
<div class="mb-3">
|
||||
<label for="email" class="form-label" id="msg_uid" name="uid">User ID</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="uid"
|
||||
name="uid"
|
||||
placeholder="Enter your User ID"
|
||||
autofocus
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-3 form-password-toggle">
|
||||
<div class="d-flex justify-content-between">
|
||||
<label class="form-label" for="password" id="msg_pw">Password</label>
|
||||
<small id="msg_pw_intro">Any password can login</small>
|
||||
</div>
|
||||
<div class="input-group input-group-merge">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
name="password"
|
||||
placeholder="············"
|
||||
aria-describedby="password"
|
||||
/>
|
||||
<span class="input-group-text cursor-pointer"><i class="bx bx-hide"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-primary d-grid w-100" style="background-color: #ce59d9;border-color: #ce59d9;" id="msg_sign_in">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="text-center" id="fido_btn">
|
||||
<a href="javascript:authenticate(true);">
|
||||
<span id="title_fido2_login">Passwordless login</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /Register -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- / Content -->
|
||||
|
||||
<!-- div class="buy-now">
|
||||
<a
|
||||
href="https://themeselection.com/products/sneat-bootstrap-html-admin-template/"
|
||||
target="_blank"
|
||||
class="btn btn-danger btn-buy-now"
|
||||
>Change language</a
|
||||
>
|
||||
</div -->
|
||||
|
||||
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
102
top.html.noused
Normal file
102
top.html.noused
Normal file
@@ -0,0 +1,102 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>A sample site for FIOD2-Note server integration</title>
|
||||
<script src="jquery.js"></script>
|
||||
<script src="files/dfido2-lib.js?v=2023040205"></script>
|
||||
<script>
|
||||
//for debug
|
||||
//alert(getI18NErrorMessage("SvrErr107:No ID field in the body of /attestation/result test", errMsgLanguages.japanese))
|
||||
|
||||
async function register() {
|
||||
var uid = $('#uid').val();
|
||||
if (!uid || 0 == uid.length) {
|
||||
alert('User Idを入力してください(Please input User Id).');
|
||||
return
|
||||
}
|
||||
var udis = $('#udis').val();
|
||||
if (!udis || 0 == udis.length) {
|
||||
alert('表示名を入力してください(Please input User display).');
|
||||
return
|
||||
}
|
||||
const result = await registerFido2(uid, udis);
|
||||
|
||||
if(result.status === 'ok'){
|
||||
alert('デバイス登録完了。(device registered)');
|
||||
}else{
|
||||
errProcessReg(result)
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticateWithUid() {
|
||||
var uid = $('#uid').val();
|
||||
if (!uid || 0 == uid.length) {
|
||||
alert('User Idを入力してください(Please input User Id.');
|
||||
return
|
||||
}
|
||||
|
||||
const result = await authenticateFido2(uid);
|
||||
|
||||
if(result.status === 'ok'){
|
||||
alert('認証成功。(Authenticated)');
|
||||
}else{
|
||||
errProcessAuth(result)
|
||||
}
|
||||
}
|
||||
|
||||
async function authenticate() {
|
||||
const result = await authenticateFido2();
|
||||
|
||||
if(result.status === 'ok'){
|
||||
alert('認証成功。(Authenticated)' + (result.username?' User:'+result.username:''));
|
||||
}else{
|
||||
errProcessAuth(result)
|
||||
}
|
||||
}
|
||||
|
||||
function errProcessAuth(result){
|
||||
if(result.errorMessage){
|
||||
if(result.name && 'NotAllowedError' === result.name){
|
||||
alert('ユーザーがキャンセルしたか、プロセスがタイムアウトしました。\n(The user canceled, or the process timeout.).');
|
||||
}else if(result.errorMessage.startsWith('SvrErr104:')){
|
||||
alert('このデバイスを登録していません、登録してください。\n(You have not registered this device, please register it.).');
|
||||
}else{
|
||||
const msg = getI18NErrorMessage(result.errorMessage, errMsgLanguages.japanese);
|
||||
alert(msg?msg:result.errorMessage);
|
||||
}
|
||||
}else{
|
||||
alert('システムエラー(System error).');
|
||||
}
|
||||
}
|
||||
|
||||
function errProcessReg(result){
|
||||
if(result.errCode){
|
||||
switch (result.errCode){
|
||||
case fido2LibErrCodes.user_canceled:
|
||||
alert('ユーザーがキャンセルしました。(the user canceled)');
|
||||
break;
|
||||
case fido2LibErrCodes.timeout:
|
||||
alert('プロセスがタイムアウトしました。(the process timeout)');
|
||||
break;
|
||||
default:
|
||||
alert(result.errorMessage?result.errorMessage:'システムエラー(System error).');
|
||||
}
|
||||
}else if(result.errorMessage){
|
||||
const msg = getI18NErrorMessage(result.errorMessage, fido2LibErrMsgLanguages.japanese);
|
||||
alert(msg?msg:result.errorMessage);
|
||||
}else{
|
||||
alert('システムエラー(System error).');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<br><br>
|
||||
<h4>User Id: <input id='uid' name='uid'></h4>
|
||||
<h4>表示名(User display): <input id='udis' name='udis'></h4>
|
||||
<button onclick="register();" style="font-size:large">FIDO2デバイス登録(register FIDO2)</button><br><br>
|
||||
|
||||
<button onclick="authenticate();" style="font-size:large">UserId自動発見認証(authenticate discovery)</button><br><br>
|
||||
<button onclick="authenticateWithUid();" style="font-size:large">UserId指定認証(authenticate)</button>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user