Moved ito Gitea

This commit is contained in:
dqj
2025-10-06 21:22:13 +09:00
commit e941063dbd
31 changed files with 30037 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.DS_Store

View File

@@ -0,0 +1,7 @@
{
"webcredentials":{
"apps":[
"RQQ6N82NA8.dqj.dFido2LibDemo"
]
}
}

View File

@@ -0,0 +1,5 @@
{
"webcredentials":{
"apps":["TWH42HVGKA.dqj.dFido2LibDemo"]
}
}

View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

809
files/bootstrap.js vendored Normal file

File diff suppressed because one or more lines are too long

6511
files/boxicons.css Normal file

File diff suppressed because it is too large Load Diff

BIN
files/boxicons/boxicons.eot Normal file

Binary file not shown.

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

27
files/config.js Normal file
View 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

File diff suppressed because one or more lines are too long

107
files/demo.css Normal file
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

101
files/helpers.js Normal file

File diff suppressed because one or more lines are too long

112
files/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

118
files/main.js Normal file
View 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

File diff suppressed because one or more lines are too long

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
View 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

File diff suppressed because one or more lines are too long

112
files/popper.js Normal file

File diff suppressed because one or more lines are too long

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

File diff suppressed because it is too large Load Diff

238
login.html Normal file
View 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="&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;&#xb7;"
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
View 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>