Files
sample-site/files/dfido2-lib.js
2026-02-05 10:22:07 +09:00

802 lines
36 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
*
* @file dfido2-lib.js
* @description FIDO2 library of amipro FIDO2 Server
* @version 2025-12-12
* @author Amipro Co., Ltd. (https://www.amipro.me/)
* @license Copyright (c) Amipro Co., Ltd. All rights reserved.
*/
const DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION = 'fido2_user_session'
const DFIDO2_LIB_LOCALSTG_NAME_REGISTERED = 'dfido2_lib_registered'
const DFIDO2_LIB_LOCALSTG_NAME_SVR_URL = 'dfido2_lib_svr_url'
let configuredServerUrl = null;
/** ===APIs=== */
if(!localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL)){
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL, 'https://fido2.amipro.me')
}
function setFidoServerURL(url){
if (!url || !url.startsWith('https://')) {
throw new Error('serverUrl must be a valid HTTPS URL');
}
configuredServerUrl = url;
localStorage.setItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL, url);
}
function getServerUrl() {
if (configuredServerUrl) {
return configuredServerUrl;
}
const stored = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_SVR_URL);
if (stored) {
configuredServerUrl = stored;
return stored;
}
return 'https://fido2.amipro.me';
}
function canTryAutoAuthentication(){
//const session_text = localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION)
//alert('canTryAuth:'+session_text+"|"+(null != localStorage.getItem(DFIDO2_LIB_LOCALSTG_NAME_REGISTERED)))
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(getServerUrl() + "/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 if (resp.errorMessage && resp.errorMessage.includes('No user session')) {
sessionStorage.removeItem(DFIDO2_LIB_LOCALSTG_NAME_USER_SESSION);
return {status:'ok', devices:[]}
} else {
return {status:'failed', errorMessage: resp.errorMessage}
}
} 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(getServerUrl() + "/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(getServerUrl() + "/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(getServerUrl() + "/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(getServerUrl() + "/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;
case fido2LibErrCodes.invalid_state:
rtn=getI18NErrorMessage('Fido2LibErr105:');
break;
case fido2LibErrCodes.not_allowed:
rtn=getI18NErrorMessage('Fido2LibErr107:');
break;
case fido2LibErrCodes.abort:
rtn=getI18NErrorMessage('Fido2LibErr108:');
break;
case fido2LibErrCodes.not_supported:
rtn=getI18NErrorMessage('Fido2LibErr109:');
break;
case fido2LibErrCodes.security:
rtn=getI18NErrorMessage('Fido2LibErr110:');
break;
case fido2LibErrCodes.network:
rtn=getI18NErrorMessage('Fido2LibErr111:');
break;
case fido2LibErrCodes.constraint:
rtn=getI18NErrorMessage('Fido2LibErr112:');
break;
case fido2LibErrCodes.not_readable:
rtn=getI18NErrorMessage('Fido2LibErr113:');
break;
case fido2LibErrCodes.encoding:
rtn=getI18NErrorMessage('Fido2LibErr114:');
break;
case fido2LibErrCodes.data_error:
rtn=getI18NErrorMessage('Fido2LibErr115:');
break;
default:
rtn=result.errorMessage?result.errorMessage:getI18NErrorMessage('Fido2LibErr104:');
}
}else if(result.name && "InvalidStateError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr105:');
}else if(result.name && "NotAllowedError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr107:');
}else if(result.name && "AbortError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr108:');
}else if(result.name && "NotSupportedError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr109:');
}else if(result.name && "SecurityError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr110:');
}else if(result.name && "NetworkError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr111:');
}else if(result.name && "ConstraintError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr112:');
}else if(result.name && "NotReadableError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr113:');
}else if(result.name && "EncodingError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr114:');
}else if(result.name && "DataError" === result.name){
rtn=getI18NErrorMessage('Fido2LibErr115:');
}else if(result.errorMessage){
const msg = getI18NErrorMessage(result.errorMessage);
rtn=msg?msg:result.errorMessage;
}else{
rtn=getI18NErrorMessage('Fido2LibErr104:');
}
return rtn;
}
const fido2LibErrCodes = {
user_canceled : -101,
timeout : -102,
unknown : -999,
invalid_state : -103,
not_allowed : -104,
abort : -105,
not_supported : -106,
security : -107,
network : -108,
constraint : -109,
not_readable : -110,
encoding : -111,
data_error : -112
}
const errMsgs = new Map();
const 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/WebAuthn.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr102:', 'The operation was canceled by the user.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr103:', 'The operation timed out.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr104:', 'A system error occurred.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr105:', 'The authenticator contains credentials that are already registered with this website.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr106:', 'Another request is already in progress.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr107:', 'The operation was not allowed.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr108:', 'The operation was aborted.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr109:', 'This operation is not supported on your device.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr110:', 'A security error occurred.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr111:', 'A network error occurred.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr112:', 'The operation failed due to a constraint violation.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr113:', 'Could not read the credential.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr114:', 'The data format is invalid.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr115:', 'A data error occurred.');
errMsgs.get(fido2LibErrMsgLanguages.english).set('Fido2LibErr106:', 'Another request is already in progress.');
errMsgs.set(fido2LibErrMsgLanguages.japanese, new Map());
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('SvrErr101:', '登録されていないエンタープライズ認証デバイス aaguid!');
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/WebAuthn をサポートしていません。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr102:', 'ユーザーによって操作がキャンセルされました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr103:', '操作がタイムアウトしました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr104:', 'システムエラーが発生しました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr105:', 'この認証デバイスには、すでにこの网站に登録されている憑拠が含まれています。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr106:', '別のリクエストがすでに進行中です。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr107:', '操作が許可されませんでした。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr108:', '操作が中止されました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr109:', 'この機器ではこの操作はサポートされていません。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr110:', 'セキュリティエラーが発生しました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr111:', 'ネットワークエラーが発生しました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr112:', '制約違反のため操作に失敗しました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr113:', '憑拠を読み取れませんでした。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr114:', 'データ形式が無効です。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr115:', 'データエラーが発生しました。');
errMsgs.get(fido2LibErrMsgLanguages.japanese).set('Fido2LibErr106:', '別のリクエストがすでに進行中です。');
errMsgs.set(fido2LibErrMsgLanguages.chinese_cn, new Map());
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('SvrErr101:', '未注册的企业认证器 aaguid!');
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('Fido2LibErr106:', '另一个请求正在进行中。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr101:', '您的浏览器不支持FIDO2/WebAuthn。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr102:', '操作已被用户取消。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr103:', '操作超时。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr104:', '发生系统错误。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr105:', '您的认证器包含已在此网站注册的凭据。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr106:', '另一个请求正在进行中。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr107:', '操作不被允许。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr108:', '操作已中止。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr109:', '您的设备不支持此操作。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr110:', '发生安全错误。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr111:', '发生网络错误。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr112:', '由于约束冲突,操作失败。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr113:', '无法读取凭据。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr114:', '数据格式无效。');
errMsgs.get(fido2LibErrMsgLanguages.chinese_cn).set('Fido2LibErr115:', '发生数据错误。');
/**
*
* @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 || attOptsResp.user.id || 'user',
displayName: attOptsResp.user.displayName || attOptsResp.user.name || attOptsResp.user.id || 'User',
},
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
if (window._fido2_pending_request) {
return {status:'failed', errorMessage: 'Fido2LibErr106:A request is already pending'};
}
window._fido2_pending_request = true;
try {
const attestationOptions = {
username: username,
displayName: encodeURIComponent(displayName),
authenticatorSelection: {
//authenticatorAttachment: "platform",
userVerification: userVerification,
requireResidentKey: false,
},
//attestation: "none",
};
if (rpId && 0 < rpId.length) {
attestationOptions.rp = { id: rpId }
}
const svrUrl = getServerUrl()
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)))
,
},
};
// Capture transports if available (WebAuthn Level 2+)
if (res.response.getTransports) {
attResult.transports = res.response.getTransports();
}
const result = await fetch(getServerUrl() + "/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 if(err.name && 'InvalidStateError' === err.name){
errRtn.errCode = fido2LibErrCodes.invalid_state
} else if(err.name && 'AbortError' === err.name){
errRtn.errCode = fido2LibErrCodes.abort
} else if(err.name && 'NotSupportedError' === err.name){
errRtn.errCode = fido2LibErrCodes.not_supported
} else if(err.name && 'SecurityError' === err.name){
errRtn.errCode = fido2LibErrCodes.security
} else if(err.name && 'NetworkError' === err.name){
errRtn.errCode = fido2LibErrCodes.network
} else if(err.name && 'ConstraintError' === err.name){
errRtn.errCode = fido2LibErrCodes.constraint
} else if(err.name && 'NotReadableError' === err.name){
errRtn.errCode = fido2LibErrCodes.not_readable
} else if(err.name && 'EncodingError' === err.name){
errRtn.errCode = fido2LibErrCodes.encoding
} else if(err.name && 'DataError' === err.name){
errRtn.errCode = fido2LibErrCodes.data_error
} else {
errRtn.errCode = fido2LibErrCodes.unknown
}
return errRtn;
} finally {
window._fido2_pending_request = false;
}
}
async function doAssertion(username = null, rpId = null, userVerification = 'preferred') {
var process_time_limit = Number.MAX_SAFE_INTEGER
if (window._fido2_pending_request) {
return {status:'failed', errorMessage: 'Fido2LibErr106:A request is already pending'};
}
window._fido2_pending_request = true;
try {
let authnOptions;
/*
Tested the codes below, but most browsers cannot find correct account when username is null.
if (!username || 0 === username.length) {
authnOptions = {
authenticatorSelection: {
//authenticatorAttachment: "platform",
userVerification: userVerification
}
};
} else {
authnOptions = {
username: username,
authenticatorSelection: {
//authenticatorAttachment: "platform",
userVerification: userVerification
}
};
}*/
authnOptions = {
username: username,
authenticatorSelection: {
//authenticatorAttachment: "platform",
userVerification: userVerification
}
};
if (rpId && 0 < rpId.length) {
authnOptions.rp = { id: rpId };
}
const response = await fetch(getServerUrl() + "/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(getServerUrl() + "/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 if(err.name && 'InvalidStateError' === err.name){
errRtn.errCode = fido2LibErrCodes.invalid_state
} else if(err.name && 'AbortError' === err.name){
errRtn.errCode = fido2LibErrCodes.abort
} else if(err.name && 'NotSupportedError' === err.name){
errRtn.errCode = fido2LibErrCodes.not_supported
} else if(err.name && 'SecurityError' === err.name){
errRtn.errCode = fido2LibErrCodes.security
} else if(err.name && 'NetworkError' === err.name){
errRtn.errCode = fido2LibErrCodes.network
} else if(err.name && 'ConstraintError' === err.name){
errRtn.errCode = fido2LibErrCodes.constraint
} else if(err.name && 'NotReadableError' === err.name){
errRtn.errCode = fido2LibErrCodes.not_readable
} else if(err.name && 'EncodingError' === err.name){
errRtn.errCode = fido2LibErrCodes.encoding
} else if(err.name && 'DataError' === err.name){
errRtn.errCode = fido2LibErrCodes.data_error
} else {
errRtn.errCode = fido2LibErrCodes.unknown
}
return errRtn;
} finally {
window._fido2_pending_request = false;
}
}
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), "");
}